Geonode logo
Geonode Team

Geonode Team

Aktualisiert: 16. Oktober 2026

Veröffentlicht: 16.10.2026

Einrichtung des aiohttp-Proxys: Sitzungen, Authentifizierung und Verbindungsbeschränkungen

aiohttp ist der Client der Wahl, wenn ein Scraper Hunderte von laufenden Anfragen benötigt und Sie die volle Kontrolle über die Ereignisschleife haben möchten. Die Weiterleitung über einen Proxy erfolgt über einige wenige Schlüsselwortargumente, doch die Standardeinstellungen für Authentifizierung, Verbindungslimits und Timeouts unterscheiden sich von denen von `requests` und httpx – und genau hier scheitern die meisten aiohttp-Proxy-Konfigurationen. Am Ende dieser Anleitung verfügen Sie über ein `ClientSession`, das sich über ein rotierendes Residential-Gateway authentifiziert, begrenzte parallele Anfragen ausführt, vorhersehbare Timeouts aufweist und Proxy-Fehler nach Typ meldet. Die Beispiele beziehen sich auf aiohttp 3.14 unter Python 3.11 oder neuer. Wenn sich das Verhalten in einer aktuellen Version geändert hat, wird die Version ausdrücklich genannt.

aiohttp ist der Client der Wahl, wenn ein Scraper Hunderte von laufenden Anfragen benötigt und Sie die volle Kontrolle über die Ereignisschleife haben möchten. Die Weiterleitung über einen Proxy erfolgt über einige wenige Schlüsselwortargumente, doch die Standardeinstellungen für Authentifizierung, Verbindungslimits und Timeouts unterscheiden sich von denen von requests und httpx – und genau hier scheitern die meisten aiohttp-Proxy-Konfigurationen. Am Ende dieser Anleitung verfügen Sie über ein ClientSession, das sich über ein rotierendes Residential-Gateway authentifiziert, begrenzte parallele Anfragen ausführt, vorhersehbare Timeouts aufweist und Proxy-Fehler nach Typ meldet.

Die Beispiele beziehen sich auf aiohttp 3.14 unter Python 3.11 oder neuer. Wenn sich das Verhalten in einer aktuellen Version geändert hat, wird die Version ausdrücklich genannt.

Konfigurieren eines aiohttp-Proxys auf Sitzungsebene

Das Schlüsselwort „proxy“ wird sowohl pro Anfrage als auch auf Sitzungsebene akzeptiert. Die Einstellung „pro Anfrage“ entspricht der ursprünglichen API und funktioniert weiterhin; die Einstellung auf Sitzungsebene ist jedoch für einen Scraper vorzuziehen, da sie verhindert, dass einzelne Aufrufe den Proxy umgehen.

import asyncio
import aiohttp

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

async def main() -> None:
    async with aiohttp.ClientSession(proxy=PROXY) as session:
        async with session.get("https://api.ipify.org?format=json") as resp:
            print(resp.status, await resp.json())

asyncio.run(main())

Port 9000 ist das rotierende HTTP(S)-Gateway von Geonode, sodass jede neue Verbindung über eine andere private IP-Adresse hergestellt wird. Ein einzelner Aufruf kann die Standardeinstellung weiterhin mit einem eigenen „proxy=“-Argument überschreiben, was praktisch ist, wenn eine Anfrage direkt weitergeleitet werden muss, beispielsweise an einen internen Metrik-Endpunkt.

Form „Pro Anfrage“

async with aiohttp.ClientSession() as session:
    async with session.get("https://api.ipify.org?format=json", proxy=PROXY) as resp:
        print(await resp.json())

Verwenden Sie diese Option, wenn der Proxy von Aufruf zu Aufruf variiert, beispielsweise wenn ein Pool-Manager pro Aufgabe eine andere „Sticky Session“ zuweist.

Authentifizierung: URL-Anmeldedaten oder proxy_headers

Es gibt zwei unterstützte Methoden zum Übermitteln von Proxy-Anmeldedaten sowie eine veraltete Methode, die Sie möglicherweise noch in älteren Anleitungen finden.

MethodeBeispielStatus
Anmeldedaten in der Proxy-URLproxy="http://user:pass@host:9000"Unterstützt
proxy_headers mit encode_basic_authproxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")}Unterstützt, empfohlen
proxy_auth=aiohttp.BasicAuth("user", "pass")Seit 3.14 veraltet, in 4.0 entfernt

Die URL-Form ist am kürzesten. Bei der Header-Form bleibt das Geheimnis aus der URL heraus, was wichtig ist, wenn Proxy-URLs in Protokollen oder Ausnahmemeldungen landen. aiohttp.encode_basic_auth gibt die vorgefertigte Zeichenfolge Basic ... zurück:

import asyncio
import os
import aiohttp

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

async def main() -> None:
    proxy_headers = {
        "Proxy-Authorization": aiohttp.encode_basic_auth(
            os.environ["GEONODE_USER"], os.environ["GEONODE_PASS"]
        )
    }
    async with aiohttp.ClientSession(proxy=PROXY, proxy_headers=proxy_headers) as session:
        async with session.get("https://api.ipify.org?format=json") as resp:
            print(await resp.json())

asyncio.run(main())

Falls Ihre Codebasis noch proxy_auth=aiohttp.BasicAuth(...) verwendet, funktioniert dies in Version 3.14 mit einem DeprecationWarning. Führen Sie jetzt die Migration durch: Der Parameter und die Klasse werden in aiohttp 4.0 entfernt, und die Umstellung erfordert lediglich eine einzeilige Änderung.

Parallele Anfragen mit asyncio.gather und einem Semaphore

Der Grund für die Verwendung von aiohttp ist die Parallelität, und der Grund, warum Parallelität Proxys zum Absturz bringt, ist, dass sie standardmäßig nicht begrenzt wird. asyncio.gather mit mehr als tausend Coroutinen versucht, tausend Verbindungen zu öffnen; der Connector begrenzt dies auf 100, und der Rest wird in aiohttp in eine Warteschlange gestellt, was zwar besser als nichts ist, Ihnen aber keine Kontrolle über das Tempo gibt. Ein Semaphore macht die Begrenzung explizit und ermöglicht es Ihnen, diese unabhängig vom Connector anzupassen.

import asyncio
import aiohttp

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

async def fetch(session: aiohttp.ClientSession, sem: asyncio.Semaphore, url: str) -> tuple[str, int]:
    async with sem:
        async with session.get(url) as resp:
            await resp.read()
            return url, resp.status

async def main() -> None:
    urls = [f"https://httpbin.org/get?i={i}" for i in range(100)]
    sem = asyncio.Semaphore(CONCURRENCY)
    connector = aiohttp.TCPConnector(limit=CONCURRENCY, ttl_dns_cache=300)
    timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=20)
    async with aiohttp.ClientSession(
        proxy=PROXY, connector=connector, timeout=timeout
    ) as session:
        results = await asyncio.gather(
            *(fetch(session, sem, u) for u in urls), return_exceptions=True
        )
    ok = sum(1 for r in results if not isinstance(r, Exception))
    print(f"{ok}/{len(urls)} succeeded")

asyncio.run(main())

return_exceptions=True verhindert, dass eine fehlgeschlagene Anfrage den gesamten Stapel abbricht. Passen Sie den Semaphor an das Limit des Connectors an, damit keine Seite stillschweigend Aufgaben in die Warteschlange stellt, die die andere Seite nicht abarbeiten kann.

TCPConnector: limit und limit_per_host

Der Connector verwaltet den Socket-Pool. Für den aiohttp-Proxy-Datenverkehr sind zwei Parameter von Bedeutung.

limit ist die Gesamtzahl der gleichzeitigen Verbindungen, Standardwert 100. limit_per_host begrenzt die Verbindungen auf ein (host, port, is_ssl)-Tripel; der Standardwert 0 bedeutet „unbegrenzt“. Über einen Proxy nutzen alle Anfragen an alle Ziele denselben Host und Port des Proxys, sodass die Begrenzung pro Host für das Gateway gilt, nicht für die Zielseiten. Bei einer reinen Proxy-Auslastung setzen Sie limit auf die gewünschte Anzahl gleichzeitiger Verbindungen und lassen limit_per_host auf 0; setzen Sie limit_per_host nur dann, wenn dieselbe Sitzung auch direkt mit den Ursprungsservern kommuniziert und Sie diese schützen möchten.

connector = aiohttp.TCPConnector(limit=30, limit_per_host=10, ttl_dns_cache=300)

ttl_dns_cache=300 speichert die DNS-Antwort des Gateways fünf Minuten lang statt der standardmäßigen zehn Sekunden, wodurch bei den meisten neuen Verbindungen ein Roundtrip zum Resolver entfällt. Die Sitzung übernimmt die Verwaltung des Connectors und schließt ihn beim Beenden; übergeben Sie connector_owner=False nur, wenn Sie einen Connector bewusst für mehrere Sitzungen gemeinsam nutzen möchten.

ClientTimeout anstelle der standardmäßigen 5 Minuten

Das standardmäßige Gesamt-Timeout von aiohttp beträgt 300 Sekunden. Das ist für direkte Anfragen großzügig bemessen, für Proxy-Verkehr jedoch viel zu lang, da bei einem blockierten privaten Ausgang die Verbindung schnell abgebrochen werden sollte, damit Sie es mit einer anderen IP erneut versuchen können.

timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=20)

total begrenzt die gesamte Anfrage einschließlich Weiterleitungen und des Downloads des Request-Bodys. connect begrenzt den Aufbau einer Verbindung aus dem Pool sowie den TCP- und Proxy-Handshake. sock_read begrenzt den Abstand zwischen aufeinanderfolgenden Bytes und erfasst so Ziele, die die Verbindung zwar akzeptieren, die Daten aber nur tröpfchenweise übermitteln. Übergeben Sie das Objekt an die Sitzung, um den Standardwert zu verwenden, oder an eine einzelne Anfrage, um ihn zu überschreiben. Bei Ablauf wird asyncio.TimeoutError ausgelöst, wie weiter unten erläutert.

trust_env und Umgebungsproxys

Im Gegensatz zu requests ignoriert aiohttp die Proxys HTTP_PROXY, HTTPS_PROXY und NO_PROXY, es sei denn, Sie aktivieren sie mit trust_env=True. Wenn diese Option aktiviert ist, verwendet aiohttp urllib.request.getproxies(), berücksichtigt no_proxy und liest zudem Proxy-Anmeldedaten aus ~/.netrc aus, sofern vorhanden.

async with aiohttp.ClientSession(trust_env=True) as session:
    async with session.get("https://api.ipify.org?format=json") as resp:
        print(await resp.json())

Für einen Scraper, der immer ein bestimmtes Gateway verwenden muss, lassen Sie trust_env deaktiviert und legen Sie proxy= explizit fest. Auf diese Weise kann ein Unternehmensproxy auf einem CI-Runner das Routing nicht hijacken, und no_proxy kann ein Ziel nicht unbemerkt ausnehmen.

SOCKS5 mit aiohttp-socks

aiohttp bietet keine integrierte SOCKS-Unterstützung, daher ist für einen aiohttp-Proxy über SOCKS5 ein Connector eines Drittanbieters erforderlich. Das Paket aiohttp-socks enthält ein ProxyConnector, das TCPConnector ersetzt und den SOCKS-Handshake übernimmt. Installieren Sie es zusammen mit pip install aiohttp-socks. Der rotierende Port von SOCKS5 in Geonode lautet 11000:

import asyncio
import aiohttp
from aiohttp_socks import ProxyConnector

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

async def main() -> None:
    connector = ProxyConnector.from_url(SOCKS_URL)
    async with aiohttp.ClientSession(connector=connector) as session:
        async with session.get("https://api.ipify.org?format=json") as resp:
            print(await resp.json())

asyncio.run(main())

Kombinieren Sie ProxyConnector nicht mit proxy= in derselben Sitzung; der Connector leitet bereits alles weiter. ProxyConnector akzeptiert dieselben Schlüsselwörter wie limit und limit_per_host, nämlich TCPConnector.

Die Einschränkung bei „TLS-in-TLS“

aiohttp kommuniziert mit HTTP-Proxys und leitet https://-Ziele über diese mit CONNECT weiter. Was es jedoch nicht zuverlässig tut, ist, über TLS eine Verbindung zum Proxy selbst herzustellen, d. h. zu einer https://-Proxy-URL, da TLS-in-TLS von der Unterstützung des asyncio-Transports abhängt, die sich über verschiedene Python-Versionen hinweg unterschiedlich gestaltet hat. In der Dokumentation wird ausdrücklich darauf hingewiesen, dass dies nicht vorausgesetzt werden sollte. In der Praxis spielt dies für Geonode keine Rolle: Das Gateway wird über einfaches HTTP auf Port 9000/10000 erreicht, und Ihr https://-Datenverkehr ist innerhalb des CONNECT-Tunnels weiterhin Ende-zu-Ende verschlüsselt. Behalten Sie daher das Proxy-URL-Schema als http:// bei.

Sticky-Sessions für mehrstufige Abläufe

Der rotierende Port vergibt pro Verbindung eine neue IP-Adresse. Für Anmeldeabläufe oder paginierte Listen benötigen Sie für einige Minuten dieselbe IP-Adresse, die Geonode über den Benutzernamen auf dem HTTP(S)-Sticky-Port 10000 bereitstellt:

import secrets
import aiohttp

def sticky_proxy(country: str = "us", lifetime: int = 600) -> str:
    session_id = secrets.token_hex(4)  # 8 alphanumeric characters
    user = f"USERNAME-country-{country}-session-{session_id}-lifetime-{lifetime}"
    return f"http://{user}:PASSWORD@premium-residential.geonode.com:10000"

session = aiohttp.ClientSession(proxy=sticky_proxy())

Die Lebensdauer reicht von 180 bis 86.400 Sekunden. Die Ausrichtung auf Bundesland und Stadt kann nicht kombiniert werden, und asn erfordert die Angabe eines Landes. Durch die Generierung einer neuen Sitzungs-ID können Sie die Rotation nach Bedarf steuern.

Diagnose: Was bedeutet welche Ausnahme?

AusnahmeBedeutungTypische Behebung
aiohttp.ClientProxyConnectionErrorTCP-Verbindung zum Proxy-Host konnte nicht geöffnet werdenHostname, Port und Firewall überprüfen; Timeout für connect erhöhen
aiohttp.ClientHttpProxyError mit .status == 407Der Proxy hat die Anmeldedaten während des CONNECT-Vorgangs abgelehntBenutzernamen oder Passwort korrigieren; Syntax der Benutzernamenoption überprüfen
aiohttp.ClientHttpProxyError mit 502/503Das Gateway konnte den Tunnel nicht aufbauenMit einer neuen Sitzungs-ID erneut versuchen; überprüfen, ob der Port zum Protokoll passt
asyncio.TimeoutErrorEin Feld von ClientTimeout ist abgelaufenVersuchen Sie es erneut mit einer neuen IP-Adresse; optimieren Sie sock_read
aiohttp.ClientConnectorErrorAllgemeiner Verbindungsfehler (DNS, abgelehnt).os_error überprüfen

ClientProxyConnectionError ist eine Unterklasse von ClientConnectorError, und ClientHttpProxyError ist eine Unterklasse von ClientResponseError; erfassen Sie daher zunächst die spezifischen Fehler.

import asyncio
import aiohttp

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

async def fetch_ip(session: aiohttp.ClientSession) -> str | None:
    try:
        async with session.get("https://api.ipify.org?format=json") as resp:
            resp.raise_for_status()
            return (await resp.json())["ip"]
    except aiohttp.ClientHttpProxyError as e:
        if e.status == 407:
            print("proxy auth failed: check credentials")
        else:
            print("proxy returned", e.status)
    except aiohttp.ClientProxyConnectionError as e:
        print("cannot reach proxy:", e)
    except asyncio.TimeoutError:
        print("timed out through proxy")
    return None

async def main() -> None:
    timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=20)
    async with aiohttp.ClientSession(proxy=PROXY, timeout=timeout) as session:
        print(await fetch_ip(session))
    await asyncio.sleep(0.25)  # let SSL transports close before the loop stops

asyncio.run(main())

Beachten Sie, dass bei einem einfachen http://-Ziel der 407-Code des Proxys als normale Antwort und nicht als Ausnahme zurückkommt; überprüfen Sie daher dort auch resp.status.

Ordentliches Herunterfahren

Wenn eine Sitzung geschlossen wird, ist asyncio möglicherweise noch dabei, die zugrunde liegenden SSL-Transporte abzubauen. Wenn die Ereignisschleife zuerst stoppt, erhalten Sie den Fehlercode ResourceWarning: unclosed transport. Die dokumentierte Abhilfe besteht in einer kurzen Pause nach dem „async with“-Block der Sitzung: „await asyncio.sleep(0)“ für reine HTTP-Sitzungen und „await asyncio.sleep(0.25)“, wenn die Sitzung HTTPS-Anfragen gestellt hat, was über einen CONNECT-Tunnel fast immer der Fall ist. Das obige Beispiel enthält diese Pause.

Häufige Fehler

Eine Sitzung pro Anfrage erstellen

ClientSession verwaltet den Verbindungspool und den Cookie-Speicher. Das Öffnen einer neuen Sitzung pro Anfrage untergräbt die Keep-Alive-Funktion zum Gateway, vervielfacht die Proxy-Handshakes und löst bei jedem Aufruf die Shutdown-Warnung aus. Erstellen Sie eine Sitzung pro Worker und geben Sie diese weiter.

Vergessen, dass der Proxy der einzige „Host“ ist

Wenn limit_per_host auf einen niedrigen Wert gesetzt ist, wird jede über den Proxy geleitete Anfrage auf den einzigen (host, port)-Eintrag des Gateways angerechnet, sodass eine Obergrenze von 5 pro Host Ihren gesamten Scraper auf 5 Verbindungen drosselt. Stellen Sie stattdessen limit ein oder belassen Sie limit_per_host auf 0.

Sich auf das Standard-Timeout verlassen

Fünf Minuten sind lang genug, dass ein blockierter privater Ausgang einen Semaphor-Slot für das gesamte Zeitfenster belegt. Übergeben Sie immer einen ClientTimeout-Wert zusammen mit den Werten für connect und sock_read.

proxy_auth wird weiterhin übergeben

Dies funktioniert derzeit mit einer Warnung, führt jedoch in Version 4.0 zu einem Fehler. Ersetzen Sie den Wert durch proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth(user, pw)} oder fügen Sie die Anmeldedaten in die Proxy-URL ein.

Nächster Schritt

Mit der oben beschriebenen Sitzung haben Sie den aiohttp-Proxy-Datenverkehr authentifiziert, gebunden und beobachtbar gemacht; nun fehlt nur noch der Pool, über den er ausgegeben wird. Geonode Privathaushalts-Proxys bedienen die hier verwendeten Endpunkte: 2,5 Millionen IP-Adressen online, 30–50 Millionen eindeutige IP-Adressen, die monatlich in über 195 Ländern rotieren, mit Targeting nach Land, Bundesland, Stadt und ASN über den Benutzernamen sowie Sticky-Sessions von bis zu 24 Stunden über HTTP, HTTPS und SOCKS5. Die Preise beginnen bei 0,27 $/GB bei großen Volumina, das Einstiegspaket umfasst 10 GB für 7,92 $/Monat und eine 3-tägige Testversion kostet 5 $ ohne Vertragsbindung. Die Anwendungsbeispiele Web-Scraping und Preisauswertung zeigen, wie Teams diese Konnektor-Konfiguration mit Wiederholungs- und Planungslogik kombinieren.

FAQ

Wie richte ich einen aiohttp-Proxy für eine gesamte Sitzung ein?

Übergeben Sie proxy= an den Konstruktor von ClientSession: aiohttp.ClientSession(proxy="http://user:pass@host:port"). Jede Anfrage aus dieser Sitzung verwendet diesen Wert, es sei denn, der Aufruf überschreibt proxy= mit einem anderen Wert. Der Proxy auf Sitzungsebene ist seit aiohttp 3.11 verfügbar; bei älteren Versionen müssen Sie stattdessen bei jedem Aufruf von get() oder post() proxy= übergeben.

Warum ist „proxy_auth“ in aiohttp veraltet und was ersetzt es?

Seit aiohttp 3.14 sind der Parameter proxy_auth und die Klasse aiohttp.BasicAuth veraltet und sollen in Version 4.0 entfernt werden. Der Ersatz ist ein einfacher Header: proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")}. Er führt dieselbe Base64-Kodierung ohne Hilfsklasse durch und funktioniert sowohl pro Anfrage als auch auf Sitzungsebene.

Unterstützt aiohttp HTTPS-Proxys oder SOCKS5?

aiohttp kommuniziert nativ mit HTTP-Proxys und nutzt CONNECT, um https://-Ziele durch diese zu tunneln. Die Verbindung zum Proxy selbst über TLS (TLS-in-TLS) hängt von der asyncio-Unterstützung in Ihrem Python-Build ab und sollte nicht als verlässlich angesehen werden. Für SOCKS4 und SOCKS5 ist das Drittanbieter-Paket aiohttp-socks sowie dessen ProxyConnector erforderlich.

Wie viele gleichzeitige Anfragen kann ich über einen aiohttp-Proxy ausführen?

TCPConnector erlaubt standardmäßig 100 gleichzeitige Verbindungen ohne Begrenzung pro Host. Über einen Proxy nutzen alle Anfragen denselben Host und denselben Port. Legen Sie daher limit explizit fest, fügen Sie limit_per_host hinzu, wenn Sie auch direkte Aufrufe durchführen, und verpacken Sie Anfrage-Coroutinen in ein asyncio.Semaphore, damit nicht alle Aufgaben in der Warteschlange gleichzeitig Sockets öffnen.

Warum hängt sich meine aiohttp-Anfrage über einen Proxy auf, anstatt fehlzuschlagen?

ClientSession hat standardmäßig ein Gesamt-Timeout von 5 Minuten, was wie ein Hängenbleiben aussieht, wenn ein privater Exit-Server ins Stocken gerät. Übergeben Sie aiohttp.ClientTimeout(total=30, connect=10, sock_read=20) an die Sitzung oder die Anfrage, damit Verzögerungen schnell als asyncio.TimeoutError erkennbar werden; versuchen Sie es dann mit einer neuen Sitzungs-ID erneut, anstatt zu warten.

Wo liegen die praktischen Grenzen beim Routing von Scrapern über private Proxys?

Ein Proxy ändert lediglich die Quell-IP-Adresse. Er gewährt keine Erlaubnis, „robots.txt“, Nutzungsbedingungen oder Ratenbeschränkungen zu ignorieren, und er macht das Erfassen personenbezogener Daten nicht rechtmäßig. Halten Sie die Parallelität in einem vernünftigen Rahmen, gehen Sie bei 429-Fehlern zurück und denken Sie daran, dass „sticky sessions“ mit dem Status Geonode höchstens 24 Stunden andauern, bevor die IP-Adresse freigegeben wird.