Geonode logo
Geonode Team

Geonode Team

Mis à jour : 16 octobre 2026

Publié : 16 octobre 2026

Proxy HTTPX en Python : configuration, authentification et requêtes asynchrones

httpx est le client HTTP vers lequel se tournent la plupart des nouveaux projets Python lorsqu'ils dépassent les capacités de `requests`, principalement parce qu'il propose une API unique pour le code synchrone et asynchrone. Ce guide explique comment intégrer un proxy httpx à la fois dans `Client` et `AsyncClient`, s’authentifier auprès d’un point de terminaison résidentiel à rotation, établir une session persistante, ajouter SOCKS5, régler les délais d’expiration et les tentatives de reconnexion, et diagnostiquer les trois erreurs que vous rencontrerez concrètement. À la fin, vous disposerez d’un client réutilisable qui achemine le trafic via un proxy et peut prouver son adresse IP de sortie. Les exemples concernent httpx 0.27 ou une version plus récente. Si vous utilisez une version plus ancienne, notez que le mot-clé `proxies=` a été renommé en `proxy=` et `mounts=` dans httpx 0.26 ; tout ce qui suit utilise les noms actuels.

httpx est le client HTTP vers lequel se tournent la plupart des nouveaux projets Python lorsqu'ils dépassent les capacités de requests, principalement parce qu'il propose une API unique pour le code synchrone et asynchrone. Ce guide explique comment intégrer un proxy httpx à la fois dans Client et AsyncClient, s’authentifier auprès d’un point de terminaison résidentiel à rotation, établir une session persistante, ajouter SOCKS5, régler les délais d’expiration et les tentatives de reconnexion, et diagnostiquer les trois erreurs que vous rencontrerez concrètement. À la fin, vous disposerez d’un client réutilisable qui achemine le trafic via un proxy et peut prouver son adresse IP de sortie.

Les exemples concernent httpx 0.27 ou une version plus récente. Si vous utilisez une version plus ancienne, notez que le mot-clé proxies= a été renommé en proxy= et mounts= dans httpx 0.26 ; tout ce qui suit utilise les noms actuels.

Deux façons de configurer un proxy httpx : proxy et mounts

httpx propose un commutateur simple et un autre plus précis. proxy= prend une seule URL et achemine toutes les requêtes par son intermédiaire. mounts= associe des modèles d’URL à des transports, et chaque transport peut disposer de son propre proxy, de son nombre de tentatives et de ses limites de connexion.

OptionPortéeQuand l’utiliser
proxy="http://..."Client entier, tous les schémasUn seul proxy pour tout, ce qui est généralement le cas avec une passerelle à rotation
mounts={"https://": HTTPTransport(proxy=...)}Par modèle d’URLDifférents proxys par schéma ou hôte, ou contournement du proxy pour les services internes
httpx.get(url, proxy=...)Appel unique de niveau supérieurVérifications rapides et scripts sans client de longue durée

La forme simple se présente ainsi :

import httpx

PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"

with httpx.Client(proxy=PROXY, timeout=20.0) as client:
    r = client.get("https://api.ipify.org?format=json")
    print(r.status_code, r.json())

Le port 9000 correspond à la passerelle rotative HTTP(S) ; ainsi, chaque nouvelle connexion se voit attribuer une adresse IP résidentielle différente. Les identifiants sont intégrés à l’URL sous forme d’authentification Basic, et httpx les transforme en en-tête Proxy-Authorization sans code supplémentaire.

Le piège du routage par schéma

Avec mounts, vous pouvez attribuer des transports distincts aux cibles http:// et https://. Le piège réside dans le schéma de l’URL du proxy elle-même. Pour une cible https://, httpx envoie CONNECT au proxy, puis négocie le protocole TLS avec le serveur d’origine à l’intérieur de ce tunnel. Le saut vers le proxy s’effectue en HTTP simple ; par conséquent, l’URL du proxy sur le montage https:// devrait normalement commencer par http://, et non par https://. En y écrivant « https:// », on demande à httpx d’envelopper le saut vers le proxy lui-même dans un tunnel TLS, ce que la plupart des passerelles ne prennent pas en charge, ce qui entraîne un échec de la négociation qui semble n’avoir aucun rapport avec les proxys.

import httpx

PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"

mounts = {
    "http://": httpx.HTTPTransport(proxy=PROXY),
    "https://": httpx.HTTPTransport(proxy=PROXY),  # http:// scheme is correct here
    # internal services stay direct: mount a transport without a proxy
    "all://*.internal.example": httpx.HTTPTransport(),
}

with httpx.Client(mounts=mounts) as client:
    print(client.get("https://api.ipify.org?format=json").json())

Les règles plus spécifiques l’emportent sur les règles générales ; ainsi, le montage all://*.internal.example contourne le proxy pour ce domaine, tandis que tout le reste est acheminé via celui-ci.

Authentification dans l'URL

Les identifiants Geonode correspondent à un nom d'utilisateur et un mot de passe en clair ; le formulaire userinfo http://user:pass@host:port est donc tout ce dont vous avez besoin. Deux remarques pratiques. Premièrement, si le mot de passe contient des caractères tels que @, : ou /, effectuez un encodage en pourcentage avec urllib.parse.quote avant de construire l'URL. Deuxièmement, évitez de placer les informations confidentielles dans le contrôle de version en les lisant à partir de l’environnement :

import os
from urllib.parse import quote

import httpx

user = os.environ["GEONODE_USER"]
password = quote(os.environ["GEONODE_PASS"], safe="")
proxy = f"http://{user}:{password}@premium-residential.geonode.com:9000"

client = httpx.Client(proxy=proxy)

Tout ce qui suit ajoute des options de ciblage et de session en étendant le nom d’utilisateur, et non l’URL, de sorte que le même modèle continue de fonctionner.

Synchronisation entre Client et AsyncClient

Le client asynchrone utilise le même mot-clé proxy=. La seule différence apparaît lorsque vous passez à mounts : AsyncClient nécessite httpx.AsyncHTTPTransport, et non httpx.HTTPTransport. Le fait de transmettre le transport synchrone à un client asynchrone génère une erreur dès la première requête.

import asyncio
import httpx

PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"

async def fetch(client: httpx.AsyncClient, url: str) -> str:
    r = await client.get(url)
    r.raise_for_status()
    return r.json()["ip"]

async def main() -> None:
    transport = httpx.AsyncHTTPTransport(proxy=PROXY, retries=2)
    async with httpx.AsyncClient(
        mounts={"all://": transport}, timeout=httpx.Timeout(10.0, connect=5.0)
    ) as client:
        urls = ["https://api.ipify.org?format=json"] * 5
        ips = await asyncio.gather(*(fetch(client, u) for u in urls))
        print(ips)

asyncio.run(main())

Le port 9000 change à chaque connexion ; par conséquent, les cinq requêtes simultanées affichent généralement plusieurs adresses IP différentes. Il s'agit du comportement attendu pour un pool à rotation ; si vous souhaitez que ces adresses correspondent, utilisez une session « sticky » (voir ci-dessous).

SOCKS5 à httpx[socks]

La prise en charge de SOCKS dans une configuration de proxy httpx est facultative. Installez-la avec pip install "httpx[socks]", qui ajoute le paquet socksio, puis remplacez le schéma. Le port rotatif de SOCKS5 (fourni par Geonode) est le 11000 :

import httpx

PROXY = "socks5://USERNAME:PASSWORD@premium-residential.geonode.com:11000"

with httpx.Client(proxy=PROXY) as client:
    print(client.get("https://api.ipify.org?format=json").json())

Sans ce module supplémentaire installé, httpx génère un message d'erreur mentionnant socksio dès qu'il détecte une URL de proxy socks5://. Pour le scraping classique, les ports HTTP sont plus simples à déboguer car les réponses 407 sont lisibles.

Variables d'environnement et trust_env

Avec la valeur par défaut trust_env=True, httpx lit les variables HTTP_PROXY, HTTPS_PROXY, ALL_PROXY et NO_PROXY et les applique automatiquement. C’est pratique sur un poste de travail, mais cela peut être source de confusion en CI, où un proxy d’entreprise présent dans l’environnement réécrit silencieusement votre routage. Si vous configurez le proxy dans le code, pensez à désactiver la recherche dans l’environnement afin que le comportement soit identique partout :

import httpx

client = httpx.Client(
    proxy="http://USERNAME:PASSWORD@premium-residential.geonode.com:9000",
    trust_env=False,
)

Un proxy= explicite a déjà la priorité sur l’environnement ; trust_env=False empêche également httpx de lire SSL_CERT_FILE, il convient donc de le définir délibérément.

Délais d'expiration et tentatives de reconnexion au niveau du transport

Un proxy ajoute un saut, ce qui augmente le temps de connexion ; le délai d'expiration par défaut de 5 secondes s'avère donc parfois trop court pour les points de sortie résidentiels. httpx.Timeout vous permet de définir un budget total avec une limite de connexion distincte, tandis que HTTPTransport(retries=...) effectue des tentatives de reconnexion en cas d'échec avant même que la requête ne soit envoyée.

import httpx

PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"

transport = httpx.HTTPTransport(proxy=PROXY, retries=2)
timeout = httpx.Timeout(15.0, connect=8.0)

with httpx.Client(mounts={"all://": transport}, timeout=timeout) as client:
    r = client.get("https://api.ipify.org?format=json")
    print(r.json())

Les tentatives de reconnexion au niveau du transport couvrent les erreurs de type ConnectError et autres erreurs similaires survenant lors de l'établissement de la connexion. Elles ne tentent pas de nouvelles connexions en fonction des codes d’état et ne tentent pas de nouvelles connexions après l’envoi d’octets ; ainsi, un code 502 provenant de la passerelle ou un code 429 du site cible parvient toujours jusqu’à votre code. Gérez ces cas dans une petite boucle que vous créerez vous-même, dans laquelle vous pourrez également décider de modifier ou non l’identifiant de session.

Sessions persistantes avec la grammaire de nom d'utilisateur Geonode

La rotation à chaque connexion n'est pas adaptée aux flux en plusieurs étapes tels que la connexion, la pagination ou le téléchargement. Dans ces cas-là, votre proxy httpx doit conserver la même adresse IP de sortie d'une requête à l'autre. Geonode encode cette information dans le nom d'utilisateur et la transmet sur le port HTTP(S) persistant 10000 :

USER[-type-residential|datacenter|mix][-country-xx][-state-name|-city-name][-asn-N][-session-XXXXXXXX][-lifetime-SECONDS]

L'identifiant de session est composé de 8 caractères alphanumériques que vous générez ; sa durée de vie est comprise entre 180 et 86 400 secondes (24 heures). L'État et la ville ne peuvent pas être combinés, et asn nécessite l'indication d'un pays.

import secrets
import httpx

def sticky_client(country: str = "us", lifetime: int = 600) -> httpx.Client:
    session_id = secrets.token_hex(4)  # 8 hex characters
    username = f"USERNAME-country-{country}-session-{session_id}-lifetime-{lifetime}"
    proxy = f"http://{username}:PASSWORD@premium-residential.geonode.com:10000"
    return httpx.Client(proxy=proxy, timeout=httpx.Timeout(15.0, connect=8.0))

with sticky_client() as client:
    first = client.get("https://api.ipify.org?format=json").json()["ip"]
    second = client.get("https://api.ipify.org?format=json").json()["ip"]
    print(first == second, first)

Un nouveau session_id correspond à une nouvelle adresse IP ; la rotation « manuelle » consiste donc simplement à créer un nouveau client. Veillez à ce que la durée de vie des sessions stables soit aussi courte que nécessaire pour la tâche.

Vérifier l'adresse IP de sortie

Tous les exemples ci-dessus accèdent à https://api.ipify.org?format=json, qui renvoie {"ip": "..."} quelle que soit l'adresse qui l'a atteint. Comparez cela une seule fois avec un appel direct (sans proxy), et vous obtenez un test de fonctionnement en deux lignes qui prouve que le proxy se trouve bien sur le chemin. Effectuez cette vérification avant toute autre opération de débogage : de nombreux rapports indiquant que « le proxy httpx ne fonctionne pas » concernent en réalité des requêtes qui n’ont jamais utilisé le proxy, car une variable d’environnement ou un modèle de montage a pris le relais.

Gestion des erreurs 407 ProxyError et ConnectError

Trois types d'erreurs sont à l'origine de la quasi-totalité des problèmes liés au proxy httpx. Le tableau ci-dessous établit un lien entre chaque erreur, sa cause et la solution correspondante.

SymptômeExceptionCause habituelleSolution
Statut de réponse 407httpx.ProxyError (générée lors de l'établissement du tunnel pour les cibles https) ou une simple réponse 407 pour les cibles httpIdentifiants incorrects, ou option de nom d'utilisateur rejetée par la passerelleVérifiez la syntaxe du nom d'utilisateur ; encodez le mot de passe en pourcentage
Tunnel refusé, code 502/503 du proxyhttpx.ProxyErrorLa passerelle n’a pas pu ouvrir le tunnel, ou le port n’est pas le bon pour le protocoleVérifiez que le port est bien 9000/10000 pour HTTP(S), 11 000/12 000 pour SOCKS5
Impossible d’atteindre l’hôte proxyhttpx.ConnectErrorDNS, pare-feu ou nom d’hôte incorrectRésolvez l’hôte manuellement ; augmentez le délai d’expiration de connect
La requête se bloque puis échouehttpx.ConnectTimeout / httpx.ReadTimeoutSortie résidentielle lente ou cible bloquéeAugmentez le délai d'expiration, réessayez avec un nouvel identifiant de session
import httpx

PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"

def get_via_proxy(url: str) -> httpx.Response | None:
    try:
        with httpx.Client(proxy=PROXY, timeout=httpx.Timeout(15.0, connect=8.0)) as client:
            r = client.get(url)
            if r.status_code == 407:
                raise RuntimeError("proxy rejected credentials (407)")
            return r
    except httpx.ProxyError as e:
        print("proxy refused tunnel:", e)
    except httpx.ConnectError as e:
        print("cannot connect to proxy host:", e)
    except httpx.TimeoutException as e:
        print("timed out:", type(e).__name__)
    return None

resp = get_via_proxy("https://api.ipify.org?format=json")
print(resp.json() if resp else "failed")

Toutes les exceptions httpx héritent de httpx.HTTPError ; un seul code général except suffit donc pour la journalisation, mais distinguer ProxyError de ConnectError vous permet de savoir s'il faut vérifier les identifiants ou le réseau.

Erreurs courantes

Utilisation de https:// dans l'URL du proxy

Comme indiqué ci-dessus, le schéma d'URL du proxy décrit le saut vers le proxy. Utilisez http:// pour les ports HTTP(S), même lorsque la cible est https://. Réservez « https:// » aux passerelles qui annoncent explicitement le protocole TLS au proxy.

Mélange de « HTTPTransport » et de « AsyncClient »

mounts sur AsyncClient doit utiliser AsyncHTTPTransport. Le transport de synchronisation est accepté au moment de la construction mais échoue au moment de la requête avec une erreur peu explicite ; ce problème est donc facile à manquer lors de tests qui n’envoient jamais de requête.

Attente d’une seule adresse IP pour le port rotatif

Le port 9000 change à chaque connexion. Si deux requêtes consécutives doivent partager une même adresse IP, utilisez le port 10000 avec un identifiant -session- dans le nom d’utilisateur. La réutilisation d’un Client ne garantit pas à elle seule le maintien de l’adresse IP, car les connexions du pool peuvent être remplacées.

Laisser l’environnement prendre le pas sur votre code

Un identifiant HTTPS_PROXY égaré dans une image Docker ou un exécuteur CI redirigera le trafic que vous pensiez direct, et NO_PROXY peut exclure des hôtes que vous pensiez voir passer par le proxy. Définissez trust_env=False lorsque le routage par proxy doit être déterministe.

Étape suivante

Une fois que le client ci-dessus renvoie une adresse IP résidentielle et qu’une session persistante est maintenue d’une requête à l’autre, la seule variable restante est le pool qui se cache derrière. Les proxys résidentiels de Geonode exposent les points de terminaison utilisés dans ce guide : 2,5 millions d’adresses IP en ligne, 30 à 50 millions d’adresses IP uniques tournant chaque mois dans plus de 195 pays, avec un ciblage par pays, région, ville et ASN via la syntaxe du nom d’utilisateur et des sessions persistantes pouvant aller jusqu’à 24 heures. Les tarifs commencent à 0,27 $/Go pour les achats en volume, la formule d’entrée de gamme est de 10 Go pour 7,92 $/mois, et un essai de 3 jours coûte 5 $. Si vous développez un robot d’exploration à partir de cette solution, le cas d'utilisation du web scraping explique comment associer des proxys à une logique de réessai, et les proxys de centres de données rotatifs constituent l'option la plus économique pour les cibles qui ne bloquent pas les plages d'adresses des centres de données.

FAQ

Comment configurer un proxy httpx pour toutes les requêtes ?

Transmettez l'URL du proxy au constructeur du client : httpx.Client(proxy="http://user:pass@host:port"). Toutes les requêtes effectuées via ce client, qu'elles soient destinées à http:// ou à https://, sont acheminées via le proxy. Ce même mot-clé fonctionne avec httpx.AsyncClient et avec les helpers de niveau supérieur tels que httpx.get(). Les identifiants présents dans l’URL sont automatiquement envoyés via Proxy-Authorization.

Quelle est la différence entre un proxy et un « mount » dans httpx ?

proxy est un raccourci qui achemine tout via un seul proxy. mounts mappe des modèles d’URL tels que "http://" ou "https://" à des transports, dont chacun peut disposer de son propre proxy, de ses propres tentatives de reconnexion et de ses propres limites. Utilisez mounts lorsque vous avez besoin de proxys différents par schéma ou par hôte, ou lorsque vous souhaitez contourner le proxy pour certaines destinations en montant un transport sans proxy.

Pourquoi le montage de mon https:// échoue-t-il lorsque l’URL du proxy commence par https://? ?

Le schéma de l’URL du proxy décrit la manière dont httpx communique avec le proxy, et non avec la cible. Pour les cibles de type https://, httpx ouvre un tunnel CONNECT via une connexion HTTP en clair vers le proxy ; l’URL du proxy doit donc normalement utiliser http://. L'utilisation de https:// demande à httpx de négocier le protocole TLS avec le proxy lui-même, ce que la plupart des points de terminaison proxy ne prennent pas en charge.

httpx prend-il en charge les proxys SOCKS5 ?

Oui, mais uniquement après avoir installé la dépendance optionnelle avec pip install httpx[socks], qui inclut socksio. Il faut ensuite passer proxy="socks5://user:pass@host:port" exactement comme vous le feriez avec un proxy HTTP. Avec Geonode, le port rotatif SOCKS5 est 11000 et le port fixe SOCKS5 est 12000 ; les ports HTTP(S) n’acceptent pas les handshakes SOCKS.

httpx peut-il réessayer automatiquement une requête lorsque la connexion au proxy échoue ?

Uniquement au niveau du transport. httpx.HTTPTransport(retries=2) réessaie les tentatives de connexion ayant échoué, y compris les erreurs de connexion au proxy, mais il ne réessaie jamais une requête ayant déjà reçu une réponse telle que 407 ou 502. Les tentatives de réessai en fonction des codes d’état HTTP doivent être gérées dans votre propre boucle, où vous pouvez changer d’identifiant de session ou espacer délibérément les requêtes.

Y a-t-il des limites à ce qu’une configuration de proxy httpx peut faire avec des adresses IP résidentielles ?

Un proxy modifie votre adresse IP de sortie ; il ne modifie pas ce qu’un site autorise. Respectez le fichier robots.txt, les conditions d’utilisation et les limites de débit, maintenez un nombre modéré de connexions simultanées et évitez de collecter des données personnelles que vous n’avez pas le droit de traiter. Les sessions résidentielles sont également limitées à 24 heures, après quoi l’adresse IP est libérée et vous devez ouvrir une nouvelle session.