# Correggere ProxyError, SSLError e ConnectTimeout in Requests

requests.exceptions.ProxyError è un sintomo, non una causa. Ecco la gerarchia delle eccezioni decodificata, ogni traceback associato alla sua vera soluzione e una funzione di controllo dello stato che classifica i fallimenti e ruota attorno alle uscite inattive.

`requests.exceptions.ProxyError` è uno dei messaggi di errore meno utili in Python: si attiva per un proxy inattivo, credenziali errate, uno schema sbagliato, un'uscita sovraccarica e un blocco del firewall, tutti con traceback quasi identici. Il trucco per risolverlo rapidamente è sapere che ProxyError non è una causa radice — è una categoria. Nel codice sorgente di Requests, `ProxyError`, `SSLError` e `ConnectTimeout` sono tutte sottoclassi di `ConnectionError`, e ognuna si attiva in una fase specifica del ciclo di vita della richiesta. Leggi la fase e leggi la causa. Questa guida decodifica la gerarchia delle eccezioni, associa ogni traceback comune alla sua vera soluzione e ti fornisce una funzione di controllo dello stato che classifica i fallimenti e ruota automaticamente attorno alle uscite morenti.

## La gerarchia delle eccezioni di requests

Ogni errore relativo ai proxy in Requests discende da `RequestException`. Il ramo utile per il debug è `ConnectionError`, perché le tre eccezioni che effettivamente incontri vivono tutte sotto di esso:

- **ProxyError** — sollevato quando la connessione al proxy stesso fallisce: host irraggiungibile, porta sbagliata, connessione rifiutata o un `407` respinto. Il messaggio spesso annida `Cannot connect to proxy` all'interno di un wrapper `HTTPSConnectionPool(...)`.
- **SSLError** — il proxy si è connesso, ma la stretta di mano TLS con il target è fallita. Nei setup proxy il trigger usuale è `https://` scritto all'interno della chiave `https` invece di `http://`.
- **ConnectTimeout** — il proxy non ha risposto entro la finestra di connessione. Sottoclassa sia `ConnectionError` che `Timeout`, ed è esplicitamente documentato come sicuro da riprovare.
- **ReadTimeout** — il proxy si è connesso e ha inoltrato la richiesta, ma il target è stato troppo lento a rispondere. Questo è un problema del target o della qualità dell'uscita, non un bug di configurazione.
- **MissingSchema / InvalidProxyURL** — sollevato prima di qualsiasi chiamata di rete quando l'URL del proxy è malformato. Questi sono errori di battitura puri.

Poiché i primi tre condividono un genitore, un solo `except requests.exceptions.ConnectionError` li cattura tutti per la logica di riprova — mentre catturare le sottoclassi individualmente ti permette di registrare *perché* ciascuno ha fallito. La configurazione pulita che evita la maggior parte di questi è trattata nella nostra [guida ai proxy di Python Requests](https://quantumproxies.io/blog/python-requests-proxy-guide); questo post riguarda cosa fare una volta che il traceback è già sullo schermo.

Un'abitudine salva più tempo di qualsiasi singola soluzione: leggi il traceback dal basso verso l'alto. Requests incapsula il fallimento sottostante di urllib3, quindi i frame superiori descrivono *dove* è stata fatta la chiamata e i frame inferiori descrivono *cosa* è andato storto. La linea che vuoi è la clausola `Caused by` più interna — nomina il fallimento concreto (una connessione rifiutata, una discrepanza di certificato, un errore di porta analizzata) che l'esterno `ProxyError` o `ConnectionError` sta semplicemente rilanciando. Una volta che puoi leggere quella linea, il resto di questa guida è una tabella di consultazione.

## Il classico ValueError prima di ProxyError

Il traceback proxy più cercato non è nemmeno un ProxyError — è `ValueError: invalid literal for int() with base 10`, lanciato in profondità dentro urllib3. Succede quando incorpori le credenziali nel valore del proxy senza uno schema, quindi il parser legge il testo dopo i due punti come un numero di porta:

```python
# Broken — no scheme, so 'pass@host' is parsed as host:port
proxies = {"https": "user:pass@45.11.22.33:8000"}
# -> ValueError: invalid literal for int() with base 10: 'pass@45.11.22.33'

# Fixed — scheme in front, password URL-encoded if it has @ : or /
from urllib.parse import quote
pw = quote("p@ss:word", safe="")
proxies = {
    "http":  f"http://user:{pw}@residentialpremium.quantumproxies.io:8080",
    "https": f"http://user:{pw}@residentialpremium.quantumproxies.io:8080",
}
```

![Il ciclo di vita della richiesta in quattro fasi, ciascuna con l'eccezione che genera: la costruzione dell'URL del proxy genera MissingSchema, la connessione al proxy genera ProxyError o ConnectTimeout, la stretta di mano TLS genera SSLError, l'attesa del target genera ReadTimeout; la scheda dice quali correggere e quali riprovare.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335156/blog/generated/qp-b9-018-diag1.png)
*Il nome della fase del ciclo di vita indica la causa: un errore di battitura in fase di costruzione è MissingSchema, un fallimento di connessione proxy è ProxyError, un fallimento TLS è SSLError, un'uscita lenta è ReadTimeout.*

## Un controllo dello stato del proxy che classifica i fallimenti

Invece di indovinare, cattura ogni tipo di eccezione e trasformala in un verdetto in inglese semplice. Questa funzione restituisce l'IP di uscita in caso di successo e una ragione etichettata in caso di fallimento — inseriscila davanti a qualsiasi scraping per confermare che il proxy è attivo prima di bruciare richieste su di esso. Funziona contro qualsiasi gateway autenticato, inclusi [proxy residenziali](https://quantumproxies.io/residential-proxies):

```python
import requests

def check_proxy(proxies, url="https://httpbin.org/ip", timeout=(5, 20)):
    try:
        r = requests.get(url, proxies=proxies, timeout=timeout)
        r.raise_for_status()
        return True, r.json().get("origin")
    except requests.exceptions.ProxyError as e:
        return False, f"proxy unreachable or auth rejected: {e}"
    except requests.exceptions.SSLError as e:
        return False, f"TLS failed (https:// in the https key?): {e}"
    except requests.exceptions.ConnectTimeout:
        return False, "proxy did not answer within the connect window"
    except requests.exceptions.ReadTimeout:
        return False, "target too slow after connect (exit quality)"
    except requests.exceptions.RequestException as e:
        return False, f"other request error: {e}"

ok, detail = check_proxy(proxies)
print("OK" if ok else "FAIL", detail)
```

## Quando curl funziona ma Python lancia ProxyError

Se le stesse credenziali hanno successo in curl ma sollevano ProxyError in Python, una variabile d'ambiente sta quasi sempre sovrascrivendo il tuo dizionario. Requests legge `HTTP_PROXY`, `HTTPS_PROXY` e `NO_PROXY` dalla shell, e un valore aziendale obsoleto reindirizza silenziosamente ogni chiamata. Stampa `session.proxies` per vedere cosa viene realmente utilizzato, quindi disabilita completamente la ricerca dell'ambiente:

```python
import requests

session = requests.Session()
session.trust_env = False          # ignore HTTP_PROXY / HTTPS_PROXY from the shell
session.proxies = {
    "http":  "http://USER:PASS@residentialpremium.quantumproxies.io:8080",
    "https": "http://USER:PASS@residentialpremium.quantumproxies.io:8080",
}
print(session.get("https://httpbin.org/ip", timeout=(5, 20)).json())
```

Un `407` che sopravvive a credenziali corrette indica un piano di whitelist IP chiamato da un indirizzo non registrato — l'elenco completo delle cause è nella nostra [guida 407 Proxy Authentication Required](https://quantumproxies.io/blog/fix-407-proxy-authentication-required).

## ProxyError intermittente: ruota, non riavviare

Il caso frustrante è il codice che funziona per venti minuti, poi lancia ProxyError, poi funziona di nuovo. Non è un bug nel tuo script — è un singolo IP di uscita che muore o viene limitato a metà esecuzione. La soluzione è riprovare con rotazione: avvolgi la chiamata, cattura `ConnectionError`, e lascia che un gateway rotante ti fornisca un nuovo IP al tentativo successivo. Attraverso [proxy rotanti](https://quantumproxies.io/rotating-proxies) ogni riprova viaggia su un'uscita diversa, quindi un indirizzo morto non può mai fallire una richiesta due volte:

```python
import requests

def get_with_rotation(url, proxies, attempts=4):
    last = None
    for _ in range(attempts):
        try:
            r = requests.get(url, proxies=proxies, timeout=(5, 20))
            if r.status_code not in (429, 500, 502, 503, 504):
                return r
            last = r.status_code
        except requests.exceptions.ConnectionError as e:  # Proxy/SSL/ConnectTimeout
            last = e
    raise RuntimeError(f"failed after {attempts} attempts: {last}")
```

Se ProxyError persiste attraverso molti IP nuovi, il problema si è spostato dal tuo pool al target: sei bloccato, non disconnesso. Questa è una lotta diversa — vedi la [checklist anti-ban](https://quantumproxies.io/blog/avoid-ip-ban-web-scraping) per il ritmo, gli header e l'igiene della sessione.

C'è un'altra distinzione che vale la pena interiorizzare, perché cambia il modo in cui rispondi. Un `ProxyError` o `ConnectTimeout` significa che la richiesta non è mai stata completata, quindi riprovarla è sicuro anche per un POST — nulla è successo dall'altra parte. Un `ReadTimeout`, al contrario, significa che il target ha ricevuto la tua richiesta e ha semplicemente impiegato troppo tempo a rispondere; riprovare una scrittura non idempotente può causare un doppio invio. Quando costruisci il ciclo di riprova, tratta i fallimenti in fase di connessione come liberamente riprovabili e i fallimenti in fase di lettura come riprovabili solo per GET e HEAD. Questa singola regola previene il bug sottile in cui un proxy instabile trasforma un checkout in tre.

![Un dizionario proxies con credenziali ma senza schema genera ValueError: invalid literal for int() with base 10, e la versione corretta aggiunge http:// e una password codificata per URL; la scheda abbina i messaggi di errore comuni alla loro correzione in una riga, dallo schema mancante alla rotazione dell'uscita in caso di errori intermittenti.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335157/blog/generated/qp-b9-018-diag2.png)
*Leggi il messaggio, applica la soluzione: host errato, schema mancante, chiave TLS sbagliata, password non codificata o un'uscita morente si mappano ciascuno a un singolo passaggio correttivo.*

## Domande frequenti

### Cosa causa requests.exceptions.ProxyError: impossibile connettersi al proxy?

L'host o la porta del proxy sono sbagliati, il proxy è inattivo o un firewall sta bloccando la connessione prima che qualsiasi richiesta parta. Verifica l'endpoint con `curl -x` usando le stesse credenziali; se curl fallisce anche, il proxy è irraggiungibile, e se curl ha successo, una variabile d'ambiente o un dizionario malformato nel tuo Python è il colpevole.

### Perché ProxyError è incapsulato in HTTPSConnectionPool?

Quell'incapsulamento nomina semplicemente il pool di connessioni che urllib3 ha usato per raggiungere il target — è rumore attorno al vero messaggio annidato all'interno. Leggi la clausola `Caused by` più interna: `Cannot connect to proxy` significa un fallimento di connessione, mentre un messaggio `407` o SSL all'interno dello stesso pool indica un problema di autenticazione o di TLS/schema invece.

### Come faccio a impedire che requests legga i proxy dall'ambiente?

Imposta `session.trust_env = False` sulla tua Session, o passa `trust_env=False` in modo equivalente, così Requests ignora `HTTP_PROXY` e `HTTPS_PROXY`. Questa è la soluzione quando un proxy funziona in una shell ma lancia ProxyError in un'altra, o quando una variabile aziendale dirotta uno scraper che non hai configurato per usare un proxy.

### ConnectTimeout è sicuro da riprovare?

Sì — la documentazione di Requests segna `ConnectTimeout` come sicuro da riprovare perché la richiesta non ha mai raggiunto il server, quindi non si è potuto verificare alcun effetto collaterale. Riprovalo, idealmente attraverso un gateway rotante in modo che il tentativo successivo utilizzi un'uscita diversa e più veloce. `ReadTimeout` è più rischioso da riprovare alla cieca su metodi non idempotenti come POST.

Una volta che smetti di trattare ProxyError come un singolo errore e inizi a leggerlo per fase del ciclo di vita, le soluzioni sono meccaniche: errori di battitura nello schema si sollevano prima della rete, fallimenti di connessione nominano un proxy morto, errori SSL nominano una chiave sbagliata, e fallimenti intermittenti vogliono rotazione, non un riavvio. Uscite pulite rimuovono la maggior parte di essi completamente.

[Scrape su IP residenziali che rimangono attivi](https://quantumproxies.io/residential-proxies)
