Proxy in Python Requests: autenticazione, timeout ed errori
Come instradare Python Requests attraverso un proxy HTTP o SOCKS5: il dict proxies, autenticazione utente/password, variabili d'ambiente, timeout, e che cosa significano davvero ProxyError, 407 e SSLError.
Questa guida è per gli sviluppatori che devono far passare Python Requests attraverso un proxy e continuare a lavorare quando il proxy si comporta male. Gli esempi usano host segnaposto e credenziali fittizie. Top Proxy non vende IP e non distribuisce elenchi gratuiti; confrontiamo i servizi che lo fanno.
Gli snippet puntano a Requests 2.34.2 su PyPI, che richiede Python 3.10 o più recente. Il comportamento del proxy sotto segue la documentazione di uso avanzato di Requests. Abbiamo rieseguito i casi chiave il 2026-10-14 contro una fixture loopback fissata (Python 3.14.6, Requests 2.34.2, urllib3 2.8.0, PySocks 1.7.1, verifica TLS accesa). Quel laboratorio mostra solo il comportamento di protocollo, non la velocità, le località o la qualità IP di un fornitore.
01Un esempio minimo di proxy in Python Requests
Ogni metodo di richiesta accetta un dict proxies. Le chiavi sono lo schema dell'URL di destinazione; i valori sono l'URL del proxy:
import requests
proxies = {
"http": "http://203.0.113.10:8080",
"https": "http://203.0.113.10:8080",
}
r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=(3.05, 27))
print(r.status_code, r.json())
Due dettagli fanno inciampare. Primo, la chiave "https" di solito punta a un URL di proxy http://. È corretto: il client chiede al proxy un tunnel CONNECT e il TLS corre al suo interno. Usare https:// lì solo se il fornitore termina il TLS sulla porta del proxy. Secondo, scrivere l'URL del proxy con uno schema esplicito (http://, https://, socks5:// o socks5h://) così il protocollo non è ambiguo. Requests 2.34.2 antepone http:// a un normale host:port senza schema; non è un URL di proxy non valido. InvalidProxyURL viene lanciato quando la stringa, dopo il parsing, non ha ancora un host, non perché lo schema è stato omesso.
httpbin.org/ip restituisce JSON con un campo origin, che è l'indirizzo visto dal server. Confrontarlo con l'IP o l'intervallo di uscita che il fornitore documenta per il piano, e annotare l'indirizzo diretto su Il mio IP. Un indirizzo invariato da solo non dimostra che il proxy è stato bypassato. Alcune configurazioni escono dalla stessa rete in cui si sta. Se serve solo sapere se un endpoint risponde, usare il controllo proxy o la procedura più lunga come controllare se un proxy funziona.
Una chiave scheme://hostname instrada un host esatto in modo diverso:
proxies = {
"https": "http://203.0.113.10:8080",
"https://api.internal.example": "http://198.51.100.5:3128",
}
Quel match di host è esatto. Non si applica a ogni nome sotto lo stesso dominio.
02Impostazioni proxy su una Session (e la trappola dell'ambiente)
Una Session riutilizza le connessioni e tiene i default in un posto:
session = requests.Session()
session.proxies.update(proxies)
session.get("https://httpbin.org/ip", timeout=10)
La documentazione di Requests avverte che session.proxies può non comportarsi come ci si aspetterebbe. I valori impostati lì possono essere sovrascritti dai proxy dell'ambiente (qualunque cosa restituisca urllib.request.getproxies()). Un HTTPS_PROXY ereditato può vincere in silenzio. Due modi per rendere la scelta esplicita:
# Option 1: pass proxies on every call (the documented fix)
session.get(url, proxies=proxies, timeout=10)
# Option 2: stop the session from reading the environment
session.trust_env = False
session.proxies.update(proxies)
trust_env = False ferma anche la sessione dal leggere .netrc e variabili del bundle CA come REQUESTS_CA_BUNDLE.
Questo non è un avviso di carta. Nella fixture del 2026-10-14, Session.proxies puntava a un proxy loopback mentre HTTP_PROXY / HTTPS_PROXY puntavano a un altro. Il proxy dell'ambiente ha ricevuto il GET. La stessa sessione, con proxies= passato sulla chiamata, ha colpito invece il proxy della sessione. Una seconda sessione con trust_env = False ha usato Session.proxies e ha ignorato l'ambiente. Se uno script usa «a caso» il proxy della CI, guardare qui prima di riscrivere l'URL.
03Variabili d'ambiente del proxy in Python Requests
Se non si passa affatto proxies, Requests legge le variabili standard http_proxy, https_proxy, no_proxy e all_proxy. In un processo normale funzionano anche i nomi in maiuscolo. Non è la regola HTTP di curl, dove http_proxy è la variabile minuscola per HTTP. Un'eccezione di CPython: se è impostato REQUEST_METHOD (un processo CGI), urllib.request.getproxies_environment() scarta il mapping HTTP arrivato da HTTP_PROXY, così un header client Proxy: non può iniettarlo. Il minuscolo http_proxy può comunque applicarsi.
export HTTP_PROXY="http://203.0.113.10:8080"
export HTTPS_PROXY="http://203.0.113.10:8080"
export NO_PROXY="localhost,127.0.0.1,.internal.example"
python my_script.py
Senza NO_PROXY, le chiamate a localhost o a servizi interni possono passare dal proxy dell'ambiente e andare in timeout. NO_PROXY governa solo i proxy presi dall'ambiente. Un host che corrisponde a NO_PROXY può comunque essere proxato se si passa un dict proxies= esplicito che lo copre.
La fixture ha confermato entrambi i lati. Con NO_PROXY=127.0.0.1,localhost e senza argomento proxies=, il GET è andato dritto all'origine locale; nessuno dei due proxy di laboratorio l'ha visto. Lo stesso host, con un dict proxies= esplicito, è passato dal proxy che abbiamo passato. Per questo un bug «funziona nella shell, non nel worker» è spesso due percorsi di configurazione diversi, non due librerie diverse.
| Da dove viene l'impostazione | Si applica a | Quando usarla |
|---|---|---|
argomento proxies= sulla richiesta | Quella sola chiamata, sovrascrivendo i proxy dell'ambiente | Script che devono usare un'uscita specifica, per es. rotazione per richiesta |
Session.proxies | Ogni chiamata su quella sessione, a meno che l'ambiente non la sovrascriva | Client di lunga durata, idealmente con trust_env = False |
HTTP_PROXY / HTTPS_PROXY / ALL_PROXY | Ogni chiamata Requests nel processo che si fida ancora dell'ambiente | Strumenti di terze parti e CI dove non si può modificare il codice |
NO_PROXY | Host che saltano i proxy dell'ambiente (un proxies= esplicito può comunque coprirli) | Localhost, endpoint di metadata, API interne |
04Autenticazione del proxy in Python Requests (nome utente e password)
La maggior parte dei proxy a pagamento richiede un login. Requests supporta l'autenticazione HTTP Basic per i proxy attraverso l'URL stesso, http://user:***@host:port, nel dict proxies o in una qualsiasi delle variabili d'ambiente:
import os
from urllib.parse import quote
user = quote(os.environ["PROXY_USER"], safe="")
password = quote(os.environ["PROXY_PASS"], safe="")
host = "gate.example-provider.com:7000"
proxy_url = f"http://{user}:{password}@{host}"
proxies = {"http": proxy_url, "https": proxy_url}
Caricare le credenziali da un secrets manager a runtime. La documentazione di Requests chiama un rischio di sicurezza conservarle in variabili d'ambiente o in file versionati. Percent-encodare sia il nome utente sia la password. Requests fa l'unquote di entrambe le parti che tira dall'URL, quindi qualsiasi @, :, / o # in ciascuno deve passare da quote(..., safe=""). Altrimenti l'URL può essere analizzato male, e il proxy non vede mai le credenziali che si intendeva inviare. Dopo la decodifica, lo user-id HTTP Basic non può contenere i due punti (RFC 7617 sezione 2); la password sì. Percent-encodare i due punti nel nome utente non lo rende legale: Requests li riporta a due punti con l'unquote. Un due punti extra non encodato nello userinfo viene analizzato come inizio della password (alice:bob:secret diventa utente alice e password bob:secret).
I flag di paese o di sessione sticky nel nome utente (user-country-us-session-abc123) sono sintassi del fornitore, non una funzione di Requests. Da soli non concedono una geografia o un account.
Nelle risposte più vecchie si può anche vedere requests.auth.HTTPProxyAuth. Aggiunge un header Proxy-Authorization all'oggetto request. Nel test loopback ha autenticato le richieste HTTP in chiaro (200), ma la richiesta HTTPS è fallita con un ProxyError che citava 407. Coincide con l'header che viaggia sulla richiesta piuttosto che sul CONNECT che il proxy controlla. Preferire le credenziali nell'URL.
Requests gestisce solo l'autenticazione Basic del proxy. I proxy NTLM o Kerberos/Negotiate hanno bisogno di una libreria di autenticazione di terze parti o di un proxy helper locale.
Se la domanda digitata era solo «che cosa significa 407?», quello stato ha una pagina propria: 407 Proxy Authentication Required. Questo articolo resta su come Requests lo espone.
05Proxy SOCKS5 in Requests
Il supporto SOCKS è un extra opzionale che tira dentro PySocks (1.7.1 su PyPI al momento della stesura):
python -m pip install "requests[socks]"
Dopo di che, gli URL SOCKS funzionano nello stesso dict proxies, credenziali comprese:
proxies = {
"http": "socks5h://user:***@203.0.113.10:1080",
"https": "socks5h://user:***@203.0.113.10:1080",
}
Fare attenzione alla h. Con socks5://, la risoluzione DNS avviene sulla sua macchina e al proxy arriva solo l'IP risultante. Con socks5h://, il proxy risolve l'hostname. È la stessa scissione che usa curl.
La fixture l'ha resa visibile su https://example.com/. Entrambi gli schemi hanno restituito 200. Il server SOCKS ha registrato ATYP 1 (IPv4, 172.66.147.243) per socks5:// e ATYP 3 (hostname example.com) per socks5h://. Le chiamate HTTP a 127.0.0.1 non mostrano la differenza, perché un IP è già un indirizzo. Usare socks5h quando non si vuole che il resolver locale veda le lookup. Per il confronto di protocollo nel linguaggio del catalogo, vedere SOCKS5 e proxy HTTP.
| Schema URL del proxy | Extra necessario | Chi risolve il DNS | Uso tipico |
|---|---|---|---|
http:// | Nessuno | Il proxy, per l'hostname richiesto | La maggior parte dei gateway HTTP(S) a pagamento |
https:// | Nessuno | Il proxy | Fornitori che terminano il TLS sulla porta del proxy |
socks5:// | requests[socks] | La sua macchina | Proxy SOCKS dove il DNS locale è accettabile |
socks5h:// | requests[socks] | Il proxy | Proxy SOCKS, le lookup DNS restano dal lato del proxy |
Non è sicuro che protocollo parla un endpoint? Passarlo dal controllo proxy.
06Timeout del proxy in Python Requests
Requests non ha un timeout di default, quindi un proxy fermo può appendere una chiamata per minuti. Passare sempre timeout, idealmente come tupla (connect, read):
r = requests.get(url, proxies=proxies, timeout=(3.05, 27))
Il valore connect copre la connessione TCP, che con un proxy significa la connessione verso il proxy. La documentazione suggerisce un valore poco sopra un multiplo di 3 per allinearsi alla ritrasmissione TCP. Il valore read è il massimo intervallo tra byte dal server, non un tetto sul tempo totale di download. La latenza dipende dal fornitore, dall'uscita e dalla destinazione, quindi impostare il valore read dai tempi di risposta misurati sull'endpoint reale.
Una cattura utile: quando il proxy stesso non accetta la connessione TCP in tempo, Requests 2.34.2 ha lanciato ProxyError che avvolgeva il ConnectTimeoutError di urllib3, non un ConnectTimeout di primo livello. Nella fixture, timeout=(1.0, 5) contro 192.0.2.1:8080 ha prodotto quel wrapper. Dopo che un proxy funzionante si era connesso, un'origine lenta con timeout=(3.05, 1.0) ha lanciato ReadTimeout. Catturare ProxyError e ReadTimeout prima di un ConnectionError generico, e ispezionare il messaggio o __cause__ se serve distinguere «il proxy non ha mai risposto» da «il proxy ha risposto, poi il sito è andato in silenzio».
Per i fallimenti transitori, montare un adapter con retry limitati invece di scrivere un loop a mano:
from requests.adapters import HTTPAdapter
from urllib3.util import Retry
retry = Retry(total=3, backoff_factor=0.5, status_forcelist=[429, 502, 503, 504])
session.mount("https://", HTTPAdapter(max_retries=retry))
session.mount("http://", HTTPAdapter(max_retries=retry))
I retry di stato seguono allowed_methods. Il set di default di urllib3 2.8.0 è GET, HEAD, PUT, DELETE, OPTIONS e TRACE; POST è escluso a meno che non lo si aggiunga. Abilitare il replay solo per le operazioni che è sicuro inviare due volte. total=3 limita i tentativi di retry, non il tempo di orologio. Quando la risposta ha Retry-After, urllib3 attende quel ritardo se respect_retry_after_header è true (il default).
I retry non sistemano un 407, una CA cattiva, o trust_env che punta al gateway sbagliato. Aiutano solo quando il fallimento è davvero transitorio.
07Errori del proxy in Python Requests: che cosa significa ciascuna eccezione
Ordinare i blocchi except con cura. ProxyError, SSLError e ConnectTimeout sono tutti sottoclassi di ConnectionError, e ConnectTimeout è anche un Timeout. Catturare prima quelle specifiche:
from requests.exceptions import (
ProxyError, SSLError, ConnectTimeout, ReadTimeout,
ConnectionError, InvalidProxyURL, RequestException,
)
try:
r = session.get(url, proxies=proxies, timeout=(3.05, 27))
r.raise_for_status()
except ProxyError as e:
... # proxy unreachable, CONNECT refused, or connect-timeout to the proxy; check the message for 407
except SSLError as e:
... # certificate problem; fix trust, don't disable verification
except ConnectTimeout:
... # could not connect in time (more often seen without a proxy)
except ReadTimeout:
... # connected, but the server went quiet
except InvalidProxyURL:
... # malformed proxy URL
except ConnectionError:
... # DNS failure, reset, other network errors
except RequestException:
... # anything else from requests
| Sintomo | Causa probabile | Che cosa provare |
|---|---|---|
ProxyError senza 407 nel messaggio | Niente accetta connessioni su quell'host/porta, proxy giù, un blocco di rete/allowlist, o un connect timeout verso il proxy | Controllare host e porta, poi testare l'endpoint nel controllo proxy; le credenziali non sono il primo sospetto |
ProxyError che cita 407 su un URL HTTPS | Il CONNECT non portava credenziali accettate dal proxy: nessuna inviata, impostate solo via HTTPProxyAuth, mal encodate, formato sbagliato, o piano inattivo | Mettere le credenziali encodate nell'URL del proxy; confermare il formato dello username e lo stato del piano |
| Risposta con stato 407 su un URL HTTP | Il proxy chiede credenziali; su una richiesta inviata senza di esse è solo la challenge | Aggiungere le credenziali all'URL del proxy; se c'erano già, controllare encoding e formato |
| Risposta con stato 401 | La destinazione, non il proxy, vuole autenticazione | Sistemare l'auth del sito; la parte proxy ha funzionato |
Requests ignora i suoi Session.proxies | I proxy dell'ambiente li sovrascrivono | Passare proxies= per chiamata o impostare trust_env = False |
InvalidSchema: Missing dependencies for SOCKS support | requests[socks] non installato | python -m pip install "requests[socks]" |
SSLError: CERTIFICATE_VERIFY_FAILED | Proxy che intercetta o un certificato di laboratorio/self-signed che il bundle di default non fida | Puntare verify= o REQUESTS_CA_BUNDLE al file CA di quel proxy |
| Si appende all'infinito | Nessun timeout impostato | Passare sempre una tupla (connect, read) |
origin non coincide con l'uscita attesa | Proxy non applicato per quello schema, NO_PROXY ha fatto match mentre si dipendeva dai proxy dell'ambiente, o un percorso diverso da quello assunto | Impostare entrambe le chiavi http e https; se si usano i proxy env, controllare NO_PROXY; confrontare con l'IP/intervallo di uscita documentato dal fornitore |
Che cosa ha mostrato un test locale

Abbiamo eseguito questi pattern contro un proxy di test loopback il 2026-10-14: Python 3.14.6, Requests 2.34.2, urllib3 2.8.0, certifi 2026.07.22, PySocks 1.7.1, timeout=(3.05, 10) salvo diversa indicazione, verifica TLS accesa. Utente fittizio demo; la password conteneva punteggiatura ed era percent-encodata negli URL, poi redatta nei log. I controlli HTTPS usavano https://example.com/ più un server locale con una CA di laboratorio. Le righe sotto sono osservazioni catturate da quella fixture, non un conteggio di test automatici passati. Mostra solo il comportamento di protocollo.
| Caso | Destinazione HTTP | Destinazione HTTPS |
|---|---|---|
| Nessuna credenziale proxy | risposta 407 | ProxyError che cita 407 |
| Credenziali percent-encodate nell'URL del proxy | 200 | 200 (example.com e TLS di laboratorio con verify= puntato alla CA di laboratorio) |
Solo HTTPProxyAuth | 200 | ProxyError che cita 407 |
| Porta del proxy chiusa | ProxyError, senza 407 | non testato |
| Auth proxy valida, il sito restituisce 401 | risposta 401 dal sito | non testato |
| CA di default contro certificato self-signed di laboratorio | n/a | SSLError: CERTIFICATE_VERIFY_FAILED; 200 dopo aver fidato la CA di laboratorio |
Session.proxies contro HTTP_PROXY | Il proxy dell'ambiente ha ricevuto il GET | non testato |
proxies= sulla chiamata, env impostato | Il proxy della sessione/aperto ha ricevuto il GET | non testato |
trust_env = False | usato Session.proxies; env ignorato | non testato |
NO_PROXY corrisponde all'host | Diretto all'origine; nessun colpo al proxy | non testato |
proxies= esplicito su un host di NO_PROXY | Comunque proxato | non testato |
| Connect timeout verso il proxy | ProxyError che avvolge ConnectTimeoutError | non testato |
| Read timeout attraverso un proxy funzionante | ReadTimeout | non testato |
SOCKS5 con credenziali encodate ha restituito 200 per HTTP e per https://example.com/. Scissione DNS: socks5:// ha consegnato un indirizzo IPv4 al proxy; socks5h:// ha consegnato l'hostname.
Certificati, proxy che intercettano e verify
Un gateway aziendale di ispezione TLS o un proxy locale che intercetta rifirma l'HTTPS con la propria autorità di certificazione, che Requests rifiuta. Fidare quella CA in modo esplicito invece di spegnere la verifica:
r = requests.get(
url,
proxies={"https": "http://127.0.0.1:8080"},
verify="/path/to/proxy-ca.pem",
timeout=10,
)
oppure export REQUESTS_CA_BUNDLE=/path/to/ca.pem per l'intero processo. Convertire prima un export DER in PEM. L'origine self-signed di laboratorio ha riprodotto la stessa classe di fallimento: il default verify=True ha lanciato SSLError (certificate verify failed: self-signed certificate); la stessa richiesta con verify= puntato alla CA di laboratorio ha restituito 200. verify=False fa accettare a Requests qualsiasi certificato e ignorare i mismatch di hostname, il che sconfigge il TLS per tutto ciò che quel client invia. Non è una correzione normale per un proxy che «non funziona».
08Proxy a rotazione in Python
Molti fornitori commerciali ruotano per lei dietro un singolo host gateway. A seconda della tariffa, l'uscita può cambiare per richiesta o su un intervallo di sessione, e un ID di sessione nel nome utente di solito fissa un'uscita per un po' invece di darne una nuova a ogni chiamata. Quelle regole sono impostazioni del fornitore, quindi controllare la loro documentazione. Proxy sticky e a rotazione copre il linguaggio di prodotto. Se gestisce lei un elenco di endpoint, sceglierne uno per richiesta e passarlo via proxies=:
import itertools
pool = itertools.cycle(
[
"http://user:***@203.0.113.10:8080",
"http://user:***@203.0.113.11:8080",
]
)
for url in urls:
p = next(pool)
session.get(url, proxies={"http": p, "https": p}, timeout=(3.05, 27))
Passare il proxy per chiamata, perché i proxy dell'ambiente possono sovrascrivere Session.proxies. Il tipo di uscita è una scelta distinta: datacenter, residenziale o mobile. Il prezzo e il modo in cui un sito di destinazione tratta il traffico dipendono dalla tariffa, dalla destinazione e dalla reputazione dell'IP, non dalla sola etichetta di tipo. Un proxy da solo non conferisce geografia né idoneità di account.
Prima di comprare, confermare che il piano scelto supporta il protocollo (HTTP o SOCKS5) e il metodo di autenticazione (login o allowlist di IP) di cui il codice ha bisogno. L'automazione del browser è uno stack diverso; per i contesti Playwright vedere Proxy Playwright. Fornitori nel nostro catalogo con gateway proxy:

Bright Data è una grande rete proxy per la raccolta dati: IP residenziali, mobile, ISP e datacenter in circa 195 paesi. Il traffico residenziale si può pagare a consumo senza abbonamento, e gli indirizzi datacenter e ISP arrivano in pacchetti da 10 IP. L'accesso residenziale e mobile richiede la verifica dell'identità.

Oxylabs vende proxy residenziali, mobile, ISP e datacenter sotto un unico abbonamento mensile da $30. Pagamenti: carte, bonifico, AliPay, PayPal.

SOAX vende proxy residenziali e mobile a rotazione su un pool di crediti condiviso, con datacenter e ISP negli stessi piani. Il Builder parte da $200/mese + IVA (200 crediti); il traffico Tier 1 è $3/GB sul Builder.

Decodo (ex Smartproxy) vende proxy residenziali e datacenter. Residenziali: $4/GB senza piano mensile, o pacchetti mensili da $3.75/GB per 3 GB. Pagamenti: carte, PayPal, Alipay, Google/Apple Pay, crypto (con limiti).
Come si usa un proxy con Python Requests?
Come si aggiungono nome utente e password a un proxy di Requests?
Perché Python Requests restituisce 407 Proxy Authentication Required?
Requests supporta i proxy SOCKS5?
Requests usa la variabile d'ambiente HTTP_PROXY?
Che timeout impostare per Requests attraverso un proxy?
Si deve impostare verify=False se il proxy rompe l'HTTPS?
Articoli correlati
Che cosa sono i proxy residenziali e come funzionano
I proxy residenziali escono da indirizzi ISP consumer. Qui si spiegano pool, sessioni sticky, che cosa vedono i siti e che cosa chiedere a un fornitore prima di comprare.
4 minI migliori proxy per il web scraping nel 2026: una lista di controllo per chi compra
Un acquisto scraping nel 2026 parte dalla destinazione, non dal fornitore. Usi questa lista per origine, sessione, geo, fatturazione e un test live prima di salire di scala.
5 minProxy sticky e a rotazione: come funziona il controllo di sessione
Proxy sticky e a rotazione: come funzionano la rotazione a ogni richiesta e quella a timer, quando una sessione sticky batte un IP nuovo, come i cookie interagiscono con il cambio di IP e come testare un ID di sessione da sé.

