HTTPX, Requests und AIOHTTP im Vergleich

Veröffentlicht:

11 Min. Lesezeit

Acar Diveroli
Autor: Acar Diveroli
Codeblöcke in drei Spalten

Für HTTP-Anfragen in Python stechen drei Bibliotheken hervor: Requests, HTTPX und AIOHTTP. Alle drei erledigen dieselbe Grundaufgabe, doch der Unterschied zwischen ihnen zeigt sich nicht beim Schreiben eines kleinen Skripts, sondern wenn Sie Tausende Anfragen gleichzeitig verwalten müssen. In diesem Beitrag vergleichen wir die drei Bibliotheken hinsichtlich Bedienbarkeit, Nebenläufigkeit, Proxy-Unterstützung, Fehlerbehandlung und Ökosystem, liefern funktionierende Beispiele für jede und zeigen den Weg von Requests zu asynchronem Code.

Die Codebeispiele wurden mit Python 3.13 und Requests 2.32, HTTPX 0.28 sowie AIOHTTP 3.13 getestet.

Die drei Bibliotheken kurz vorgestellt

  • Requests: Die verbreitetste HTTP-Bibliothek in Python. Läuft synchron, ist sehr leicht zu lernen, Dokumentation und Beispiele gibt es überall. Beachten Sie: Das Programm wartet an dieser Zeile, bis jede Anfrage abgeschlossen ist.
  • HTTPX: Bietet eine Requests sehr ähnliche Schnittstelle sowohl synchron als auch asynchron. Unterstützt auch HTTP/2. HTTPX ist meist der am wenigsten aufwendige Weg, bestehenden Requests-Code auf asynchron umzustellen.
  • AIOHTTP: Eine von Grund auf asynchron konzipierte Bibliothek. Bringt neben dem Client auch einen Webserver mit. Für Aufgaben mit sehr hoher Nebenläufigkeit eine ausgereifte und verbreitete Wahl.

Was bedeuten synchron und asynchron?

Da dieser Begriff im Zentrum des Vergleichs steht, erklären wir ihn kurz. Ein synchroner Client sendet die Anfrage und wartet, bis die Antwort eintrifft; währenddessen tut das Programm nichts anderes. Ein asynchroner Client übergibt nach dem Senden der Anfrage die Kontrolle an die Ereignisschleife; während auf die Antwort gewartet wird, können weitere Anfragen losgeschickt werden.

Der größte Teil der Dauer einer Webanfrage vergeht im Netzwerk, also beim Warten auf die Antwort des Zielservers. Bei synchronem Code verstreicht diese Wartezeit ungenutzt; bei asynchronem Code kann in derselben Zeit auf Dutzende weitere Anfragen gewartet werden. Bei hundert Seiten spürt man den Unterschied nicht; bei zehntausend Seiten wird daraus der Unterschied zwischen Stunden und Minuten.

Der Preis dieses Vorteils ist Komplexität: die async/await-Syntax, die Ereignisschleife und Inkompatibilität mit nicht-asynchronen Bibliotheken. Deshalb ist "asynchron ist immer besser" falsch; ohne echten Bedarf ist synchroner Code lesbarer und leichter zu warten.

Die wichtigsten Unterschiede in einer Tabelle

MerkmalRequestsHTTPXAIOHTTP
Synchrone NutzungJaJaNein
Asynchrone NutzungNeinJaJa
HTTP/2NeinJa (mit Zusatzpaket)Nein
Proxy (HTTP/HTTPS)EingebautEingebautEingebaut
SOCKS-ProxyMit requests[socks]Mit httpx[socks]Mit externem Paket
Standard-TimeoutKeiner (wartet unbegrenzt)5 Sekunden5 Minuten (gesamt)
Weiterleitungen folgenStandardmäßig aktivStandardmäßig ausStandardmäßig aktiv
Connection-PoolMit SessionMit ClientMit ClientSession
LernkurveSehr leichtLeichtMittel
Geeignet fürEinfaches Skript, kleines VolumenGemischte synchron+asynchrone ProjekteHohe Nebenläufigkeit

Die Zeile "Standard-Timeout" in der Tabelle ist der am häufigsten übersehene Unterschied zwischen den drei Bibliotheken. Geben Sie bei Requests kein Timeout an, kann die Anfrage unbegrenzt warten; deshalb sollte timeout= bei jedem Requests-Aufruf zur Gewohnheit werden.

Requests: einfach und synchron

Installation:

bash
pip install requests

Eine einzelne Anfrage über einen Proxy:

python
import requests

PROXY = "http://kullanici:parola@pr.proxynet.io:8000"
proxies = {"http": PROXY, "https": PROXY}

response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=20)
print(response.status_code, response.json())

Der Schlüssel https im proxies-Dictionary ist der Proxy, der beim Zugriff auf HTTPS-Adressen verwendet wird. Dass der Wert mit http:// beginnt, ist korrekt: HTTPS-Traffic wird innerhalb der mit dem Proxy aufgebauten Verbindung getunnelt.

Senden Sie mehrere Anfragen, nutzen Sie Session; Verbindungen werden wiederverwendet, Cookies bleiben erhalten und die Proxy-Einstellung erfolgt einmalig:

python
import requests

PROXY = "http://kullanici:parola@pr.proxynet.io:8000"

with requests.Session() as session:
    session.proxies.update({"http": PROXY, "https": PROXY})
    session.headers.update({"User-Agent": "daten-erfassung-bot/1.0"})
    for path in ["ip", "headers", "user-agent"]:
        r = session.get(f"https://httpbin.org/{path}", timeout=20)
        print(path, r.status_code)

Requests' Grenze ist die Nebenläufigkeit. Rufen Sie 1.000 Seiten nacheinander ab, nähert sich die Gesamtdauer der Summe der Dauer jeder einzelnen Anfrage. Das lässt sich mit concurrent.futures.ThreadPoolExecutor abmildern, doch bei sehr hohem Volumen arbeitet eine asynchrone Bibliothek effizienter.

HTTPX: zwischen zwei Welten

Installation:

bash
pip install httpx

Die synchrone Nutzung ähnelt Requests fast eins zu eins:

python
import httpx

PROXY = "http://kullanici:parola@pr.proxynet.io:8000"

with httpx.Client(proxy=PROXY, timeout=20) as client:
    response = client.get("https://httpbin.org/ip")
    print(response.json())

Die asynchrone Variante desselben Codes sendet mehrere Anfragen gleichzeitig:

python
import asyncio
import httpx

PROXY = "http://kullanici:parola@pr.proxynet.io:8000"
URLS = ["https://httpbin.org/ip"] * 10

async def main():
    async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
        responses = await asyncio.gather(*(client.get(url) for url in URLS))
        print([r.status_code for r in responses])

asyncio.run(main())

Hier werden 10 Anfragen nicht nacheinander, sondern gleichzeitig losgeschickt. Die Gesamtdauer nähert sich der Dauer der langsamsten Anfrage.

Für HTTP/2 genügt es, pip install "httpx[http2]" zu installieren und dem Client http2=True zu übergeben. Senden Sie viele Anfragen an denselben Server, sinkt der Verbindungsaufbau-Aufwand, weil über eine einzige Verbindung gemultiplext wird. Bei Proxy-Nutzung läuft HTTP/2 innerhalb des CONNECT-Tunnels zwischen Ihnen und der Zielseite; der Proxy muss HTTP/2 nicht unterstützen.

AIOHTTP: für hohe Nebenläufigkeit

Installation:

bash
pip install aiohttp
python
import asyncio
import aiohttp

PROXY = "http://pr.proxynet.io:8000"
AUTH = aiohttp.BasicAuth("kullanici", "parola")
URLS = ["https://httpbin.org/ip"] * 10

async def fetch(session, url):
    async with session.get(url, proxy=PROXY, proxy_auth=AUTH) as response:
        return response.status

async def main():
    async with aiohttp.ClientSession() as session:
        statuses = await asyncio.gather(*(fetch(session, url) for url in URLS))
        print(statuses)

asyncio.run(main())

Bei AIOHTTP wird der Proxy nicht in der Session, sondern bei jeder Anfrage über den Parameter proxy= übergeben. Die Zugangsdaten können Sie getrennt mit proxy_auth angeben oder direkt in die Adresse schreiben (http://kullanici:parola@...). Bei jeder Anfrage einen anderen Proxy anzugeben, ist mit diesem Design von Natur aus möglich; wollen Sie Ihre eigene IP-Liste rotieren lassen, macht AIOHTTP das einfach.

Eine weitere Besonderheit von AIOHTTP: Der Antwortkörper muss innerhalb des Kontextmanagers gelesen werden. await response.text() außerhalb des async with-Blocks aufzurufen, führt zu einem Fehler; lesen Sie den Body innerhalb des Blocks in eine Variable.

Nebenläufigkeit begrenzen

Lassen Sie bei asynchroner Nutzung die Nebenläufigkeit nicht unbegrenzt. Zehntausend URLs mit asyncio.gather auf einmal zu starten, überfordert sowohl das Limit an Dateideskriptoren Ihres eigenen Systems als auch drängt es die Zielseite sofort ins Rate-Limiting. Mit asyncio.Semaphore die Anzahl gleichzeitig offener Anfragen zu begrenzen, schützt beide Seiten:

python
import asyncio
import httpx

PROXY = "http://kullanici:parola@pr.proxynet.io:8000"
URLS = [f"https://httpbin.org/get?i={i}" for i in range(100)]
LIMIT = asyncio.Semaphore(10)

async def fetch(client, url):
    async with LIMIT:
        r = await client.get(url)
        return r.status_code

async def main():
    async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
        results = await asyncio.gather(*(fetch(client, u) for u in URLS))
        print(sum(1 for s in results if s == 200), "erfolgreich")

asyncio.run(main())

In diesem Beispiel werden hundert Anfragen in die Warteschlange gestellt, aber höchstens zehn gleichzeitig sind offen. Passen Sie das Limit an die Toleranz der Zielseite und das Kontingent an gleichzeitigen Verbindungen Ihres Proxy-Pakets an.

Fehlerbehandlung und Wiederholung

In allen drei Bibliotheken kommen Netzwerkfehler als Exception; HTTP-Fehlercodes (404, 429, 500) sind standardmäßig keine Exception. Die Antwort zu prüfen ist Ihre Aufgabe:

  • Bei Requests und HTTPX wirft response.raise_for_status() bei 4xx- und 5xx-Antworten eine Exception.
  • Bei AIOHTTP geschieht dasselbe mit response.raise_for_status() oder beim Öffnen der Session mit raise_for_status=True.

Bei der Arbeit mit einem Proxy sehen Sie am häufigsten folgende Codes:

CodeBedeutungWas tun?
407Proxy-Authentifizierung fehlgeschlagenBenutzername, Passwort und Kodierung von Sonderzeichen prüfen
429Zielseite wendet Rate-Limiting anNebenläufigkeit senken, warten und erneut versuchen, IP-Verteilung erhöhen
403Zielseite lehnt die Anfrage abUser-Agent und IP-Typ überprüfen
502 / 504Proxy erreicht das Ziel nichtMit einer anderen ausgehenden IP erneut versuchen

Schreiben Sie die Wiederholungslogik mit exponentiellem Backoff: erster Versuch eine Sekunde, dann zwei, dann vier. In konstanten Abständen zu beharren kann dazu führen, dass eine IP, die 429 erhalten hat, vollständig gesperrt wird.

Performance: welche ist schneller?

Bei der Bibliothekswahl schaut die Frage "welche ist schneller" meist an der falschen Stelle. Der größte Teil der Dauer einer Webanfrage vergeht im Netzwerk; die Verarbeitungszeit der Bibliothek selbst ist im Vergleich dazu klein.

Entscheidend ist, auf wie viele Anfragen Sie gleichzeitig warten können:

  • Ein synchroner Client wartet auf jede Anfrage nacheinander.
  • Ein asynchroner Client kann auf Hunderte Anfragen gleichzeitig warten.

Für eine Aufgabe mit 10 Seiten reicht also Requests. Für eine Aufgabe mit 10.000 Seiten verkürzt der asynchrone Client von HTTPX oder AIOHTTP die Dauer erheblich. Innerhalb derselben Klasse (asynchrones HTTPX gegenüber AIOHTTP) gibt es messbare Unterschiede, und AIOHTTP liegt im reinen Durchsatz meist vorn; dieser Unterschied verschwindet aber in den meisten Projekten im Schatten der Antwortzeit der Zielseite und der Proxy-Latenz.

Wie sollten Proxy und Nebenläufigkeit zusammen gedacht werden?

Asynchrone Bibliotheken können viele Anfragen gleichzeitig senden; kommen diese Anfragen aber alle von einer einzigen IP, wendet die Zielseite schnell Rate-Limiting an. Mit steigender Nebenläufigkeit müssen Sie auch über die IP-Verteilung nachdenken:

  • Bei jeder Anfrage eine andere IP: Mit Rotierender Proxy nutzen Sie eine einzige Eingangsadresse und erhalten bei jeder Anfrage eine andere ausgehende IP. Ihr Code muss sich nicht ändern; alle obigen Beispiele funktionieren unverändert.
  • Über die Sitzung dieselbe IP: Bei angemeldeten Abläufen oder solchen mit Warenkorb-Status behält Sticky-Proxy dieselbe IP für einen bestimmten Zeitraum.
  • Eigene Liste rotieren: Haben Sie eine feste IP-Liste, können Sie die Rotation im Code selbst vornehmen; ein Schritt-für-Schritt-Beispiel finden Sie in unserem Beitrag Wie rotiert man Proxys in Python?.
  • IP-Typ: Bei geschützten Zielen ist der IP-Typ wichtiger als die Nebenläufigkeit; warum, haben wir in Residential- und Datacenter-Proxy im Vergleich erklärt.

Der Wechsel von Requests zu HTTPX

Möchten Sie ein bestehendes Requests-Projekt auf asynchron umstellen, ist HTTPX der kürzeste Weg. Beim Umstieg zu beachtende Unterschiede:

RequestsHTTPXAnmerkung
requests.get(url)httpx.get(url)Gleich
proxies={"http": p, "https": p}proxy=pEin Parameter
timeout=None Standardtimeout=5 StandardBei HTTPX ist ein Timeout standardmäßig aktiv
Weiterleitung wird standardmäßig verfolgtfollow_redirects=True erforderlichHTTPX folgt standardmäßig nicht
Session()Client() / AsyncClient()Gleiche Logik
response.json()response.json()Gleich

Der Großteil des Codes funktioniert unverändert; die Unterschiede sammeln sich in den fünf obigen Zeilen. Zuerst mit dem synchronen Client umzusteigen und die Tests laufen zu lassen, bevor Sie zu AsyncClient wechseln, senkt das Risiko.

Welche sollten Sie wählen?

  • Wählen Sie Requests: für kleine Skripte, Automatisierungsaufgaben, API-Integrationen und Arbeiten, bei denen Nebenläufigkeit keine Rolle spielt.
  • Wählen Sie HTTPX: wenn Sie heute synchron beginnen und morgen möglicherweise asynchron werden, HTTP/2 benötigen oder im selben Projekt beide Nutzungsarten zusammen wollen.
  • Wählen Sie AIOHTTP: für von Grund auf asynchron konzipierte, sehr hochvolumige Datenerfassungsarbeiten und wenn Sie in derselben Anwendung sowohl Client als auch Server brauchen.

Reicht das reine Abrufen der Seiten per HTTP nicht aus, weil der Inhalt per JavaScript geladen wird, genügen diese Bibliotheken allein nicht mehr und Sie brauchen eine Browser-Automatisierung. Diese Unterscheidung haben wir in Web Scraping: JavaScript oder Python? behandelt. Für Browser-Automatisierung auf Python-Seite finden Sie in unseren Anleitungen zu Selenium und SeleniumBase mit Proxy weitere Informationen.

Häufig gestellte Fragen

Ist der Wechsel von Requests zu HTTPX schwierig?

Meist nicht. httpx.get statt requests.get zu schreiben, funktioniert in den meisten einfachen Codes direkt. Die Unterschiede liegen in Details wie dem standardmäßig aktiven Timeout, nicht standardmäßig verfolgten Weiterleitungen und dem Namen des Proxy-Parameters. Die obige Umstiegstabelle listet diese Unterschiede.

Wie nutzt man einen SOCKS5-Proxy?

Installieren Sie für Requests pip install "requests[socks]", für HTTPX pip install "httpx[socks]" und verwenden Sie in der Proxy-Adresse das Schema socks5://. Soll auch die DNS-Auflösung proxyseitig erfolgen, schreiben Sie bei Requests socks5h://. Die Unterschiede zwischen den Protokollen finden Sie in unserem Beitrag SOCKS und HTTP Proxy im Vergleich.

Ist asynchroner Code immer besser?

Nein. Asynchroner Code ist komplexer und schwerer zu debuggen. Für eine Aufgabe, die von Nebenläufigkeit nicht wirklich profitiert, ist synchroner Code lesbarer und leichter zu warten.

Ist die Nutzung von Threads mit Requests eine Alternative zu asynchron?

Für mittlere Volumen ja. Mit ThreadPoolExecutor lassen sich Dutzende Anfragen parallel ausführen, und der Code bleibt synchron. Bei Hunderten gleichzeitigen Anfragen steigt der Speicheraufwand von Threads; ab diesem Punkt ist ein asynchroner Client effizienter.

Muss ich Proxy-Zugangsdaten in den Code schreiben?

Nein. Alle drei Bibliotheken lesen die Umgebungsvariablen HTTP_PROXY und HTTPS_PROXY (bei HTTPX ist trust_env standardmäßig aktiv). Zugangsdaten in einer Umgebungsvariable zu halten, senkt das Risiko, sie beim Einchecken des Codes zu leaken.

Wie viele Anfragen sollte ich gleichzeitig öffnen?

Es gibt keine einzelne richtige Zahl. Entscheidend sind die Toleranz der Zielseite, das Kontingent an gleichzeitigen Verbindungen Ihres Proxy-Pakets und die Grenzen Ihrer eigenen Maschine. Mit zehn zu beginnen und zu erhöhen, solange keine 429-Antwort erscheint, ist ein sicheres Vorgehen.

Fazit

Alle drei Bibliotheken erledigen ihre Aufgabe gut; die Wahl hängt vom Umfang des Projekts ab. Für einfache Arbeiten sticht Requests hervor, für Flexibilität und Zukunftssicherheit HTTPX, für hohe Nebenläufigkeit AIOHTTP. Für welche Bibliothek Sie sich auch entscheiden: Setzen Sie ein Timeout, begrenzen Sie die Nebenläufigkeit und schreiben Sie Wiederholungen mit exponentiellem Backoff. Mit steigendem Volumen gewinnt neben der Bibliothek auch die IP-Strategie an Bedeutung. Für eine großangelegte Dateninfrastruktur können Sie sich unsere Datenerfassungslösungen ansehen.

ChatGPT fragenClaude fragen