← Retour au blog

Proxies asynchrones dans httpx pour Python : rotation, HTTP/2 et 5 erreurs courantes des débutants

Nous expliquons comment configurer correctement la rotation des proxies dans httpx, activer HTTP/2 et éviter les pièges rencontrés par 90 % des développeurs de parseurs débutants.

📅17 octobre 2026

La bibliothèque httpx est devenue la norme pour les requêtes HTTP asynchrones en Python, mais lorsqu'il s'agit de travailler avec des proxies, elle présente des nuances qui peuvent casser les parseurs en production. Voyons comment configurer la rotation asynchrone des proxies, activer HTTP/2 et pourquoi votre script échoue tous les 50-100 requêtes.

Pourquoi httpx, et pas requests ou aiohttp

requests est synchrone et ne supporte pas HTTP/2 par défaut. aiohttp est asynchrone, mais la configuration des proxies y est moins conviviale, et le support de HTTP/2 est totalement absent. httpx répond à ces deux questions : un client synchrone et asynchrone avec une API unique, un support natif de HTTP/2 via l'extension h2, et une gestion flexible des proxies au niveau du transport.

Pour les tâches de parsing sur les marketplaces (Wildberries, Ozon, Avito), la collecte de données via les API des réseaux sociaux ou la surveillance des prix des concurrents, l'asynchronisme est crucial — il offre un gain de vitesse exponentiel par rapport aux requêtes séquentielles. Mais sans une rotation adéquate des proxies et une gestion des connexions, l'asynchronisme devient une source de nouveaux problèmes : épuisement du pool de connexions, coroutines bloquées, fuites de descripteurs de fichiers.

Un autre point important — HTTP/2 peut multiplexe plusieurs requêtes dans une seule connexion TCP, ce qui réduit la charge sur le serveur cible et aide à contourner certains systèmes anti-bot naïfs qui surveillent le nombre de handshakes TCP depuis une seule IP.

Configuration de base des proxies dans httpx

Depuis la version httpx 0.26+, la syntaxe de configuration des proxies a changé — au lieu d'un dictionnaire proxies, on utilise le paramètre proxy. C'est une source fréquente de confusion si vous consultez de vieux tutoriels.

import httpx
import asyncio

PROXY_URL = "http://user:[email protected]:8000"

async def fetch(url: str):
    async with httpx.AsyncClient(proxy=PROXY_URL, timeout=15.0) as client:
        response = await client.get(url)
        return response.status_code, response.text[:200]

asyncio.run(fetch("https://httpbin.org/ip"))

Si vous devez séparer les proxies pour le trafic HTTP et HTTPS ou configurer différents proxies pour différents domaines, utilisez httpx.Client(mounts=...) — les transports sont montés selon le modèle d'URL :

mounts = {
    "http://": httpx.AsyncHTTPTransport(proxy="http://user:[email protected]:8000"),
    "https://": httpx.AsyncHTTPTransport(proxy="http://user:[email protected]:8000"),
}

async with httpx.AsyncClient(mounts=mounts) as client:
    response = await client.get("https://example.com")

Pour l'authentification par nom d'utilisateur et mot de passe, le format http://user:pass@host:port fonctionne de manière fiable pour les proxies HTTP. Pour SOCKS5, vous devrez installer httpx[socks], sinon vous obtiendrez une ImportError à l'exécution, et non lors de l'écriture du code — c'est un piège dans lequel beaucoup tombent.

Rotation asynchrone des proxies : code fonctionnel

La principale erreur dans la rotation des proxies est de créer un nouveau AsyncClient pour chaque requête. Cela tue la performance, car le client maintient un pool de connexions, et sa recréation annule tous les avantages des connexions keep-alive. L'approche correcte consiste à créer un pool de clients à l'avance, un par proxy, et à répartir les tâches entre eux.

import httpx
import asyncio
import itertools
import random

PROXIES = [
    "http://user:[email protected]:8000",
    "http://user:[email protected]:8000",
    "http://user:[email protected]:8000",
]

class ProxyPool:
    def __init__(self, proxies: list[str]):
        self.clients = [
            httpx.AsyncClient(proxy=p, timeout=httpx.Timeout(15.0, connect=5.0))
            for p in proxies
        ]
        self._cycle = itertools.cycle(self.clients)

    def get_client(self) -> httpx.AsyncClient:
        return next(self._cycle)  # round-robin
        # pour un choix aléatoire : random.choice(self.clients)

    async def close_all(self):
        for client in self.clients:
            await client.aclose()

async def fetch_with_retry(pool: ProxyPool, url: str, retries: int = 3):
    for attempt in range(retries):
        client = pool.get_client()
        try:
            response = await client.get(url)
            if response.status_code == 200:
                return response.text
        except (httpx.ConnectTimeout, httpx.ProxyError, httpx.ReadTimeout):
            await asyncio.sleep(0.5 * (attempt + 1))
            continue
    return None

async def main():
    pool = ProxyPool(PROXIES)
    urls = [f"https://example.com/page/{i}" for i in range(100)]

    sem = asyncio.Semaphore(20)  # limiter le parallélisme

    async def bound_fetch(url):
        async with sem:
            return await fetch_with_retry(pool, url)

    results = await asyncio.gather(*(bound_fetch(u) for u in urls))
    await pool.close_all()
    return results

asyncio.run(main())

Ce modèle résout trois problèmes à la fois : il réutilise les connexions TCP via keep-alive, répartit la charge entre les proxies de manière round-robin et limite le parallélisme via un sémaphore, afin de ne pas surcharger le serveur cible et de ne pas provoquer de bannissement en raison de la limite de taux.

Pour les parseurs en production qui travaillent avec Wildberries, Ozon ou l'API Instagram, il est important que le pool de proxies soit suffisamment grand et diversifié géographiquement — sinon, la rotation au sein d'un même sous-réseau ne sauvera pas de la blocage. Les proxies résidentiels sont bien adaptés ici, car ils possèdent des IP de véritables utilisateurs et sont moins souvent filtrés par les systèmes anti-bot qui analysent l'ASN et la réputation de la plage d'adresses.

HTTP/2 dans httpx : pourquoi et comment l'activer

Par défaut, httpx utilise HTTP/1.1. Pour activer HTTP/2, vous devez installer l'extension et transmettre explicitement le drapeau :

# pip install httpx[http2]

async with httpx.AsyncClient(http2=True, proxy=PROXY_URL) as client:
    response = await client.get("https://example.com")
    print(response.http_version)  # nous attendons "HTTP/2"

Un point important : tous les proxies ne supportent pas correctement le tunneling HTTP/2. Certains proxies de centres de données bon marché fonctionnent uniquement comme des proxies HTTP/1.1 même si le serveur final supporte HTTP/2 — dans ce cas, httpx reviendra silencieusement à HTTP/1.1 via un tunnel CONNECT, et vous ne le remarquerez pas toujours sans vérification explicite de response.http_version.

HTTP/2 offre un réel avantage dans trois scénarios : lorsque vous effectuez de nombreuses requêtes vers un même domaine (le multiplexage dans une seule connexion réduit les latences), lorsque réduire la visibilité des requêtes pour la protection anti-bot est important (moins de handshakes TCP depuis une seule IP semble plus naturel), et lorsque le site cible exige lui-même HTTP/2 et renvoie des erreurs ou un contenu limité aux clients sur HTTP/1.1 — cela se rencontre parfois sur des sites utilisant Cloudflare.

Pour les tâches où la vitesse et la stabilité des connexions HTTP/2 via des proxies sont importantes, les proxies de centres de données dédiés sont mieux adaptés — ils sont plus rapides que les proxies résidentiels et prennent généralement en charge correctement le tunneling des protocoles modernes.

5 erreurs typiques avec les proxies dans httpx

1. Recréer le client pour chaque requête

Un code de type async with httpx.AsyncClient(...) as client: await client.get(url) à l'intérieur d'une boucle est un anti-modèle. Chaque création de client entraîne une nouvelle connexion TCP, un nouveau handshake TLS, et une ignorance totale du pooling de connexions. Pour 1000 requêtes, la différence de vitesse entre l'approche correcte et incorrecte peut atteindre 5 à 10 fois.

2. Absence de délais d'attente ou délais d'attente par défaut trop longs

Par défaut, httpx fixe un délai d'attente de 5 secondes pour tout — connexion, lecture, écriture, pool. Lorsqu'on travaille avec des proxies instables, cela est insuffisant pour la connexion, mais trop long pour la lecture, si le proxy est simplement "bloqué". Séparez explicitement les délais d'attente :

timeout = httpx.Timeout(
    connect=5.0,
    read=10.0,
    write=10.0,
    pool=5.0
)
client = httpx.AsyncClient(proxy=PROXY_URL, timeout=timeout)

3. Ignorer les limites du pool de connexions

Lorsqu'il y a un grand nombre de tâches parallèles (gather sur 500+ coroutines sans sémaphore), httpx commence à lancer PoolTimeout, car la limite de connexions dans le pool (par défaut 100) est atteinte. Configurez les limites explicitement via httpx.Limits(max_connections=200, max_keepalive_connections=50) et utilisez toujours un sémaphore pour contrôler le parallélisme.

4. Désactiver la vérification sans comprendre les conséquences

Beaucoup copient verify=False depuis Stack Overflow pour se débarrasser des erreurs SSL lors de l'utilisation de proxies avec des certificats MITM. Cela ouvre la porte aux attaques de type "homme du milieu" et peut entraîner des fuites de données via un proxy compromis. Si le proxy nécessite un certificat spécial, ajoutez-le explicitement via verify="/path/to/cert.pem", au lieu de désactiver la vérification complètement.

5. Clients non fermés et fuites de descripteurs de fichiers

Si vous créez un pool de clients manuellement (comme dans l'exemple ci-dessus), n'oubliez pas d'appeler await client.aclose() pour chacun d'eux à la fin. Les scripts de longue durée sans fermeture explicite des connexions atteignent la limite des descripteurs de fichiers ouverts du système d'exploitation (généralement 1024 par défaut sur Linux), et le parseur échoue avec une erreur mystérieuse Too many open files après quelques heures de fonctionnement.

Quel type de proxy choisir pour le parsing

Le choix du proxy influence directement le nombre d'erreurs de la liste ci-dessus que vous rencontrerez — des proxies instables provoquent des délais d'attente et des ruptures de connexion, même si le code est écrit de manière parfaite.

Type de proxy Vitesse Risque de blocage Quand utiliser
Centre de données Élevée Élevé sur les sites sécurisés API ouvertes, protection anti-bot faible
Résidentiels Moyenne Faible Wildberries, Ozon, réseaux sociaux
Mobiles Moyenne-faible Minime Instagram, TikTok API, protection stricte

Pour le parsing asynchrone avec une vitesse élevée des requêtes vers l'API avec une protection modérée, les proxies de centres de données restent une option économiquement avantageuse — l'essentiel est de répartir uniformément la charge sur le pool, comme montré dans le code ci-dessus.

Checklist avant la mise en production

  • Les clients httpx sont créés une fois et réutilisés, et non pour chaque requête
  • Les délais d'attente sont séparés en connect/read/write/pool, et non le défaut
  • Le parallélisme est limité via asyncio.Semaphore
  • Les limites du pool de connexions sont configurées selon la charge réelle
  • Il existe une logique de réessai avec gestion des ProxyError, ConnectTimeout, ReadTimeout
  • Tous les clients sont explicitement fermés via aclose() à la fin
  • verify=False n'est pas utilisé sans nécessité absolue
  • HTTP/2 est activé de manière consciente et vérifié via response.http_version

Conclusion

httpx offre un puissant outil pour le travail asynchrone avec des proxies et HTTP/2, mais nécessite un réglage minutieux du pool de clients, des délais d'attente et des limites de connexions. La plupart des pannes des parseurs en production sont liées non pas à la bibliothèque, mais à des erreurs architecturales : recréation des clients, absence de limitation du parallélisme et mauvaise gestion des exceptions de proxy.

Si vous construisez un parseur pour des marketplaces ou des API de réseaux sociaux qui surveillent activement les activités suspectes, la stabilité de l'infrastructure proxy est tout aussi importante que le code. Pour de telles tâches, il vaut la peine de considérer les proxies résidentiels — elles réduisent le nombre d'erreurs ProxyError et ConnectTimeout en raison de la réputation plus élevée des adresses IP par rapport aux pools de centres de données bon marché.