Scraping mit nodriver: Voll asynchrone CDP-Automatisierung mit Residential Proxies

Praktischer Guide für Scraping mit nodriver — der CDP-basierte Nachfolger von undetected-chromedriver. Proxy-Konfiguration mit ProxyHat, Skalierung, Code-Beispiele und Best Practices.

Scraping With nodriver: A Practical Guide to Undetected Async Browser Automation
In diesem Artikel

Scraping mit nodriver: Voll asynchrone CDP-Automatisierung mit Residential Proxies

Wenn Sie bislang undetected-chromedriver eingesetzt haben, kennen Sie das Problem: Selenium-basierte Wrapper sind synchron, schwer zu skalieren und hinterlassen Spuren, die moderne Anti-Bot-Systeme wie Cloudflare und Imperva erkennen. Scraping mit nodriver bietet einen fundamental anderen Ansatz — keine WebDriver-Schicht, direkte Kommunikation über das Chrome DevTools Protocol (CDP) und eine vollständig asynchrone API, die sich nativ in Python-Event-Loops einfügt. In diesem Guide zeigen wir, wie Sie nodriver idiomatisch einsetzen und mit nodriver proxy-Konfiguration über ProxyHat residential IPs kombinieren, um auch stark geschützte Seiten zuverlässig zu scrapen.

Rechtlicher Hinweis: Web Scraping ist legal, solange Sie öffentliche Daten erfassen und die Nutzungsbedingungen der Zielseite respektieren. In den USA kann der Computer Fraud and Abuse Act (CFAA) bei unbefugtem Zugriff greifen; in der EU regelt die DSGVO (GDPR) die Verarbeitung personenbezogener Daten. Scrapen Sie nur Seiten, für die Sie autorisiert sind, oder die explizit öffentliche Daten bereitstellen.

Warum nodriver existiert: Die Architektur hinter nodriver undetected

nodriver (früher undetected_chromedriver v2) wurde von denselben Entwicklern erstellt, die schon den Vorgänger gepflegt haben. Der Kernunterschied: nodriver entfernt die gesamte Selenium/WebDriver-Schicht und spricht direkt über Websockets mit dem Chrome DevTools Protocol. Das bedeutet:

  • Kein navigator.webdriver-Flag: Da kein WebDriver-Protokoll verwendet wird, bleibt die Standard-Eigenschaft navigator.webdriver auf undefined, was der häufigste Erkennungsvektor für Headless-Browser ist.
  • Keine CDP-Lecks: nodriver patcht bekannte CDP-Spuren, die von Anti-Bot-Systemen abgefragt werden — darunter Runtime.evaluate-Fingerabdrücke und Page.addScriptToEvaluateOnNewDocument-Artefakte.
  • Voll asynchron: Jede Interaktion ist ein await-Aufruf. nodriver async ermöglicht echte Nebenläufigkeit: Sie können dutzende Tabs parallel steuern, ohne Threads blockieren zu müssen.

Die Architektur ist einfach: uc.start() startet einen Chrome-Prozess und öffnet eine CDP-Websocket-Verbindung. Sie erhalten ein Browser-Objekt, von dem aus Sie Tabs (Tab-Objekte) erstellen und steuern. Jeder Tab ist eine eigenständige CDP-Session mit eigenem Kontext.

CDP vs. WebDriver: Warum es gegen Cloudflare hilft

Cloudflare und Imperva nutzen mehrere Heuristiken zur Bot-Erkennung. Eine der wichtigsten ist die Kombination aus navigator.webdriver === true und dem Vorhandensein von WebDriver-spezifischen Chrome-Flags. Da nodriver kein WebDriver-Protokoll spricht, fallen diese Checks weg. Laut der offiziellen CDP-Dokumentation kommuniziert der Browser nativ über JSON-Befehle auf der DevTools-Websocket-Schnittstelle — genau wie ein Entwickler in den Chrome DevTools.

Das allein reicht jedoch nicht. Ohne eine nodriver proxy-Lösung mit residential IPs wird Cloudflare Ihre Rechenzentrum-IP erkennen und eine Challenge auslösen. Mehr dazu unten.

Die idiomatische nodriver API

nodriver ist nodriver undetected und idiomatisch asynchron. Die wichtigsten Konzepte sind uc.start() für den Browser-Launch, Tab-Objekte für die Seitensteuerung und Event-Hooks statt WebDriver-Waits.

Browser und Tab-Objekte

import nodriver as uc
import asyncio

async def main():
    browser = await uc.start(
        headless=False,
        browser_args=["--disable-blink-features=AutomationControlled"]
    )
    tab = await browser.get("https://example.com")
    await asyncio.sleep(2)
    # Element finden — kein WebDriverWait, sondern await tab.find()
    heading = await tab.find("h1")
    print(heading.text)
    browser.stop()

asyncio.run(main())

Beachten Sie: tab.find() ist ein await-Aufruf, der das Element abwartet, sobald es im DOM erscheint. Es gibt kein implizites time.sleep oder WebDriverWait. Das ist die idiomatische Art, nodriver web scraping zu betreiben.

Event Hooks statt WebDriver Waits

nodriver nutzt CDP-Events anstelle von Polling. Sie können Handler registrieren, die bei bestimmten CDP-Events feuern:

import nodriver as uc
import asyncio

async def on_request(event):
    if event.request and "api" in event.request.url:
        print(f"API call detected: {event.request.url}")

async def main():
    browser = await uc.start()
    tab = await browser.get("https://example.com")
    tab.add_handler(uc.cdp.network.RequestWillBeSent, on_request)
    await asyncio.sleep(5)
    browser.stop()

asyncio.run(main())

Dieser Ansatz ist effizienter als Polling und blockiert nicht den Event-Loop. Für nodriver async-Workflows ist dies der empfohlene Weg, auf dynamische Inhalte zu reagieren.

Proxy-Konfiguration: nodriver proxy mit ProxyHat

nodriver hat keine eingebaute Proxy-Rotation. Sie konfigurieren Proxies über Chrome-Befehlszeilenargumente, genauer über --proxy-server=. Das Problem: Chrome unterstützt keine Proxy-Authentifizierung über Befehlszeilenargumente. nodriver löst dies über CDP: Sie registrieren einen Fetch.authRequired-Handler, der die Anmeldedaten bereitstellt.

ProxyHat verwendet das Format USERNAME:PASSWORD@gate.proxyhat.com:8080. Für nodriver müssen Sie den Proxy-Server und die Authentifizierung separat handhaben:

import nodriver as uc
import asyncio

PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 8080
PROXY_USER = "user-country-US-session-abc123"
PROXY_PASS = "your_password"

async def auth_handler(event):
    await event.tab.send(uc.cdp.fetch.continue_with_auth(
        request_id=event.request_id,
        auth_challenge_response=uc.cdp.fetch.AuthChallengeResponse(
            response="ProvideCredentials",
            username=PROXY_USER,
            password=PROXY_PASS,
        )
    ))

async def main():
    browser = await uc.start(
        headless=False,
        browser_args=[
            f"--proxy-server=http://{PROXY_HOST}:{PROXY_PORT}",
            "--disable-blink-features=AutomationControlled",
        ]
    )
    tab = await browser.get("https://example.com")
    
    # Auth-Handler für Proxy registrieren
    tab.add_handler(uc.cdp.fetch.AuthRequired, auth_handler)
    await tab.send(uc.cdp.fetch.enable(handle_auth_requests=True))
    
    await asyncio.sleep(3)
    content = await tab.get_content()
    print(content[:500])
    browser.stop()

asyncio.run(main())

Warum Residential Proxies unverzichtbar sind

Auch der beste Browser-Fingerprint nützt nichts, wenn die IP-Adresse aus einem bekannten Rechenzentrum-Block stammt. Cloudflare und Imperva unterhalten Datenbanken mit AS-Nummern (Autonomous System Numbers) von Cloud-Providern wie AWS, DigitalOcean und Hetzner. Eine IP aus AS14061 (DigitalOcean) wird fast immer als Bot eingestuft.

Residential Proxies verwenden IPs, die von ISPs an Privathaushalte vergeben werden. Sie erscheinen als reguläre Endnutzer und haben eine deutlich niedrigere Block-Rate. In der Praxis sehen wir Block-Raten von 40-60% mit Datacenter-IPs bei Cloudflare-geschützten Seiten, während residential IPs typischerweise unter 5% liegen. Die ProxyHat Locations bieten über 90 Länder zur Auswahl.

Praktisches Beispiel: Geschützte Seite scrapen mit ProxyHat

Hier ist ein vollständiges, ausführbares Beispiel, das nodriver über einen US-Residential-Proxy mit Sticky Session startet, eine geschützte Seite navigiert und JSON-Daten extrahiert:

import nodriver as uc
import asyncio
import json

class ProxyHatConfig:
    HOST = "gate.proxyhat.com"
    PORT = 8080
    
    def __init__(self, country="US", session_id="abc123"):
        self.username = f"user-country-{country}-session-{session_id}"
        self.password = "your_proxyhat_password"
    
    @property
    def server_url(self):
        return f"http://{self.HOST}:{self.PORT}"
    
    def auth_challenge_response(self):
        return uc.cdp.fetch.AuthChallengeResponse(
            response="ProvideCredentials",
            username=self.username,
            password=self.password,
        )

async def scrape_protected_page(url: str, proxy: ProxyHatConfig):
    browser = await uc.start(
        headless=False,
        browser_args=[
            f"--proxy-server={proxy.server_url}",
            "--disable-blink-features=AutomationControlled",
            "--no-first-run",
            "--no-default-browser-check",
        ]
    )
    
    tab = await browser.get(url)
    
    # Proxy-Auth über CDP Fetch.enable
    await tab.send(uc.cdp.fetch.enable(handle_auth_requests=True))
    
    async def on_auth_required(event):
        await tab.send(uc.cdp.fetch.continue_with_auth(
            request_id=event.request_id,
            auth_challenge_response=proxy.auth_challenge_response()
        ))
    
    tab.add_handler(uc.cdp.fetch.AuthRequired, on_auth_required)
    
    # Auf Seitenladung warten
    await tab.wait_for("body")
    await asyncio.sleep(2)
    
    # JSON aus Script-Tag extrahieren
    script_tag = await tab.find("script#__NEXT_DATA__")
    if script_tag:
        raw_json = await script_tag.get_attribute("innerHTML")
        data = json.loads(raw_json)
        print(json.dumps(data, indent=2)[:1000])
    
    browser.stop()
    return data

# Ausführung
proxy = ProxyHatConfig(country="US", session_id="sess-001")
result = asyncio.run(scrape_protected_page(
    "https://example.com/protected-page",
    proxy
))

Dieser Ansatz kombiniert nodriver undetected-Stealth mit ProxyHat residential IPs. Die session-abc123-Flag sorgt dafür, dass alle Anfragen über dieselbe IP laufen — wichtig für Seiten, die Session-Konsistenz erwarten. Wenn Sie eine neue IP benötigen, ändern Sie einfach die Session-ID. Dies ist die Kernstrategie für nodriver web scraping gegen Anti-Bot-Systeme.

Skalierung: Concurrent Tabs, Headless Fleet, Docker

Mehrere Tabs parallel

Da nodriver nodriver async ist, können Sie mehrere Tabs in einem Browser parallel steuern. Das ist ressourceneffizienter als mehrere Browser-Prozesse:

import nodriver as uc
import asyncio

async def scrape_tab(browser, url, proxy_config):
    tab = await browser.get(url)
    await tab.send(uc.cdp.fetch.enable(handle_auth_requests=True))
    
    async def on_auth(event):
        await tab.send(uc.cdp.fetch.continue_with_auth(
            request_id=event.request_id,
            auth_challenge_response=proxy_config.auth_challenge_response()
        ))
    
    tab.add_handler(uc.cdp.fetch.AuthRequired, on_auth)
    await tab.wait_for("body")
    title = await tab.find("title")
    print(f"Title: {title.text}")
    await tab.close()

async def main():
    browser = await uc.start(
        headless=True,
        browser_args=["--proxy-server=http://gate.proxyhat.com:8080"]
    )
    
    urls = [
        "https://example.com/page1",
        "https://example.com/page2",
        "https://example.com/page3",
    ]
    
    tasks = [scrape_tab(browser, url, ProxyHatConfig()) for url in urls]
    await asyncio.gather(*tasks, return_exceptions=True)
    
    browser.stop()

asyncio.run(main())

Headless Fleet in Docker

Für Produktionssysteme empfiehlt sich eine Container-basierte Architektur. Jeder Container führt eine nodriver-Instanz mit eigenem Proxy-Kontext. Mit 2 GB RAM pro Browser-Context und 100 gleichzeitigen Sessions benötigen Sie etwa 200 GB RAM — oder Sie verteilen die Last über mehrere Worker.

Ein einfaches Dockerfile für nodriver:

FROM python:3.11-slim

RUN apt-get update && apt-get install -y \
    chromium \
    chromium-driver \
    fonts-liberation \
    libnss3 \
    libxss1 \
    && rm -rf /var/lib/apt/lists/*

ENV CHROME_PATH=/usr/bin/chromium

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY scraper.py .

CMD ["python", "scraper.py"]

Per-Context Proxy-Zuweisung und Graceful Shutdown

Für Multi-Proxy-Setups können Sie jedem Browser-Kontext einen anderen Proxy zuweisen. In nodriver entspricht jeder Tab einem Browser-Kontext. Sie können verschiedene Proxy-Session-IDs verwenden, um verschiedene IPs zu rotieren:

async def scrape_with_rotation(urls):
    browser = await uc.start(
        headless=True,
        browser_args=["--proxy-server=http://gate.proxyhat.com:8080"]
    )
    
    results = []
    for i, url in enumerate(urls):
        # Neue Session-ID = neue IP
        proxy = ProxyHatConfig(
            country="US",
            session_id=f"rot-{i:04d}"
        )
        try:
            data = await scrape_tab(browser, url, proxy)
            results.append({"url": url, "status": "ok", "data": data})
        except Exception as e:
            results.append({"url": url, "status": "error", "error": str(e)})
    
    browser.stop()
    return results

Verwenden Sie try/finally oder einen Context-Manager, um sicherzustellen, dass browser.stop() immer aufgerufen wird — auch bei Ausnahmen. Ein offengebliebener Chrome-Prozess verbraucht typischerweise 300-500 MB RAM und blockiert den CDP-Port.

Wann Sie keinen Browser benötigen: HTTP mit curl_cffi

Nicht jedes Scraping-Szenario erfordert einen vollständigen Browser. Wenn die Zielseite keine JavaScript-Ausführung für die Datenladung benötigt, ist ein HTTP-Client deutlich schneller und billiger. nodriver ist ideal für Cloudflare-Turnstile, JavaScript-Rendering und interaktive Seiten — aber für einfache API-Calls oder statische HTML-Seiten ist es Overkill.

Eine leichtgewichtige Alternative ist curl_cffi, das TLS-Fingerprinting nutzt und mit ProxyHat kompatibel ist:

from curl_cffi import requests

proxy = "http://user-country-DE-session-001:password@gate.proxyhat.com:8080"

response = requests.get(
    "https://httpbin.org/ip",
    proxies={"http": proxy, "https": proxy},
    impersonate="chrome120"
)
print(response.json())

Dieser Ansatz verbraucht etwa 50 MB RAM pro Request und ist 10-20x schneller als ein vollständiger Browser. Für nodriver web scraping ist der Browser nur dann gerechtfertigt, wenn die Zielseite aktiv JavaScript ausführt, um Inhalte zu laden oder Anti-Bot-Challenges zu bestehen.

Kriterium nodriver (Browser) curl_cffi (HTTP)
RAM pro Session 300-500 MB ~50 MB
Latenz 2000-5000 ms 200-800 ms
Cloudflare Turnstile Ja (mit residential Proxy) Nein
JavaScript-Rendering Ja Nein
Skalierbarkeit ~50-100 concurrent ~1000+ concurrent

Häufige Fehler und Edge Cases

1. Proxy-Authentifizierung vergessen

Der häufigste Fehler: --proxy-server wird gesetzt, aber die Authentifizierung fehlt. Chrome zeigt dann einen ERR_PROXY_AUTH_UNSUPPORTED-Fehler. Die Lösung ist der CDP Fetch.authRequired-Handler wie oben gezeigt.

2. Headless-Modus ohne Stealth-Anpassungen

Headless Chrome hat andere Eigenschaften als Headful Chrome — z.B. fehlt navigator.plugins. nodriver patcht einige davon, aber nicht alle. Testen Sie immer mit Fingerprint-Test-Seiten, um sicherzustellen, dass Ihr Setup nicht auffällt.

3. Zu viele gleichzeitige Tabs

Mehr als 10-15 Tabs pro Browser-Prozess führen zu Speicherdruck und CDP-Timeouts. Skalieren Sie horizontal mit mehreren Browser-Instanzen, nicht vertikal mit unendlich vielen Tabs. Bei 100 concurrent Sessions sollten Sie mindestens 4-8 Browser-Instanzen verteilen.

4. Fehlendes graceful shutdown

Wenn browser.stop() nicht aufgerufen wird, bleiben Chrome-Prozesse als Zombies zurück. Verwenden Sie immer try/finally oder ein async with-Pattern.

Key Takeaways

  • nodriver entfernt die WebDriver-Schicht und kommuniziert direkt über CDP — das eliminiert navigator.webdriver und andere Erkennungsvektoren.
  • Die nodriver async-API nutzt await und Event-Hooks statt synchroner WebDriver-Waits. Das ermöglicht echte Nebenläufigkeit mit mehreren Tabs.
  • Proxy-Konfiguration erfolgt über --proxy-server= und CDP Fetch.authRequired-Handler, da Chrome keine Proxy-Authentifizierung über CLI-Argumente unterstützt.
  • Residential Proxies sind unverzichtbar — selbst der beste Browser-Fingerprint nützt nichts mit einer Datacenter-IP. ProxyHat residential IPs bieten über 90 Länder und Sticky Sessions.
  • Für einfache HTTP-Anfragen ohne JavaScript-Rendering ist curl_cffi 10-20x schneller und verbraucht 10x weniger RAM.
  • Skalierung: Container-basierte Architektur mit 2 GB RAM pro Browser-Context, try/finally für graceful shutdown.

Fazit

Scraping mit nodriver ist der natürliche nächste Schritt für Python-Entwickler, die undetected-chromedriver ersetzen möchten. Die CDP-basierte Architektur ist robuster gegen Bot-Erkennung, die asynchrone API ermöglicht effiziente Skalierung, und die Kombination mit ProxyHat residential Proxies sorgt für die nötige IP-Diversität. Beginnen Sie mit einem einzelnen Tab und Proxy, validieren Sie gegen Ihre Zielseite, und skalieren Sie dann schrittweise. Die ProxyHat Preise bieten flexible Optionen für jede Skalierungsstufe.

Weitere Ressourcen: Web Scraping Use Cases, SERP Tracking, und die ProxyHat Dokumentation.

Häufig gestellte Fragen

Was ist Scraping mit nodriver?

Scraping mit nodriver ist eine Methode der Web-Automatisierung, die auf dem Chrome DevTools Protocol (CDP) statt auf Selenium/WebDriver basiert. nodriver ist der Nachfolger von undetected-chromedriver und kommuniziert direkt über Websockets mit Chrome. Dadurch entfällt das navigator.webdriver-Flag, was die Erkennung durch Anti-Bot-Systeme wie Cloudflare deutlich erschwert. Die API ist vollständig asynchron und nutzt Python await/async-Syntax.

Warum ist Scraping mit nodriver wichtig für Proxy-Nutzer?

nodriver allein macht einen Browser schwer erkennbar, aber ohne den richtigen Proxy bleibt die IP-Adresse ein Schwachpunkt. Datacenter-IPs werden von Cloudflare und Imperva über AS-Nummern identifiziert und oft blockiert. Die Kombination von nodriver mit residential Proxies wie ProxyHat ist essenziell, da residential IPs als reguläre Endnutzer-Verbindungen erscheinen. Mit Sticky Sessions können Sie zudem Session-Konsistenz wahren, was viele geschützte Seiten erwarten.

Welcher Proxy-Typ funktioniert am besten für Scraping mit nodriver?

Residential Proxies sind die beste Wahl für Scraping mit nodriver. Sie verwenden IPs von echten ISPs, die an Privathaushalte vergeben werden, und werden daher seltener von Anti-Bot-Systemen blockiert. Datacenter-IPs haben Block-Raten von 40-60% bei Cloudflare-geschützten Seiten, während residential IPs typischerweise unter 5% liegen. Mobile Proxies sind eine Alternative für höchste Stealth-Anforderungen, sind aber teurer. ProxyHat bietet residential Proxies mit Geo-Targeting für über 90 Länder und Sticky Sessions über die Session-ID im Benutzernamen.

Wie vermeidet man Blocks beim Scraping mit nodriver?

Um Blocks zu vermeiden, sollten Sie mehrere Maßnahmen kombinieren: Verwenden Sie residential Proxies mit IP-Rotation über verschiedene Session-IDs, setzen Sie --disable-blink-features=AutomationControlled als Browser-Argument, begrenzen Sie die Anfrage-Rate pro IP, und nutzen Sie Sticky Sessions für Seiten, die Session-Konsistenz erwarten. Außerdem sollten Sie nicht mehr als 10-15 Tabs pro Browser-Prozess öffnen und bei Blockaden die Session-ID wechseln, um eine neue IP zu erhalten. Ein graceful shutdown mit try/finally verhindert Zombie-Prozesse.

Kann man nodriver mit SOCKS5 Proxies verwenden?

Ja, nodriver unterstützt SOCKS5-Proxies über den --proxy-server Browser-Parameter mit dem socks5://-Schema. ProxyHat bietet SOCKS5 auf Port 1080. Die Konfiguration lautet dann --proxy-server=socks5://gate.proxyhat.com:1080. Die Authentifizierung erfolgt wie bei HTTP-Proxies über den CDP Fetch.authRequired-Handler, der Benutzername und Passwort bereitstellt. Beachten Sie, dass SOCKS5 keinen HTTP-Caching unterstützt, was die Latenz minimal erhöhen kann.

Bereit loszulegen?

Residential-, ISP- und Mobile-Proxys in über 148 Ländern. Erstelle ein kostenloses Konto.

Kostenloses Konto erstellen
← Zurück zum Blog