Se stai estraendo dati da siti web o automatizzando richieste tramite proxy in Python, prima o poi ti imbatterai in tre
errori classici: ProxyError,
SSLError: WRONG_VERSION_NUMBER e HTTP
407 Proxy Authentication Required. In questo articolo analizzeremo
la causa di ciascuno di essi e mostreremo codice funzionante per la risoluzione.
Come requests lavora con i proxy: sintassi di base
La libreria requests accetta i proxy tramite un dizionario
proxies, dove le chiavi sono lo schema della richiesta
(http e https),
e i valori sono gli URL del server proxy con autorizzazione opzionale.
import requests
proxies = {
"http": "http://user:password@proxy_host:port",
"https": "http://user:password@proxy_host:port",
}
response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(response.json())
Nota: anche per le richieste HTTPS, lo schema del proxy è solitamente specificato come
http://, perché la maggior parte dei server proxy accetta
un tunnel CONNECT tramite il protocollo HTTP, e non lo incapsula in SSL. Se specifichi
https:// per un proxy che non lo supporta, riceverai
un SSLError, la cui analisi sarà fornita più avanti.
Se stai lavorando con proxy SOCKS5, sarà necessario un pacchetto aggiuntivo:
pip install requests[socks]
# E la sintassi dei proxy cambia:
proxies = {
"http": "socks5://user:password@proxy_host:port",
"https": "socks5://user:password@proxy_host:port",
}
ProxyError: cause e soluzioni
requests.exceptions.ProxyError è un wrapper attorno a eccezioni
di rete a basso livello che si verificano quando si tenta di stabilire una connessione con un server proxy. Le situazioni più comuni sono quattro.
1. Proxy non disponibile o indirizzo/porta errati
requests.exceptions.ProxyError: HTTPSConnectionPool(host='example.com', port=443):
Max retries exceeded with url: / (Caused by ProxyError('Cannot connect to proxy.',
ConnectionResetError(104, 'Connection reset by peer')))
Controlla che l'host e la porta siano inseriti correttamente e che il server proxy sia effettivamente attivo. Il modo più veloce per diagnosticare è
provare a connettersi tramite curl:
curl -x http://user:password@proxy_host:port https://httpbin.org/ip -v
2. Il proxy ha bloccato la tua richiesta (ban per IP o User-Agent)
I proxy dei data center vengono spesso bloccati dai siti target perché i loro range di IP sono noti ai sistemi antifrode. Se l'errore si verifica costantemente quando si accede a un sito specifico (ad esempio, Wildberries, Ozon o social network), e non a tutti i siti contemporaneamente, il problema non è nel proxy stesso, ma nella sua reputazione. In questo caso, passare a proxy residenziali può aiutare — utilizzano IP di utenti reali e vengono bloccati meno frequentemente dai sistemi antifrode.
3. Limite di connessioni simultanee superato
Se stai eseguendo un'estrazione multithreading e apri centinaia di connessioni tramite lo stesso proxy, il server potrebbe
iniziare a chiudere le connessioni in eccesso. La soluzione è utilizzare un pool di connessioni tramite
requests.adapters.HTTPAdapter con un limite
pool_maxsize e
max_retries:
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
retries = Retry(total=3, backoff_factor=1, status_forcelist=[502, 503, 504])
adapter = HTTPAdapter(max_retries=retries, pool_connections=20, pool_maxsize=20)
session.mount("http://", adapter)
session.mount("https://", adapter)
response = session.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
4. Timeout dal lato del server proxy
Se il proxy "rimane bloccato" oltre il timeout impostato, vedrai
ProxyError, avvolto in
ReadTimeoutError. Aumenta il timeout a un valore ragionevole
(di solito 15-30 secondi sono sufficienti) e aggiungi la gestione delle eccezioni separatamente:
try:
response = requests.get(url, proxies=proxies, timeout=20)
except requests.exceptions.ProxyError as e:
print(f"Problema con il proxy: {e}")
except requests.exceptions.Timeout:
print("Il proxy non ha risposto nel tempo assegnato")
SSLError WRONG_VERSION_NUMBER: analisi del problema
Questo errore è uno dei più incomprensibili a prima vista, ma in realtà ha quasi sempre una sola causa.
requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443):
Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL:
WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)')))
WRONG_VERSION_NUMBER significa che il client ha tentato di eseguire
un handshake SSL/TLS dove il server si aspetta traffico HTTP normale (senza crittografia). La causa più comune è un errore
nello schema del proxy specificato nel dizionario proxies.
Errore comune: specificare https:// invece di
http:// nell'indirizzo del server proxy. La maggior parte dei proxy HTTP(S)
accetta connessioni tramite HTTP normale, anche quando proxyizzano il traffico HTTPS tramite il meccanismo CONNECT.
# ERRATO — causerà WRONG_VERSION_NUMBER
proxies = {
"http": "https://user:password@proxy_host:port",
"https": "https://user:password@proxy_host:port",
}
# CORRETTO
proxies = {
"http": "http://user:password@proxy_host:port",
"https": "http://user:password@proxy_host:port",
}
Un'altra causa comune è una porta che fisicamente non supporta TLS. Ad esempio, se il fornitore di proxy ha assegnato la porta 8080 solo per il traffico HTTP, e stai tentando di stabilire una connessione TLS direttamente (saltando CONNECT), il server risponderà con testo normale invece di un handshake TLS, e la libreria client OpenSSL genererà proprio questo errore. Controlla nella documentazione del tuo fornitore di proxy quali porte e protocolli (HTTP/HTTPS/SOCKS5) sono disponibili su ciascuna porta specifica.
Un terzo scenario è che stai utilizzando socket o librerie di terze parti
per stabilire una connessione TCP diretta con il proxy, bypassando il meccanismo standard di requests. In questo caso, assicurati che
la crittografia TLS venga applicata solo al sito web di destinazione reale, e non alla connessione stessa con il proxy.
407 Proxy Authentication Required: autorizzazione
Il codice di risposta 407 è l'equivalente dello standard 401, ma per i server proxy. Significa che il server richiede l'autenticazione, e tu o non l'hai fornita, o l'hai fornita in un formato errato.
Metodo 1: login e password direttamente nell'URL
proxies = {
"http": "http://login:password@proxy_host:port",
"https": "http://login:password@proxy_host:port",
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
Questo metodo è il più semplice, ma se il login o la password contengono caratteri speciali (ad esempio, @,
:, /),
devono essere eseguite l'escape tramite urllib.parse.quote:
from urllib.parse import quote
login = quote("user@domain")
password = quote("p@ssw:ord!")
proxy_url = f"http://{login}:{password}@proxy_host:port"
proxies = {"http": proxy_url, "https": proxy_url}
Metodo 2: intestazione Proxy-Authorization manualmente
Se il fornitore di proxy utilizza uno schema di autorizzazione non standard o hai bisogno di maggiore controllo, puoi costruire l'intestazione manualmente tramite codifica base64:
import base64
import requests
login = "user"
password = "password"
credentials = base64.b64encode(f"{login}:{password}".encode()).decode()
headers = {
"Proxy-Authorization": f"Basic {credentials}"
}
proxies = {
"http": "http://proxy_host:port",
"https": "http://proxy_host:port",
}
response = requests.get(
"https://httpbin.org/ip",
proxies=proxies,
headers=headers,
timeout=10
)
Importante: alcuni fornitori di proxy legano l'autenticazione non a login/password, ma a un IP statico specifico del client (whitelist). Se cambi IP (ad esempio, lavori tramite VPN o IP dinamico del fornitore), l'errore 407 può apparire anche con credenziali corrette — perché il tuo attuale IP esterno non è incluso nella whitelist sul lato proxy. La soluzione è accedere al pannello di controllo del fornitore e aggiornare l'elenco degli IP consentiti, oppure passare a un'autenticazione tramite login/password invece di IP-whitelist.
Logica di retry e gestione delle eccezioni
Nella pratica, gli errori proxy raramente sono sporadici — soprattutto durante l'estrazione di massa. La corretta architettura del codice dovrebbe prevedere tentativi ripetuti con ritardi e registrazione di ogni errore separatamente.
import time
import requests
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def fetch_with_retry(url, proxies, max_retries=3, backoff=2):
for attempt in range(1, max_retries + 1):
try:
response = requests.get(url, proxies=proxies, timeout=15)
response.raise_for_status()
return response
except requests.exceptions.ProxyError as e:
logger.warning(f"[Tentativo {attempt}] ProxyError: {e}")
except requests.exceptions.SSLError as e:
logger.warning(f"[Tentativo {attempt}] SSLError: {e}")
except requests.exceptions.HTTPError as e:
if response.status_code == 407:
logger.error("407: controlla login/password o whitelist IP del proxy")
break
logger.warning(f"[Tentativo {attempt}] HTTPError: {e}")
except requests.exceptions.Timeout:
logger.warning(f"[Tentativo {attempt}] Timeout della connessione")
time.sleep(backoff * attempt)
raise RuntimeError(f"Impossibile ottenere {url} dopo {max_retries} tentativi")
Questo approccio ti fa risparmiare ore di debug: invece di far crashare lo script al primo intoppo di rete, ottieni un log chiaro con l'indicazione del tipo specifico di errore in ogni tentativo.
Rotazione dei proxy e lotta contro i ban
Se ProxyError e 407 sono spesso legati alle impostazioni del codice, i ban di massa e i rifiuti stabili delle connessioni su domini specifici indicano quasi sempre un problema con la qualità stessa del pool di IP. Per attività come l'estrazione di marketplace (Wildberries, Ozon, Avito) o lavoro con social network, è consigliabile ruotare gli IP per ogni richiesta o attraverso un intervallo specificato.
import random
proxy_pool = [
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
]
def get_random_proxy():
proxy = random.choice(proxy_pool)
return {"http": proxy, "https": proxy}
response = requests.get(url, proxies=get_random_proxy(), timeout=15)
Per i parser in produzione, è solitamente più comodo utilizzare proxy con rotazione automatica da parte del fornitore — in questo modo non è necessario mantenere un elenco di IP autonomamente, la rotazione avviene in modo trasparente tramite un endpoint. In questo caso, i proxy residenziali con rotazione sono particolarmente adatti per eludere le protezioni anti-bot su siti con sistemi di rilevamento rigidi, mentre i proxy dei data center sono una buona scelta se la velocità delle richieste è importante e il sito target non blocca aggressivamente i range dei data center.
| Tipo di proxy | Velocità | Rischio di ban | Attività |
|---|---|---|---|
| Data center | Alta | Medio-alto | Estrazione veloce di API aperti |
| Residenziali | Media | Basso | Estrazione di marketplace, social network |
| Mobile | Media-bassa | Minimo | Lavoro con sistemi anti-bot rigidi |
Session, timeout e verifica SSL
Per script a lungo termine (ad esempio, parser ciclici) è consigliabile utilizzare
requests.Session() invece di singole chiamate
requests.get(). La Session riutilizza le connessioni TCP,
il che riduce il carico sul server proxy e diminuisce il numero di ProxyError su grandi volumi di richieste.
session = requests.Session()
session.proxies.update({
"http": "http://user:password@proxy_host:port",
"https": "http://user:password@proxy_host:port",
})
for url in urls_list:
try:
resp = session.get(url, timeout=15)
print(resp.status_code, url)
except requests.exceptions.RequestException as e:
print(f"Errore su {url}: {e}")
Vale la pena menzionare separatamente il parametro verify. Se stai lavorando
con un proxy che sostituisce il certificato SSL (proxy MITM per il debug o alcuni proxy aziendali),
requests potrebbe lamentarsi per la non corrispondenza del certificato. Disabilitare completamente la verifica
(verify=False) è consigliabile solo in un ambiente di test — in produzione
ciò crea il rischio di attacchi "uomo nel mezzo":
# Solo per debug locale!
response = requests.get(url, proxies=proxies, verify=False, timeout=15)
# Il modo corretto è specificare il percorso del certificato proxy attendibile
response = requests.get(url, proxies=proxies, verify="/path/to/proxy_cert.pem")
Checklist di diagnosi degli errori
Se lo script si blocca con un errore proxy, segui questo elenco dall'alto verso il basso — nel 90% dei casi la causa sarà nei primi tre punti.
- Controlla lo schema nel dizionario
proxies— dovrebbe quasi sempre esserehttp://, anche per richieste HTTPS. - Testa il proxy direttamente tramite
curl -x, per escludere problemi nel codice. - Assicurati che login e password non contengano caratteri speciali non escapati nell'URL.
- Controlla se il proxy è legato a un IP specifico (whitelist) invece di autorizzazione tramite login/password.
- Aumenta il
timeouta 15-30 secondi prima di considerare il proxy non funzionante. - Se l'errore appare solo su un sito specifico, il problema è nella reputazione dell'IP, non nel codice.
- Aggiungi logica di retry con ritardi esponenziali invece di tentativi singoli.
- Durante l'estrazione di massa, utilizza la rotazione degli IP, non un singolo proxy statico per tutte le richieste.
Conclusione
La maggior parte degli errori proxy in Python requests si riduce a tre cose: uno schema errato nell'URL del proxy
(http invece di https),
mancanza o formato errato dell'autenticazione, e qualità del pool di IP stesso. ProxyError solitamente indica un problema
di connessione o ban dell'IP, SSLError WRONG_VERSION_NUMBER quasi sempre indica uno schema proxy errato, e 407 —
un problema con login/password o whitelist.
Se ti imbatti regolarmente in ban e errori 407 durante l'estrazione di marketplace o social network, è opportuno rivedere la fonte stessa del proxy, non solo il codice. Per un'estrazione stabile con una bassa percentuale di blocchi, ti consigliamo di provare proxy residenziali — utilizzano indirizzi IP reali e causano significativamente meno ProxyError su siti protetti. Se invece la priorità è la velocità e il volume delle richieste, piuttosto che eludere sistemi anti-bot complessi, considera i proxy dei data center — sono più economici e più veloci per compiti di estrazione semplici.