← Alle Artikel

Warum Dein Playwright-Browser Challenges Bekommt und Dein Echtes Chrome Nicht

Du schickst Playwright auf eine Seite und bekommst einen “Just a moment…”-Bildschirm. Er dreht sich. Manchmal erscheint eine Checkbox. Manchmal lädt die Seite neu und landet wieder in derselben Challenge. Irgendwann gibst du auf, öffnest exakt dieselbe URL in dem Chrome, das du jeden Tag benutzt, auf demselben Laptop im selben WLAN, und die Seite ist in einer Sekunde da. Keine Checkbox.

Gleiche IP. Gleicher Rechner. Gleiche URL. Geändert hat sich nur der Browser. Genau das ist der Hinweis: Meistens reagiert das Anti-Bot-System nicht auf dein Netzwerk, sondern auf den Browser, den dein Automatisierungstool gestartet hat.

Woran Du das Problem Erkennst

Das Problem folgt einem erkennbaren Muster:

  • Eine Challenge-Seite, die sich im automatisierten Browser nie von selbst auflöst, im normalen Chrome aber passiv durchläuft (oder gar nicht erst erscheint).
  • Eine Checkbox-Challenge, die nach dem Klick deines Skripts zu einer weiteren Challenge oder zu einer endgültigen “Access denied”-Seite wird.
  • Ein Scraper, der auf deinem Laptop mit sichtbarem Fenster funktioniert und auf einem Server headless scheitert.
  • Ein Setup, das monatelang lief und nach einem Framework-Update plötzlich scheitert, ohne dass du am Code etwas geändert hast.

Wenn auch dein normales Chrome aus demselben Netz blockiert wird, kannst du hier aufhören zu lesen: Dann liegt dein Problem bei der IP-Reputation oder der Request-Rate, nicht beim Browser. Der Rest dieses Artikels behandelt den Fall, in dem der Browser den Unterschied macht.

Dein Automatisierungs-Browser Ist Nicht das Chrome, das Du Benutzt

Wenn du pip install playwright und playwright install ausführst, bekommst du nicht das Google Chrome von deinem Desktop. Du bekommst einen Browser, der für Automatisierung gebaut ist:

  • Playwright ab Version 1.57 läuft auf Chrome for Testing-Builds statt auf Open-Source-Chromium. Läufe mit sichtbarem Fenster nutzen Chrome for Testing, headless Läufe standardmäßig chrome-headless-shell.
  • Puppeteer ab v20 lädt Chrome for Testing herunter und seit v21.6 zusätzlich chrome-headless-shell für headless Läufe.

Google beschreibt Chrome for Testing als eine Variante, die “ausschließlich für Browser-Automatisierung und Tests” geschaffen wurde. Sie ist auf eine Version festgelegt und aktualisiert sich nicht automatisch, genau das, was eine Testsuite braucht.

chrome-headless-shell ist eine andere Geschichte. Es ist die alte Headless-Implementierung, die Chromes Dokumentation als “einen schlanken Wrapper um Chromiums //content-Modul” beschreibt, und sie gibt sich historisch mit HeadlessChrome im User-Agent zu erkennen. Sie ist schnell und ideal für Screenshots. Sie ist aber nicht der vollständige Browser.

Dein Desktop-Chrome ist der offizielle Marken-Build, den ein großer Teil der echten Nutzer verwendet, und er hält sich automatisch aktuell. Für ein Anti-Bot-System, das bewertet, wie normal ein Besucher aussieht, ist der Abstand zwischen “dem Browser, den alle nutzen” und “einem Build für Automatisierung” ein Signal, das sich zu lesen lohnt.

Warum Anti-Bot-Systeme den Unterschied Erkennen

Moderne Challenges sind keine Rätsel. Cloudflare schreibt, dass Turnstile “eine Reihe kleiner, nicht interaktiver JavaScript-Challenges ausführt, um Signale über den Besucher oder die Browser-Umgebung zu sammeln”, darunter das “Abfragen von Web-APIs” und Challenges “zur Erkennung von Browser-Eigenheiten und menschlichem Verhalten”. Andere Anbieter arbeiten ähnlich. An diesen Stellen fallen Automatisierungs-Builds auf:

Automatisierungs-Flags

Laut MDN setzt Chrome navigator.webdriver auf true, wenn es mit --enable-automation, --headless oder --remote-debugging-port=0 gestartet wird. Automatisierungs-Frameworks starten Chrome mit einer Reihe von Standard-Switches, und --enable-automation ist einer der Klassiker. Er löst auch die Leiste “Chrome wird von automatisierter Testsoftware gesteuert” aus.

Unterschiede im Build und bei Funktionen

Die Playwright-Dokumentation weist selbst darauf hin, dass “Chromium nicht alle Codecs hat, die Google Chrome oder Microsoft Edge mitliefern”. Einer Headless Shell fehlen noch größere Teile des echten Browsers. Jede Prüfung, die fragt “Was kann dieser Browser tatsächlich?” und das mit “Was behauptet er zu sein?” vergleicht, kann solche Lücken finden.

Versionsdrift

Ein festgelegter Build hat jeden Tag dieselbe Version, während die echte Chrome-Population alle paar Wochen weiterzieht. Monate später meldet dein Scraper eine Browser-Version, die immer weniger echte Besucher noch nutzen.

Der Steuerkanal

Frameworks sprechen mit Chrome über das DevTools-Protokoll, und einige der Befehle, die sie standardmäßig verwenden, haben Nebenwirkungen, die ein Skript auf der Seite bemerken kann. Tools wie Patchright gibt es genau dafür, die bekanntesten davon zu vermeiden, etwa das Runtime.enable-Leck.

Die Umgebung um den Browser

Ein frisches Profil ohne Verlauf, ein fester Standard-Viewport, Server-Schriften und Software-Rendering in einem Container ohne GPU summieren sich. Keiner dieser Punkte ist allein entscheidend. Zusammen ergeben sie ein Bild, das nicht nach einem Menschen am Laptop aussieht.

Warum der Klick auf die Checkbox nicht hilft

Wenn die Checkbox erscheint, hat das System deine Umgebung bereits als unsicher eingestuft. Ein geskripteter Klick ist nur ein weiterer Datenpunkt, gemessen in derselben verdächtigen Umgebung. Deshalb führt der Klick oft zu einer härteren Challenge oder einer Sperre statt zum Durchlass. Wenn sich die Challenge nicht passiv auflöst, repariere zuerst die Umgebung.

So Diagnostizierst Du es Lokal

Rate nicht. Führe dieselbe Prüfung in jedem Browser-Setup aus und vergleiche. Dieses Skript startet den mitgelieferten Build und dein installiertes Chrome, jeweils mit sichtbarem Fenster und headless, und gibt aus, was ein Skript auf der Seite sehen kann:

from playwright.sync_api import sync_playwright

PROBE = """() => {
  const gl = document.createElement('canvas').getContext('webgl');
  const dbg = gl && gl.getExtension('WEBGL_debug_renderer_info');
  return {
    userAgent: navigator.userAgent,
    webdriver: navigator.webdriver,
    brands: navigator.userAgentData
      ? navigator.userAgentData.brands.map(b => `${b.brand} ${b.version}`)
      : null,
    h264: window.MediaSource
      ? MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E"')
      : null,
    webglRenderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : null,
    viewport: `${innerWidth}x${innerHeight}`,
  };
}"""

def probe(channel, headless):
    with sync_playwright() as p:
        browser = p.chromium.launch(channel=channel, headless=headless)
        page = browser.new_page()
        page.goto("https://example.com")
        result = page.evaluate(PROBE)
        browser.close()
        return result

for channel in (None, "chrome"):
    for headless in (True, False):
        label = f"{channel or 'bundled'} / {'headless' if headless else 'headed'}"
        print(label)
        for key, value in probe(channel, headless).items():
            print(f"  {key}: {value}")

Jede Zeile, die sich zwischen dem mitgelieferten Build und deinem echten Chrome unterscheidet, kann auch eine Website lesen. Dann kommt der eigentliche Test: Lade die geschützte Seite mit jedem der vier Setups aus demselben Netz. Die Kombination, die durchkommt, zeigt dir, welche Ebene du reparieren musst. Kommt keine durch, ist der Browser nicht dein Engpass.

So Behebst Du es

1. Nutze das Marken-Chrome

Beide Frameworks können das Chrome steuern, das du bereits installiert hast:

browser = p.chromium.launch(channel="chrome", headless=False)
const browser = await puppeteer.launch({ channel: 'chrome', headless: false });

Auf einem Rechner ohne Chrome installiert playwright install chrome Google Chrome am Standardort des Systems (eine vorhandene Installation wird ersetzt, also mach das auf Servern, nicht auf deinem Laptop). Als Bonus aktualisiert sich das Marken-Chrome auf Desktops automatisch, sodass du nicht mehr hinter der echten Nutzerbasis zurückfällst.

2. Mit sichtbarem Fenster laufen lassen, oder zumindest im echten Headless-Modus

Ein sichtbares Fenster ist die authentischste Option. Auf einem Linux-Server läuft das in einem virtuellen Display mit xvfb-run python scraper.py. Wenn du headless laufen musst, nutze channel="chrome" mit headless=True: Seit Playwright 1.49 verwendet der Chrome-Kanal Chromes neuen Headless-Modus, den die Dokumentation “den echten Chrome-Browser” nennt, statt der Headless Shell.

3. Entferne die auffälligsten Automatisierungs-Flags

Entferne --enable-automation und schalte das Blink-Feature AutomationControlled ab. Damit fallen die billigsten Prüfungen weg. Unsichtbar wirst du dadurch nicht.

4. Verwende ein persistentes Profil

Ein brandneues, leeres Profil bei jedem Lauf sieht jedes Mal wie ein Erstbesucher aus. Ein persistentes Profil behält Cookies, Cache und Website-Speicher zwischen den Läufen, genau wie dein eigener Browser. Kombiniere das mit den Ideen zur Cookie-Wiederverwendung aus unserem Artikel über Session-Persistenz.

5. Täusche nichts vor, was du nicht belegen kannst

Ein eigener User-Agent auf einem anderen Browser-Build erzeugt einen Widerspruch zwischen dem, was der Browser behauptet, und dem, was er kann. Lass das echte Chrome sich selbst ausweisen.

6. Lass die Challenge sich selbst auflösen

Warte, bis die Challenge verschwindet, statt sie anzuklicken. Alles zusammen:

from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout

URL = "https://example.com/protected-page"

with sync_playwright() as p:
    context = p.chromium.launch_persistent_context(
        user_data_dir="./chrome-profile",
        channel="chrome",
        headless=False,
        no_viewport=True,
        ignore_default_args=["--enable-automation"],
        args=["--disable-blink-features=AutomationControlled"],
    )
    page = context.pages[0] if context.pages else context.new_page()
    page.goto(URL, wait_until="domcontentloaded")

    try:
        # Passe diese Prüfung an die Challenge an, die du tatsächlich siehst.
        page.wait_for_function(
            "() => !document.title.toLowerCase().includes('just a moment')",
            timeout=30_000,
        )
        html = page.content()
    except PlaywrightTimeout:
        html = None
        print("Challenge did not clear: check the environment, IP and request rate")

    context.close()

7. Zieh einen gepatchten Treiber in Betracht

Wenn du diese Korrekturen fertig gebündelt willst, ist Patchright ein direkter Ersatz für Playwright, der bekannte Lecks wie Runtime.enable und die Automatisierungs-Flags patcht. Das empfohlene Setup ist dasselbe wie oben: ein persistenter Kontext, channel="chrome", sichtbares Fenster, kein fester Viewport und kein eigener User-Agent. nodriver, der Nachfolger von undetected-chromedriver, geht einen ähnlichen Weg und steuert dein installiertes Chrome ohne WebDriver.

Der Browser Ist Nur Eine Ebene

Einen Browser zu reparieren beseitigt einen Grund, dich herauszufordern. Die anderen bleiben. Ein perfektes echtes Chrome, das tausende Requests pro Stunde von einer einzigen IP schickt, verbrennt trotzdem die Reputation dieser IP, und ein reiner HTTP-Client hat weiterhin das Problem mit dem TLS-Fingerprint, egal mit welchem Browser du die Cookies geholt hast. Die vollständige Checkliste für einen der häufigsten Schutzmechanismen findest du unter Wie scrape ich eine durch Cloudflare geschützte Website ohne 403. Und prüfe immer den Inhalt, nicht den Statuscode: Eine Challenge-Seite kann als 200-Antwort ankommen.

Respektiere die Bedingungen der Website und halte deine Request-Rate vernünftig. Höflich zu sein ist auch der günstigste Weg, nicht blockiert zu werden.

FAQ

Ist Chrome for Testing dasselbe wie Google Chrome?

Es entsteht im selben Release-Prozess und ist laut Google “so nah wie möglich am regulären Chrome”, aber es ist eine eigene Variante, die nur für Automatisierung und Tests gedacht ist. Es aktualisiert sich nicht selbst und ist nicht der Build, den gewöhnliche Besucher verwenden.

Macht navigator.webdriver auf false Playwright unauffindbar?

Nein. Es entfernt eine billige Prüfung. Build-Unterschiede, der DevTools-Steuerkanal, die Umgebung, deine IP und dein Verhalten bleiben sichtbar. Betrachte es als Grundhygiene, nicht als Lösung.

Sollte mein Scraper die Turnstile-Checkbox automatisch anklicken?

Meist nicht als ersten Schritt. Die Checkbox erscheint, weil die Umgebung bereits unsicher wirkt, und ein geskripteter Klick in genau dieser Umgebung ändert das Urteil selten. Passe das Browser-Setup an, bis sich die Challenge passiv auflöst.

Kann ich das Marken-Chrome in Docker laufen lassen?

Ja. Installiere Google Chrome im Image (playwright install chrome kann das für dich erledigen) und lass es mit sichtbarem Fenster unter Xvfb laufen, oder headless mit channel="chrome", um den neuen Headless-Modus statt der Headless Shell zu bekommen.

Spar Dir die Browser-Wartung

Browser-Builds aktuell, Profile warm und Flags sauber zu halten ist echte Daueraufgabe, und sie bricht jedes Mal, wenn ein Framework oder ein Anti-Bot-System ein Update bekommt. Wenn du diese Zeit lieber in die Daten steckst, rendert ScrapeUnblocker die Seite für dich in einem echten Browser und liefert das HTML mit einer einzigen API-Anfrage. Die Dokumentation zeigt dir, wie du in wenigen Minuten deinen ersten Aufruf machst.

ScrapeUnblocker kostenlos testen

Über 95 % Erfolgsquote · ab 0,55 € pro 1.000 Aufrufe · 500 kostenlose Anfragen bei der Registrierung.

Kostenlos testen → Preise ansehen