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 voyezCela signifieCorrectif
ProxyError … 407 Proxy Authentication RequiredIdentifiants incorrects, ou votre IP n'est pas sur liste blancheVérifiez user/pass (longues chaînes de rotation !), encodez les caractères spéciaux, vérifiez la liste blanche
InvalidSchema: Missing dependencies for SOCKS supportrequests sans l'extra sockspip 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 / ConnectTimeoutMauvais hôte/port, proxy hors ligne, ou pare-feuVérifiez avec curl -x ; essayez l'autre protocole
IP réelle renvoyée par un endpoint d'écho IPProxy 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 proxyLe proxy intercepte TLS (MITM) ou mauvais schémaUtilisez le schéma http:// pour le proxy ; demandez au fournisseur s'il termine TLS
403/CAPTCHA sur des sites protégés, IP semble correcteL'empreinte TLS/HTTP identifie Pythoncurl_cffi avec impersonate, ou un vrai navigateur
Fonctionne un moment, puis 429Limite de débit par IP atteinteFaites 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.