Proxy Python requests : authentification, délais et erreurs
Comment faire passer Python requests par un proxy HTTP ou SOCKS5 : le dict proxies, l'authentification identifiant/mot de passe, les variables d'environnement, les délais, et ce que ProxyError, 407 et SSLError veulent réellement dire.
Ce guide s'adresse aux développeurs qui ont besoin que Python Requests passe par un proxy et continue de fonctionner quand le proxy se comporte mal. Les exemples utilisent des hôtes fictifs et des identifiants factices. Top Proxy ne vend pas d'IP et ne distribue pas de listes gratuites ; nous comparons les services qui le font.
Les extraits visent Requests 2.34.2 sur PyPI, qui exige Python 3.10 ou plus récent. Le comportement proxy ci-dessous suit la documentation d'usage avancé de Requests. Nous avons relancé les cas clés le 14 octobre 2026 contre un montage loopback figé (Python 3.14.6, Requests 2.34.2, urllib3 2.8.0, PySocks 1.7.1, vérification TLS activée). Ce laboratoire montre le comportement du protocole seulement, pas la vitesse, les emplacements ou la qualité d'IP d'un fournisseur.
01Un exemple minimal de proxy Python requests
Chaque méthode de requête prend un dict proxies. Les clés sont le schéma de l'URL cible ; les valeurs sont l'URL du 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())
Deux détails piègent souvent. D'abord, la clé "https" pointe en général vers une URL de proxy http://. C'est correct : le client demande au proxy un tunnel CONNECT et le TLS s'exécute à l'intérieur. N'utilisez https:// à cet endroit que si votre fournisseur termine le TLS sur le port du proxy. Ensuite, écrivez l'URL du proxy avec un schéma explicite (http://, https://, socks5:// ou socks5h://) pour que le protocole soit sans ambiguïté. Requests 2.34.2 préfixe http:// à un host:port sans schéma ; ce n'est pas une URL de proxy invalide. InvalidProxyURL est levée quand la chaîne n'a toujours pas d'hôte après l'analyse, pas parce que le schéma a été omis.
httpbin.org/ip renvoie du JSON avec un champ origin, qui est l'adresse vue par le serveur. Comparez-la avec l'IP ou la plage de sortie que votre fournisseur documente pour votre forfait, et notez votre adresse directe sur Mon IP. Une adresse inchangée ne prouve pas à elle seule que le proxy a été contourné. Certains montages sortent par le même réseau que le vôtre. Si vous avez seulement besoin de savoir si un point d'accès répond, utilisez le vérificateur de proxies ou le tutoriel plus long comment vérifier qu'un proxy fonctionne.
Une clé scheme://hostname route un hôte exact autrement :
proxies = {
"https": "http://203.0.113.10:8080",
"https://api.internal.example": "http://198.51.100.5:3128",
}
Cette correspondance d'hôte est exacte. Elle ne s'applique pas à chaque nom sous le même domaine.
02Réglages proxy sur une Session (et le piège de l'environnement)
Une Session réutilise les connexions et centralise les défauts :
session = requests.Session()
session.proxies.update(proxies)
session.get("https://httpbin.org/ip", timeout=10)
La documentation de Requests prévient que session.proxies peut ne pas se comporter comme on l'attend. Les valeurs posées là peuvent être écrasées par les proxies de l'environnement (ce que urllib.request.getproxies() renvoie). Un HTTPS_PROXY hérité peut gagner en silence. Deux façons de rendre le choix explicite :
# 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 empêche aussi la session de lire .netrc et les variables de bundle d'AC telles que REQUESTS_CA_BUNDLE.
Ce n'est pas un avertissement théorique. Dans le montage du 14 octobre 2026, Session.proxies pointait vers un proxy loopback tandis que HTTP_PROXY / HTTPS_PROXY pointaient vers un autre. Le proxy d'environnement a reçu le GET. La même session, avec proxies= passé sur l'appel, a touché le proxy de la session. Une seconde session avec trust_env = False a utilisé Session.proxies et ignoré l'environnement. Si un script « au hasard » utilise le proxy de CI, regardez ici avant de réécrire l'URL.
03Variables d'environnement de proxy Python requests
Si vous ne passez pas proxies du tout, Requests lit les variables standard http_proxy, https_proxy, no_proxy et all_proxy. Dans un processus normal, les noms en majuscules marchent aussi. Ce n'est pas la règle HTTP de curl, où http_proxy est la variable en minuscules pour HTTP. Une exception CPython : si REQUEST_METHOD est défini (processus CGI), urllib.request.getproxies_environment() abandonne le mapping HTTP venu de HTTP_PROXY, afin qu'un en-tête client Proxy: ne puisse pas l'injecter. http_proxy en minuscules peut encore s'appliquer.
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
Sans NO_PROXY, les appels vers localhost ou des services internes peuvent passer par le proxy d'environnement et expirer. NO_PROXY ne gouverne que les proxies pris depuis l'environnement. Un hôte qui correspond à NO_PROXY peut encore être proxifié si vous passez un dict proxies= explicite qui le couvre.
Le montage a confirmé les deux côtés. Avec NO_PROXY=127.0.0.1,localhost et sans argument proxies=, le GET est allé tout droit à l'origine locale ; aucun des deux proxies du laboratoire ne l'a vu. Le même hôte, avec un dict proxies= explicite, est passé par le proxy que nous avons fourni. C'est pourquoi un bug « ça marche dans le shell, pas dans le worker » est souvent deux chemins de configuration différents, pas deux bibliothèques différentes.
| D'où vient le réglage | S'applique à | Quand l'utiliser |
|---|---|---|
Argument proxies= sur la requête | Cet appel seulement, en écrasant les proxies d'environnement | Scripts qui doivent utiliser une sortie précise, p. ex. rotation par requête |
Session.proxies | Chaque appel de cette session, sauf si l'environnement l'écrase | Clients longue durée, idéalement avec trust_env = False |
HTTP_PROXY / HTTPS_PROXY / ALL_PROXY | Chaque appel Requests du processus qui fait encore confiance à l'environnement | Outils tiers et CI où vous ne pouvez pas modifier le code |
NO_PROXY | Hôtes qui sautent les proxies d'environnement (un proxies= explicite peut encore les couvrir) | Localhost, points d'accès de métadonnées, API internes |
04Authentification proxy Python requests (identifiant et mot de passe)
La plupart des proxies payants exigent un login. Requests prend en charge l'authentification HTTP Basic pour les proxies via l'URL elle-même, http://user:***@host:port, dans le dict proxies ou dans n'importe laquelle des variables d'environnement :
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}
Chargez les identifiants depuis un gestionnaire de secrets à l'exécution. La documentation de Requests considère que les stocker dans des variables d'environnement ou des fichiers versionnés est un risque de sécurité. Encodez en pourcentage l'identifiant et le mot de passe. Requests décode les deux parties qu'il tire de l'URL, donc tout @, :, / ou # dans l'un ou l'autre doit passer par quote(..., safe=""). Sinon l'URL peut s'analyser de travers, et le proxy ne voit jamais les identifiants que vous vouliez envoyer. Après décodage, l'user-id HTTP Basic ne peut pas contenir de deux-points (RFC 7617 section 2) ; le mot de passe, si. Encoder un deux-points dans l'identifiant ne le rend pas légal : Requests le décode à nouveau en deux-points. Un deux-points supplémentaire non encodé dans le userinfo est analysé comme le début du mot de passe (alice:bob:secret devient l'utilisateur alice et le mot de passe bob:secret).
Les drapeaux de pays ou de session sticky dans l'identifiant (user-country-us-session-abc123) sont une syntaxe fournisseur, pas une fonctionnalité de Requests. Ils n'accordent pas à eux seuls une géographie ou un compte.
Vous pouvez aussi rencontrer requests.auth.HTTPProxyAuth dans des réponses plus anciennes. Il ajoute un en-tête Proxy-Authorization à l'objet requête. Dans le test loopback, il a authentifié les requêtes HTTP simples (200), mais la requête HTTPS a échoué avec un ProxyError mentionnant 407. Cela correspond à l'en-tête qui voyage sur la requête plutôt que sur le CONNECT que le proxy vérifie. Préférez les identifiants dans l'URL.
Requests ne gère que l'authentification Basic du proxy. Les proxies NTLM ou Kerberos/Negotiate ont besoin d'une bibliothèque d'auth tierce ou d'un proxy d'aide local.
Si la question que vous avez tapée était seulement « que veut dire 407 ? », ce statut a sa propre page : 407 Proxy Authentication Required. Cet article reste sur la façon dont Requests le fait remonter.
05Proxies SOCKS5 dans requests
Le support SOCKS est un extra optionnel qui tire PySocks (1.7.1 sur PyPI au moment de l'écriture) :
python -m pip install "requests[socks]"
Ensuite, les URL SOCKS fonctionnent dans le même dict proxies, identifiants compris :
proxies = {
"http": "socks5h://user:***@203.0.113.10:1080",
"https": "socks5h://user:***@203.0.113.10:1080",
}
Faites attention au h. Avec socks5://, la résolution DNS se fait sur votre machine et seule l'IP résultante va au proxy. Avec socks5h://, le proxy résout le nom d'hôte. C'est la même scission que curl.
Le montage l'a rendu visible sur https://example.com/. Les deux schémas ont renvoyé 200. Le serveur SOCKS a journalisé ATYP 1 (IPv4, 172.66.147.243) pour socks5:// et ATYP 3 (nom d'hôte example.com) pour socks5h://. Les appels HTTP vers 127.0.0.1 ne montrent pas la différence, parce qu'une IP est déjà une adresse. Utilisez socks5h quand vous ne voulez pas que votre résolveur local voie les lookups. Pour la comparaison de protocoles dans le langage du catalogue, voir SOCKS5 ou HTTP.
| Schéma d'URL de proxy | Extra nécessaire | Qui résout le DNS | Usage typique |
|---|---|---|---|
http:// | Aucun | Le proxy, pour le nom d'hôte que vous demandez | La plupart des passerelles HTTP(S) payantes |
https:// | Aucun | Le proxy | Fournisseurs qui terminent le TLS sur le port du proxy |
socks5:// | requests[socks] | Votre machine | Proxies SOCKS où le DNS local est acceptable |
socks5h:// | requests[socks] | Le proxy | Proxies SOCKS, les lookups DNS restent côté proxy |
Incertain de ce que parle un point d'accès ? Passez-le dans le vérificateur de proxies.
06Délai (timeout) proxy Python requests
Requests n'a pas de délai par défaut, donc un proxy coincé peut bloquer un appel pendant des minutes. Passez toujours timeout, idéalement comme un tuple (connect, read) :
r = requests.get(url, proxies=proxies, timeout=(3.05, 27))
La valeur connect couvre la connexion TCP, ce qui avec un proxy veut dire la connexion vers le proxy. La documentation suggère une valeur légèrement au-dessus d'un multiple de 3 pour s'aligner sur la retransmission TCP. La valeur read est l'écart maximal entre octets du serveur, pas un plafond sur le temps total de téléchargement. La latence dépend du fournisseur, de la sortie et de la cible, donc réglez la valeur read d'après les temps de réponse mesurés sur votre vrai point d'accès.
Un piège utile : quand le proxy lui-même n'accepte pas la connexion TCP à temps, Requests 2.34.2 a levé ProxyError en encapsulant le ConnectTimeoutError d'urllib3, pas un ConnectTimeout de premier niveau. Dans le montage, timeout=(1.0, 5) contre 192.0.2.1:8080 a produit ce wrapper. Après qu'un proxy fonctionnel se soit connecté, une origine lente avec timeout=(3.05, 1.0) a levé ReadTimeout. Attrapez ProxyError et ReadTimeout avant un ConnectionError générique, et inspectez le message ou __cause__ si vous devez distinguer « le proxy n'a jamais répondu » de « le proxy a répondu, puis le site s'est tu ».
Pour les échecs transitoires, montez un adaptateur avec des nouvelles tentatives bornées plutôt que d'écrire une boucle à la main :
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))
Les nouvelles tentatives de statut suivent allowed_methods. L'ensemble par défaut d'urllib3 2.8.0 est GET, HEAD, PUT, DELETE, OPTIONS et TRACE ; POST est exclu sauf si vous l'ajoutez. N'activez la relecture que pour les opérations sûres à envoyer deux fois. total=3 plafonne les tentatives, pas le temps d'horloge. Quand la réponse a Retry-After, urllib3 attend ce délai si respect_retry_after_header est vrai (le défaut).
Les nouvelles tentatives ne corrigeront pas un 407, une mauvaise AC, ou un trust_env qui pointe vers la mauvaise passerelle. Elles n'aident que lorsque l'échec est réellement transitoire.
07Erreurs proxy Python requests : ce que chaque exception veut dire
Ordonnez vos blocs except avec soin. ProxyError, SSLError et ConnectTimeout sous-classent tous ConnectionError, et ConnectTimeout est aussi un Timeout. Attrapez d'abord les plus spécifiques :
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
| Symptôme | Cause probable | Quoi essayer |
|---|---|---|
ProxyError sans 407 dans le message | Rien n'accepte de connexions sur cet hôte/port, proxy down, un blocage réseau/liste d'autorisation, ou un délai de connexion vers le proxy | Vérifiez l'hôte et le port, puis testez le point d'accès dans le vérificateur de proxies ; les identifiants ne sont pas le premier suspect |
ProxyError mentionnant 407 sur une URL HTTPS | Le CONNECT n'emportait pas d'identifiants acceptés par le proxy : aucun envoyé, posés seulement via HTTPProxyAuth, mal encodés, mauvais format, ou forfait inactif | Mettez des identifiants encodés dans l'URL du proxy ; confirmez le format d'identifiant et le statut du forfait |
| Réponse de statut 407 sur une URL HTTP | Le proxy demande des identifiants ; sur une requête envoyée sans eux, c'est juste le défi | Ajoutez les identifiants à l'URL du proxy ; s'ils y étaient déjà, vérifiez l'encodage et le format |
| Réponse de statut 401 | La destination, pas le proxy, veut une authentification | Corrigez l'auth du site lui-même ; la partie proxy a fonctionné |
Requests ignore votre Session.proxies | Les proxies d'environnement les écrasent | Passez proxies= par appel ou posez trust_env = False |
InvalidSchema: Missing dependencies for SOCKS support | requests[socks] n'est pas installé | python -m pip install "requests[socks]" |
SSLError: CERTIFICATE_VERIFY_FAILED | Proxy interceptant ou un certificat de laboratoire/auto-signé que le bundle par défaut ne reconnaît pas | Pointez verify= ou REQUESTS_CA_BUNDLE vers le fichier d'AC de ce proxy |
| Reste bloqué indéfiniment | Aucun timeout posé | Passez toujours un tuple (connect, read) |
origin ne correspond pas à la sortie attendue | Proxy non appliqué pour ce schéma, NO_PROXY a correspondu alors que vous comptiez sur les proxies d'environnement, ou une route différente de celle supposée | Posez les deux clés http et https ; si vous utilisez les proxies d'env, vérifiez NO_PROXY ; comparez avec l'IP/plage de sortie documentée du fournisseur |
Ce qu'un test local a montré

Nous avons exécuté ces motifs contre un proxy de test loopback le 14 octobre 2026 : Python 3.14.6, Requests 2.34.2, urllib3 2.8.0, certifi 2026.07.22, PySocks 1.7.1, timeout=(3.05, 10) sauf mention, vérification TLS activée. Utilisateur factice demo ; le mot de passe contenait de la ponctuation et a été encodé en pourcentage dans les URL, puis masqué dans les journaux. Les contrôles HTTPS ont utilisé https://example.com/ plus un serveur local avec une AC de laboratoire. Les lignes ci-dessous sont des observations capturées de ce montage, pas un décompte de tests automatisés réussis. Cela montre le comportement du protocole seulement.
| Cas | Cible HTTP | Cible HTTPS |
|---|---|---|
| Pas d'identifiants proxy | Réponse 407 | ProxyError mentionnant 407 |
| Identifiants encodés en pourcentage dans l'URL du proxy | 200 | 200 (example.com et TLS de laboratoire avec verify= pointé vers l'AC du lab) |
HTTPProxyAuth seulement | 200 | ProxyError mentionnant 407 |
| Port proxy fermé | ProxyError, pas de 407 | non testé |
| Auth proxy valide, le site renvoie 401 | Réponse 401 du site | non testé |
| AC par défaut contre certificat auto-signé du lab | n/a | SSLError: CERTIFICATE_VERIFY_FAILED ; 200 après avoir fait confiance à l'AC du lab |
Session.proxies contre HTTP_PROXY | Le proxy d'environnement a reçu le GET | non testé |
proxies= sur l'appel, env défini | Le proxy de session/ouvert a reçu le GET | non testé |
trust_env = False | Session.proxies utilisé ; env ignoré | non testé |
NO_PROXY correspond à l'hôte | Direct vers l'origine ; aucun hit proxy | non testé |
proxies= explicite sur un hôte NO_PROXY | Quand même proxifié | non testé |
| Délai de connexion vers le proxy | ProxyError encapsulant ConnectTimeoutError | non testé |
| Délai de lecture à travers un proxy fonctionnel | ReadTimeout | non testé |
SOCKS5 avec identifiants encodés a renvoyé 200 pour HTTP et pour https://example.com/. Scission DNS : socks5:// a livré une adresse IPv4 au proxy ; socks5h:// a livré le nom d'hôte.
Certificats, proxies interceptants et verify
Une passerelle d'inspection TLS d'entreprise ou un proxy interceptant local re-signe le HTTPS avec sa propre autorité de certification, que Requests refuse. Faites confiance à cette AC explicitement au lieu de couper la vérification :
r = requests.get(
url,
proxies={"https": "http://127.0.0.1:8080"},
verify="/path/to/proxy-ca.pem",
timeout=10,
)
ou export REQUESTS_CA_BUNDLE=/path/to/ca.pem pour tout le processus. Convertissez d'abord un export DER en PEM. L'origine auto-signée du laboratoire a reproduit la même classe d'échec : le verify=True par défaut a levé SSLError (certificate verify failed: self-signed certificate) ; la même requête avec verify= pointé vers l'AC du lab a renvoyé 200. verify=False fait accepter à Requests n'importe quel certificat et ignorer les décalages de nom d'hôte, ce qui défait le TLS pour tout ce que ce client envoie. Ce n'est pas une correction normale pour un proxy qui « ne marche pas ».
08Proxies rotatifs en Python
Beaucoup de fournisseurs commerciaux tournent pour vous derrière un seul hôte de passerelle. Selon le tarif, la sortie peut changer par requête ou sur un intervalle de session, et un identifiant de session dans le nom d'utilisateur épingle en général une sortie un moment plutôt que d'en donner une nouvelle à chaque appel. Ces règles sont des réglages fournisseur, donc consultez leur documentation. Sticky ou rotatif couvre le langage produit. Si vous gérez vous-même une liste de points d'accès, choisissez-en un par requête et passez-le 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))
Passez le proxy par appel, puisque les proxies d'environnement peuvent écraser Session.proxies. Le type de sortie est un choix séparé : datacenter, résidentiel ou mobile. Le prix et la façon dont un site cible traite le trafic dépendent du tarif, de la cible et de la réputation de l'IP, pas du libellé de type seul. Un proxy n'accorde pas à lui seul une géographie ou une éligibilité de compte.
Avant d'acheter, confirmez que le forfait choisi prend en charge le protocole (HTTP ou SOCKS5) et la méthode d'auth (login ou liste d'autorisation d'IP) dont votre code a besoin. L'automatisation de navigateur est une autre pile ; pour les contextes Playwright, voir Proxy Playwright. Fournisseurs de notre catalogue avec des passerelles proxy :

Bright Data est un grand réseau de proxies pour la collecte de données : IP résidentielles, mobiles, ISP et datacenter dans environ 195 pays. Le trafic résidentiel peut se payer à l'usage sans abonnement, et les adresses datacenter et ISP se vendent par packs dès 10 IP. L'accès résidentiel et mobile exige une vérification d'identité.

Oxylabs vend des proxies résidentiels, mobiles, ISP et datacenter sous un seul abonnement mensuel dès $30. Paiements acheteur : cartes, virement, AliPay, PayPal.

SOAX vend des proxies résidentiels et mobiles rotatifs sur un pool de crédits partagé, avec datacenter et ISP dans les mêmes forfaits. Builder commence à $200/mois + TVA (200 crédits) ; le trafic Tier 1 est à $3/GB sur Builder.

Decodo (anciennement Smartproxy) vend des proxies résidentiels et datacenter. Résidentiel : $4/GB sans forfait mensuel, ou packs mensuels dès $3.75/GB pour 3 GB. Paiements : cartes, PayPal, Alipay, Google/Apple Pay, crypto (avec limites).
Comment utiliser un proxy avec Python requests ?
Comment ajouter un identifiant et un mot de passe à un proxy requests ?
Pourquoi Python requests renvoie-t-il 407 Proxy Authentication Required ?
Requests prend-il en charge les proxies SOCKS5 ?
Requests utilise-t-il la variable d'environnement HTTP_PROXY ?
Quel timeout dois-je poser pour requests à travers un proxy ?
Faut-il poser verify=False si le proxy casse le HTTPS ?
Articles associés
Qu'est-ce qu'un proxy résidentiel et comment ça marche
Les proxies résidentiels sortent par des adresses d'opérateur grand public. Pools, sessions sticky, ce que le site voit, et quoi demander au fournisseur avant d'acheter.
4 min de lectureMeilleurs proxies pour le web scraping en 2026 : une liste de contrôle d'achat
Un achat de scraping en 2026 commence par la cible, pas par un fournisseur. Utilisez cette liste pour l'origine, la session, la géo, la facturation, et un test en direct avant de monter.
5 min de lectureProxies sticky ou rotatifs : le contrôle de session expliqué
Proxies sticky ou rotatifs : rotation à chaque requête ou sur minuterie, quand une session sticky bat une nouvelle IP, comment les cookies réagissent à un changement d'IP, et comment tester un identifiant de session.
8 min de lecture

