Chaque bibliothèque HTTP Python prend un proxy sous forme d'URL de type scheme://user:pass@host:port ; ce qui diffère, c'est le nom du paramètre, qu'il soit défini par client ou par requête, et la gestion de SOCKS5 et du DNS. Les extraits ci-dessous sont le code exact qui fonctionne avec les versions actuelles de requests, httpx et aiohttp — chacun a été exécuté via un proxy avec authentification avant la publication de cet article — suivis des trois choses qui expliquent la plupart des questions « mon proxy ne fonctionne pas en Python » : noms de paramètres incorrects copiés d'anciens tutoriels, extras SOCKS manquants, et une empreinte TLS qui trahit le script quel que soit l'IP.
L'URL du proxy
http://user:pass@proxy.example.com:8080 # proxy HTTP, utilisé pour les cibles http:// et https://
socks5://user:pass@proxy.example.com:1080 # SOCKS5, DNS résolu localement
socks5h://user:pass@proxy.example.com:1080 # SOCKS5, DNS résolu par le proxy (à utiliser)
Deux points qui posent problème. Premièrement, un proxy HTTP gère aussi les cibles HTTPS — le client envoie CONNECT et tunnelise TLS à travers lui — donc vous n'avez presque jamais besoin d'un schéma de proxy https:// ; cela signifierait TLS vers le proxy lui-même, ce que peu de fournisseurs offrent. Deuxièmement, si votre nom d'utilisateur contient des paramètres de rotation (user-country-de-session-abc123) ou si votre mot de passe contient @, : ou /, encodez-les en pourcentage (urllib.parse.quote(pw, safe='')) sinon l'URL sera mal analysée.
requests
import requests
PROXY = "http://user:pass@proxy.example.com:8080"
proxies = {"http": PROXY, "https": PROXY}
# par requête
r = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=20)
print(r.json())
# par session (réutilise les connexions ; la méthode normale)
s = requests.Session()
s.proxies.update(proxies)
r = s.get("https://api.ipify.org?format=json", timeout=20)
Le dictionnaire est indexé par le schéma de la cible : les requêtes vers des URL http:// utilisent l'entrée "http", celles vers https:// utilisent "https". Définissez les deux sur le même proxy sauf raison contraire. Toujours passer un timeout ; un proxy bloqué gèlerait sinon votre script indéfiniment.
SOCKS5 nécessite l'extra : pip install "requests[socks]". Ensuite proxies = {"http": "socks5h://…", "https": "socks5h://…"}. Sans l'extra, vous obtenez InvalidSchema: Missing dependencies for SOCKS support.
Variables d'environnement : requests honore HTTP_PROXY / HTTPS_PROXY / NO_PROXY par défaut (trust_env=True). Si un proxy est mystérieusement utilisé alors que vous n'en avez pas défini, ou pas utilisé alors que vous en avez défini un, vérifiez l'environnement.
httpx
import httpx
PROXY = "http://user:pass@proxy.example.com:8080"
# synchrone
with httpx.Client(proxy=PROXY, timeout=20) as client:
r = client.get("https://api.ipify.org?format=json")
print(r.json())
# asynchrone
import asyncio
async def main():
async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
r = await client.get("https://api.ipify.org?format=json")
print(r.json())
asyncio.run(main())
Le paramètre est proxy= (au singulier). Les anciens tutoriels montrent proxies={"http://": …, "https://": …} ; ce mot-clé a été déprécié dans httpx 0.26 et supprimé dans 0.28, et le passer maintenant lève TypeError: Client.__init__() got an unexpected keyword argument 'proxies'. Si vous avez besoin de proxies différents pour différentes cibles, utilisez mounts :
mounts = {
"https://api.example.com": httpx.HTTPTransport(proxy="http://user:pass@dc-proxy:8080"),
"all://": httpx.HTTPTransport(proxy="http://user:pass@resi-proxy:8080"),
}
client = httpx.Client(mounts=mounts)
SOCKS5 : pip install "httpx[socks]", puis proxy="socks5://…". httpx résout les noms d'hôte via le proxy SOCKS par défaut. HTTP/2 : pip install "httpx[http2]" et httpx.Client(http2=True, …) — pertinent car un navigateur via proxy est de toute façon en HTTP/2 (voir HTTP/2 et HTTP/3 via des proxies).
aiohttp
import aiohttp, asyncio
PROXY = "http://user:pass@proxy.example.com:8080"
async def main():
async with aiohttp.ClientSession() as session:
async with session.get("https://api.ipify.org?format=json",
proxy=PROXY,
timeout=aiohttp.ClientTimeout(total=20)) as r:
print(await r.json())
asyncio.run(main())
Le proxy est passé par requête (ou défini une fois via un connecteur personnalisé). Les identifiants vont dans l'URL ; la forme séparée proxy_auth=aiohttp.BasicAuth(...) fonctionne encore en 3.x mais est dépréciée depuis 3.14 et prévue pour suppression en 4.0, donc préférez la forme URL. aiohttp ne lit pas les variables d'environnement HTTP_PROXY sauf si vous créez la session avec trust_env=True. aiohttp natif ne parle que les proxies HTTP ; pour SOCKS, utilisez le paquet aiohttp-socks et son ProxyConnector.
Rotation de proxies : le modèle d'ID de session
Avec une passerelle de backconnect (voir comment fonctionnent les passerelles de rotation), vous faites rarement la rotation en Python du tout — vous faites la rotation en changeant le nom d'utilisateur. Un nouvel ID de session signifie une nouvelle IP de sortie ; un ID réutilisé la conserve :
import secrets, requests
def proxy_for(session_id: str, country: str = "de") -> dict:
user = f"customer-country-{country}-session-{session_id}" # format spécifique au fournisseur
url = f"http://{user}:PASSWORD@gate.provider.com:7777"
return {"http": url, "https": url}
# sticky : même IP pour un flux multi-étapes
sid = secrets.token_hex(4)
s = requests.Session(); s.proxies.update(proxy_for(sid))
s.get("https://shop.example/login"); s.post("https://shop.example/cart", data=...)
# rotation : nouvelle IP par élément
for url in urls:
requests.get(url, proxies=proxy_for(secrets.token_hex(4)), timeout=20)
Faites aussi la rotation en cas d'échec : interceptez 403/429 et un marqueur CAPTCHA, et réessayez avec un nouvel ID de session et un nouveau cookie jar — la même IP avec les mêmes cookies est le même visiteur.
L'erreur qu'aucun proxy ne corrige : l'empreinte TLS
Les trois bibliothèques font des connexions TLS via le module ssl de Python (OpenSSL). Les systèmes anti-bot identifient cette poignée de main et savent que ce n'est pas un navigateur, quelle que soit la façon dont vous définissez le User-Agent. Derrière une IP résidentielle parfaite, le script est toujours identifié comme un script — le mécanisme est expliqué dans l'empreinte TLS expliquée. Pour les cibles protégées, passez à un client d'usurpation ; curl_cffi conserve une API proche de requests :
# pip install curl_cffi
from curl_cffi import requests as cffi_requests
r = cffi_requests.get("https://protected.example/",
impersonate="chrome", # reproduit l'empreinte TLS + HTTP/2 de Chrome
proxies={"https": "http://user:pass@proxy.example.com:8080"},
timeout=20)
Ensuite, gardez le reste de l'histoire cohérent — un User-Agent Chrome, des indices client sec-ch-ua correspondants, un Accept-Language plausible pour le pays du proxy. Pour les sites qui exigent en plus des défis JavaScript, aucun client HTTP ne suffit ; utilisez un vrai navigateur comme dans Proxies dans Playwright, Puppeteer et Selenium.
Erreurs et leur signification
| Vous voyez | Cela signifie | Correctif |
|---|---|---|
ProxyError … 407 Proxy Authentication Required | Identifiants incorrects, ou votre IP n'est pas sur liste blanche | Vérifiez user/pass (longues chaînes de rotation !), encodez les caractères spéciaux, vérifiez la liste blanche |
InvalidSchema: Missing dependencies for SOCKS support | requests sans l'extra socks | pip install "requests[socks]" |
TypeError: … unexpected keyword argument 'proxies' | httpx ≥ 0.28 avec le paramètre supprimé | Utilisez proxy= ou mounts= |
ProxyError … Cannot connect to proxy / ConnectTimeout | Mauvais hôte/port, proxy hors ligne, ou pare-feu | Vérifiez avec curl -x ; essayez l'autre protocole |
| IP réelle renvoyée par un endpoint d'écho IP | Proxy non appliqué (mauvaise clé de dictionnaire, variable d'environnement, paramètre par requête manquant dans aiohttp) | Affichez session.proxies ; vérifiez trust_env ; passez proxy= sur la requête |
SSLError uniquement via le proxy | Le proxy intercepte TLS (MITM) ou mauvais schéma | Utilisez le schéma http:// pour le proxy ; demandez au fournisseur s'il termine TLS |
| 403/CAPTCHA sur des sites protégés, IP semble correcte | L'empreinte TLS/HTTP identifie Python | curl_cffi avec impersonate, ou un vrai navigateur |
| Fonctionne un moment, puis 429 | Limite de débit par IP atteinte | Faites tourner l'ID de session ; ralentissez ; voir le guide de rotation |
Un modèle minimal et correct
import requests, secrets
from urllib.parse import quote
GATE, PORT = "gate.provider.com", 7777
USER_BASE, PASS = "customer-country-de", quote("p@ss:word", safe="")
def make_session() -> requests.Session:
sid = secrets.token_hex(4)
url = f"http://{USER_BASE}-session-{sid}:{PASS}@{GATE}:{PORT}"
s = requests.Session()
s.proxies.update({"http": url, "https": url})
s.headers.update({"User-Agent": "python-requests"}) # UA honnête pour les cibles tolérantes
return s
s = make_session()
r = s.get("https://api.ipify.org?format=json", timeout=20)
r.raise_for_status()
print(r.json())
Vérifiez que l'IP change, puis construisez dessus. Pour un pré-vol complet (fuites, en-têtes, listes noires), voir Comment tester un proxy ; pour choisir entre datacenter et résidentiel pour une cible donnée, la comparaison des types de proxies.
Questions fréquemment posées
Comment utiliser un proxy avec Python requests ?
Passez proxies={"http": url, "https": url} à la requête ou définissez-le sur une Session, où url est http://user:pass@host:port. Ajoutez timeout=. Pour SOCKS5, installez requests[socks] et utilisez socks5h://.
Pourquoi httpx dit « unexpected keyword argument proxies » ?
Parce que proxies= a été supprimé dans httpx 0.28. Utilisez proxy="http://…" pour un seul proxy, ou mounts= pour des proxies par cible.
Comment définir un proxy dans aiohttp ?
Passez proxy="http://user:pass@host:port" à chaque requête (session.get(url, proxy=…)). Le paramètre proxy_auth= est déprécié ; mettez les identifiants dans l'URL. Utilisez aiohttp-socks pour SOCKS.
Comment faire la rotation de proxies en Python ?
Avec un fournisseur de backconnect, changez l'ID de session dans le nom d'utilisateur du proxy par requête ou par flux — pas besoin de liste d'IP. Faites la rotation sur 403/429 et associez chaque nouvelle IP à un nouveau cookie jar.
Pourquoi mon script est-il bloqué alors que mon navigateur sur le même proxy ne l'est pas ?
Empreinte TLS et HTTP/2 : le module ssl de Python ne ressemble pas à un navigateur. Utilisez curl_cffi avec impersonate= ou automatisez un vrai navigateur.
Dois-je utiliser socks5 ou socks5h ?
socks5h. Le h fait résoudre les noms d'hôte par le proxy, évitant les fuites DNS et permettant d'atteindre des hôtes que seul le proxy peut résoudre.