Proxy in Python Requests: autenticazione, timeout ed errori

12 min

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'impostazioneSi applica aQuando usarla
argomento proxies= sulla richiestaQuella sola chiamata, sovrascrivendo i proxy dell'ambienteScript che devono usare un'uscita specifica, per es. rotazione per richiesta
Session.proxiesOgni chiamata su quella sessione, a meno che l'ambiente non la sovrascrivaClient di lunga durata, idealmente con trust_env = False
HTTP_PROXY / HTTPS_PROXY / ALL_PROXYOgni chiamata Requests nel processo che si fida ancora dell'ambienteStrumenti di terze parti e CI dove non si può modificare il codice
NO_PROXYHost 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 proxyExtra necessarioChi risolve il DNSUso tipico
http://NessunoIl proxy, per l'hostname richiestoLa maggior parte dei gateway HTTP(S) a pagamento
https://NessunoIl proxyFornitori che terminano il TLS sulla porta del proxy
socks5://requests[socks]La sua macchinaProxy SOCKS dove il DNS locale è accettabile
socks5h://requests[socks]Il proxyProxy 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
SintomoCausa probabileChe cosa provare
ProxyError senza 407 nel messaggioNiente accetta connessioni su quell'host/porta, proxy giù, un blocco di rete/allowlist, o un connect timeout verso il proxyControllare host e porta, poi testare l'endpoint nel controllo proxy; le credenziali non sono il primo sospetto
ProxyError che cita 407 su un URL HTTPSIl CONNECT non portava credenziali accettate dal proxy: nessuna inviata, impostate solo via HTTPProxyAuth, mal encodate, formato sbagliato, o piano inattivoMettere le credenziali encodate nell'URL del proxy; confermare il formato dello username e lo stato del piano
Risposta con stato 407 su un URL HTTPIl proxy chiede credenziali; su una richiesta inviata senza di esse è solo la challengeAggiungere le credenziali all'URL del proxy; se c'erano già, controllare encoding e formato
Risposta con stato 401La destinazione, non il proxy, vuole autenticazioneSistemare l'auth del sito; la parte proxy ha funzionato
Requests ignora i suoi Session.proxiesI proxy dell'ambiente li sovrascrivonoPassare proxies= per chiamata o impostare trust_env = False
InvalidSchema: Missing dependencies for SOCKS supportrequests[socks] non installatopython -m pip install "requests[socks]"
SSLError: CERTIFICATE_VERIFY_FAILEDProxy che intercetta o un certificato di laboratorio/self-signed che il bundle di default non fidaPuntare verify= o REQUESTS_CA_BUNDLE al file CA di quel proxy
Si appende all'infinitoNessun timeout impostatoPassare sempre una tupla (connect, read)
origin non coincide con l'uscita attesaProxy non applicato per quello schema, NO_PROXY ha fatto match mentre si dipendeva dai proxy dell'ambiente, o un percorso diverso da quello assuntoImpostare 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

Risultati osservati dei test Requests dalla fixture proxy locale controllata

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.

CasoDestinazione HTTPDestinazione HTTPS
Nessuna credenziale proxyrisposta 407ProxyError che cita 407
Credenziali percent-encodate nell'URL del proxy200200 (example.com e TLS di laboratorio con verify= puntato alla CA di laboratorio)
Solo HTTPProxyAuth200ProxyError che cita 407
Porta del proxy chiusaProxyError, senza 407non testato
Auth proxy valida, il sito restituisce 401risposta 401 dal sitonon testato
CA di default contro certificato self-signed di laboratorion/aSSLError: CERTIFICATE_VERIFY_FAILED; 200 dopo aver fidato la CA di laboratorio
Session.proxies contro HTTP_PROXYIl proxy dell'ambiente ha ricevuto il GETnon testato
proxies= sulla chiamata, env impostatoIl proxy della sessione/aperto ha ricevuto il GETnon testato
trust_env = Falseusato Session.proxies; env ignoratonon testato
NO_PROXY corrisponde all'hostDiretto all'origine; nessun colpo al proxynon testato
proxies= esplicito su un host di NO_PROXYComunque proxatonon testato
Connect timeout verso il proxyProxyError che avvolge ConnectTimeoutErrornon testato
Read timeout attraverso un proxy funzionanteReadTimeoutnon 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 — proxy residenziali e datacenter
Bright Data — proxy residenziali e datacenter4.41442

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à.

10 paesi
5,00 USD
per 1 GB
Oxylabs — proxy residenziali e datacenter
Oxylabs — proxy residenziali e datacenter4.0771

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

10 paesi
0,45 USD
per 1 GB
SOAX — proxy residenziali e mobile
SOAX — proxy residenziali e mobile3.8218

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.

85 paesi
3,00 USD
per 1 GB
Decodo (Smartproxy) — proxy residenziali e datacenter
Decodo (Smartproxy) — proxy residenziali e datacenter4.22118

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).

83 paesi
0,38 USD
per 1 GB

Come si usa un proxy con Python Requests?

Passare un dict come {"http": "http://host:port", "https": "http://host:port"} come argomento proxies. Le chiavi sono lo schema dell'URL di destinazione; i valori sono URL di proxy. Preferire uno schema esplicito su quei valori così HTTP e SOCKS non si mescolano. Un normale host:port senza schema viene anteposto con http:// in Requests 2.34.2; non viene rifiutato come InvalidProxyURL.

Come si aggiungono nome utente e password a un proxy di Requests?

Metterli nell'URL del proxy (http://user:***@host:port), percent-encodando entrambe le parti con urllib.parse.quote(value, safe=""), e caricarli da un secrets manager invece di scriverli in chiaro. Dopo la decodifica, lo user-id HTTP Basic non può contenere i due punti (RFC 7617); la password sì. Non fare affidamento su due punti percent-encodati per mettere i due punti nel nome utente.

Perché Python Requests restituisce 407 Proxy Authentication Required?

Il proxy vuole credenziali che non ha ricevuto o non ha accettato. Su una richiesta inviata senza credenziali, un 407 è solo la challenge. Altrimenti controllare encoding, formato dello username e stato del piano. Le destinazioni HTTPS lanciano un ProxyError che cita 407; le destinazioni HTTP restituiscono una risposta 407. Un ProxyError senza 407, per esempio da una porta chiusa, punta invece alla connettività. HTTPProxyAuth ha autenticato HTTP nel nostro laboratorio ma non CONNECT HTTPS.

Requests supporta i proxy SOCKS5?

Sì, dopo python -m pip install "requests[socks]". Usare socks5h:// per far risolvere gli hostname al proxy, o socks5:// per risolverli in locale. Nel laboratorio, socks5 ha consegnato un indirizzo IPv4 per example.com e socks5h ha consegnato l'hostname.

Requests usa la variabile d'ambiente HTTP_PROXY?

Sì, quando non si passa proxies: legge http_proxy, https_proxy, all_proxy e no_proxy. In un processo normale funzionano anche i nomi in maiuscolo. Se è impostato REQUEST_METHOD (CGI), urllib ignora HTTP_PROXY per HTTP così un header Proxy non può iniettarlo; il minuscolo http_proxy può comunque applicarsi. Non è la regola HTTP solo-minuscolo di curl. I proxy dell'ambiente possono sovrascrivere Session.proxies, quindi passare proxies per chiamata o impostare session.trust_env = False. NO_PROXY salta solo i proxy dell'ambiente; un dict proxies= esplicito può comunque coprire quegli host.

Che timeout impostare per Requests attraverso un proxy?

Impostarne sempre uno; Requests non ha un default. Partire da una tupla come (3.05, 27) e regolare dopo aver misurato i tempi di risposta attraverso l'endpoint proxy reale. Un connect timeout verso il proxy può emergere come ProxyError che avvolge ConnectTimeoutError; un'origine silenziosa dopo il connect è ReadTimeout.

Si deve impostare verify=False se il proxy rompe l'HTTPS?

No. Fidare la CA del proxy che intercetta con verify= o REQUESTS_CA_BUNDLE. verify=False accetta qualsiasi certificato e ignora i mismatch di hostname. Nel laboratorio, un'origine self-signed ha fallito la verifica di default e è riuscita solo dopo che la CA di laboratorio è stata passata a verify=.