1. Développement, langage

Appels HTTP asynchrones en Python : aiohttp, HTTPX et limites de concurrence

Le cas d’usage le plus courant de l’asynchrone

Les appels HTTP sont l’un des meilleurs exemples pour comprendre l’intérêt de la programmation asynchrone en Python. Une requête réseau passe beaucoup de temps à attendre : résolution DNS, connexion TLS, réponse du serveur, téléchargement du contenu. Si vous devez interroger dix, cent ou mille URLs, l’asynchrone peut réduire fortement le temps total.

Deux bibliothèques reviennent souvent : aiohttp, très utilisée historiquement, et HTTPX, intéressante si vous connaissez déjà l’API de requests. Dans les deux cas, la règle est la même : réutiliser un client/session, définir des timeouts et limiter la concurrence.

Pour remettre ce sujet dans le parcours Python du site, vous pouvez aussi relire le langage Python, apprendre à coder en Python et les conseils pour écrire un code Python propre et lisible.

Exemple simple avec HTTPX

import asyncio
import httpx

URLS = [
    "https://example.com",
    "https://www.python.org",
]

async def fetch(client, url):
    r = await client.get(url)
    r.raise_for_status()
    return url, len(r.text)

async def main():
    timeout = httpx.Timeout(10.0, connect=3.0)
    async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:
        resultats = await asyncio.gather(*(fetch(client, url) for url in URLS))
    print(resultats)

asyncio.run(main())

Le point important est l’usage de AsyncClient dans un bloc async with. Créer un nouveau client pour chaque URL gaspille des connexions et rend le code plus lent.

Limiter les appels simultanés

Un crawler ou un script d’audit ne doit pas attaquer un serveur avec une concurrence illimitée. Il faut protéger votre machine, respecter les sites distants et éviter les erreurs 429.

sem = asyncio.Semaphore(5)

async def fetch_limited(client, url):
    async with sem:
        try:
            r = await client.get(url)
            return {"url": url, "status": r.status_code, "bytes": len(r.content)}
        except httpx.RequestError as exc:
            return {"url": url, "error": str(exc)}

Une limite de 5 à 20 requêtes simultanées suffit souvent. Le bon réglage dépend du serveur cible, de vos timeouts et du volume à traiter.

Variante avec aiohttp

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        response.raise_for_status()
        text = await response.text()
        return url, len(text)

async def main():
    timeout = aiohttp.ClientTimeout(total=10)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        resultats = await asyncio.gather(*(fetch(session, u) for u in URLS))
    print(resultats)

aiohttp est très performant et complet. HTTPX est souvent plus agréable si vous alternez entre code synchrone et asynchrone. Le choix compte moins que la discipline autour des timeouts, sessions et erreurs.

Gérer les erreurs sans casser tout le lot

En production, certaines URLs échouent. Une stratégie réaliste consiste à retourner un objet résultat qui contient soit les données, soit l’erreur. Cela évite qu’une seule requête défaillante interrompe tout un lot.

async def safe_fetch(client, url):
    try:
        r = await client.get(url)
        return {"url": url, "ok": True, "status": r.status_code}
    except (httpx.TimeoutException, httpx.NetworkError) as exc:
        return {"url": url, "ok": False, "error": type(exc).__name__}

Ne pas oublier le backpressure

Le backpressure consiste à éviter de produire plus de travail que le système ne peut en absorber. Si vous lisez un fichier de 500 000 URLs et créez immédiatement 500 000 tâches, vous risquez de saturer la mémoire. Préférez traiter par lots ou utiliser une queue asynchrone.

async def traiter_par_lots(urls, taille=100):
    async with httpx.AsyncClient(timeout=10) as client:
        for i in range(0, len(urls), taille):
            lot = urls[i:i+taille]
            yield await asyncio.gather(*(safe_fetch(client, u) for u in lot))

Conclusion

Les appels HTTP asynchrones donnent des gains rapides, mais seulement si le code reste responsable : une session réutilisée, des timeouts, une limite de concurrence et une gestion explicite des erreurs. C’est cette base qui permet ensuite de construire des crawlers, des intégrations API ou des services web robustes.

Frédéric Gaurat - formateur et développeur
Résumé de la politique de confidentialité

Ce site utilise des cookies afin que nous puissions vous fournir la meilleure expérience utilisateur possible. Les informations sur les cookies sont stockées dans votre navigateur et remplissent des fonctions telles que vous reconnaître lorsque vous revenez sur notre site Web et aider notre équipe à comprendre les sections du site que vous trouvez les plus intéressantes et utiles.