Captcha in Patchright lösen, ohne die Stealth-Tarnung zu verlieren

Ein Captcha-Ablauf in Patchright besteht aus denselben drei Zügen wie überall sonst: den sitekey lesen, ihn an einen Löser schicken, das Token zurück in die Seite schreiben. Beim dritten Zug überrascht diese Bibliothek viele. Patchright führt Ihr JavaScript standardmäßig in einem isolierten Kontext aus, und ein isolierter Kontext teilt sich zwar das DOM der Seite, nicht aber das JavaScript-Global der Seite. Der Schreibvorgang für das Token kommt also an, die Textarea enthält den Wert tatsächlich, und der eigene Callback der Website läuft trotzdem nie, weil er in dem Scope, in dem Ihr Code ausgeführt wird, gar nicht existiert. Ein einziges zusätzliches Argument behebt das.
Was Sie brauchen
- Python 3.10 oder neuer, oder Node.js, wenn Sie das JavaScript-Paket bevorzugen. Patchright wird für beide Runtimes ausgeliefert und ist jeweils ein Drop-in-Ersatz für Playwright.
- Ein Chromium-Browser, der über den eigenen Installer von Patchright heruntergeladen wurde. Firefox und WebKit sind nicht gepatcht und werden nicht unterstützt.
- Die Seiten-URL des geschützten Formulars. Den sitekey lesen Sie zur Laufzeit aus.
- CapSkip im Lokal-Modus, wenn Skript und Löser auf derselben Maschine liegen, oder im Server-Modus, wenn nicht. Beides ist beschrieben unter Verbindungseinstellungen.
# pip install patchright pip install -U capskip patchright # Pulls the browser. Real Chrome is the recommended channel, # and the maintainers say so explicitly. patchright install chrome
Was Patchright tatsächlich ändert
Patchright ist Playwright, bei dem die offensichtlichen Automatisierungs-Verräter entfernt wurden. Der wichtigste Patch: Es ruft niemals Runtime.enable auf, das lauteste einzelne Signal, das eine gewöhnliche Playwright-Sitzung von sich gibt, und es erreicht das, indem es Ihre Skripte stattdessen in isolierten ExecutionContexts ausführt. Außerdem deaktiviert es die Console API vollständig, setzt das Flag, das navigator.webdriver verbirgt, und lässt mehrere Playwright-Voreinstellungen weg, die eine Sitzung als automatisiert kennzeichnen: das Automatisierungs-Flag selbst, das Übergehen des Popup-Blockers, die Sperre für Komponenten-Updates sowie die Schalter, die Standard-Apps und Erweiterungen deaktiviert haben.
Zwei davon haben direkte Folgen für die Captcha-Arbeit, und über beide stolpert man leicht.
Dass die Konsole aus ist, bedeutet: Nichts, was Sie innerhalb der Seite loggen, erreicht Sie. Die Konsolenfunktionalität arbeitet in Patchright überhaupt nicht, die übliche Angewohnheit, eine Log-Zeile in einen evaluate-Aufruf zu setzen und sie vom Treiber aus zu lesen, ist hier also tot. Geben Sie stattdessen einen Wert aus dem Aufruf zurück. Das ist ohnehin die bessere Praxis, und es ist die einzige Option, die Sie haben.
Die wieder aktivierten Erweiterungen sind die freundlichere Folge. Playwright startet normalerweise mit deaktivierten Erweiterungen, und Patchright entfernt diesen Schalter, sodass eine Browsererweiterung, die in ein persistentes Profil geladen wurde, tatsächlich läuft. Wenn Sie diesen Code lieber gar nicht schreiben möchten, übernimmt die CapSkip-Browsererweiterung das Widget in der Seite, und Sie bedienen das Formular, als hätte ein Mensch es bestanden.
Eine weitere Fähigkeit ist erwähnenswert: Patchright greift mit gewöhnlichen Locators und XPaths in geschlossene Shadow Roots hinein. Widgets, die ihr Markup hinter einem geschlossenen Root verstecken, sind ohne jede Sonderbehandlung ansprechbar.
Schritt 1: so starten, wie es die Maintainer empfehlen
Die Tarnung von Patchright hängt ebenso von der Startkonfiguration ab wie von den Patches. Das dokumentierte Setup ist ein persistenter Kontext auf dem echten Chrome-Channel, mit sichtbarem Fenster und ohne Viewport-Override, und ganz ohne eigenen User-Agent oder eigene Header. Die letzten beiden Punkte sind wichtig: Ein von Hand gesetzter User-Agent widerspricht dem übrigen Fingerprint und macht die ganze Arbeit zunichte.
# pip install patchright
from patchright.sync_api import sync_playwright
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
user_data_dir="C:\\profiles\\scraper",
channel="chrome",
headless=False,
no_viewport=True,
# Do not set user_agent or extra headers here.
)
page = context.new_page()
page.goto("https://example.com/page-with-recaptcha")Beachten Sie das sichtbare Fenster. Headless ist der Punkt, an dem das meiste Erkennungsbudget verbraucht wird, und die empfohlene Konfiguration nutzt es nicht. Unter Windows bedeutet das: Das Konto, das das Skript ausführt, braucht eine interaktive Desktop-Sitzung. Das sollten Sie einplanen, bevor Sie das Ganze auf einen Server stellen.
Schritt 2: Den Sitekey von der Seite lesen
Der sitekey steht im Host-Dokument, nicht im iframe des Widgets, und ein einfacher Locator liest ihn aus. Locators laufen über das Browser-Protokoll und nicht durch irgendeinen Ausführungskontext, an diesem Schritt ändert der isolierte Kontext also nichts.
# Locators auto-wait, so this doubles as a wait condition
# for a widget that renders late.
holder = page.locator("div.g-recaptcha")
holder.wait_for(state="attached", timeout=15000)
sitekey = holder.get_attribute("data-sitekey")
print(sitekey) # 6LxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxManche Websites legen den Key gar nicht auf die Host-Seite und übergeben ihn nur in der iframe-URL des Widgets. Lesen Sie ihn in diesem Fall aus dem Query-String aus und prüfen Sie ihn, bevor Sie eine Lösung dafür verbrauchen, denn ein leerer Wert wandert den ganzen Weg bis zum Löser und kommt zurück als ERROR_GOOGLEKEY, weit entfernt von dem Lesevorgang, der tatsächlich fehlgeschlagen ist.
# Fallback: the k= parameter on the anchor iframe.
from urllib.parse import urlparse, parse_qs
src = page.locator("iframe[src*='recaptcha/api2/anchor']").get_attribute("src")
sitekey = parse_qs(urlparse(src).query)["k"][0]Schritt 3: auf der eigenen Maschine lösen
Das Python SDK spricht mit CapSkip auf Port 8080 und gibt das Token als einfachen String zurück. Eine Methode deckt reCAPTCHA v2, Invisible, Enterprise und v3 ab, wobei die Varianten als Optionen übergeben werden statt über getrennte Aufrufe.
# pip install capskip from capskip import CapSkip solver = CapSkip(host="127.0.0.1", port=8080) # Invisible v2 takes invisible=1, Enterprise takes enterprise=1, # and v3 takes version="v3" with an action. result = solver.recaptcha(sitekey=sitekey, url=PAGE_URL) token = result["code"] # the g-recaptcha-response value
Turnstile und GeeTest haben eigene Methoden und dieselbe Form. Turnstile gibt zusätzlich den User-Agent zurück, mit dem gelöst wurde, und eine Challenge-Seite weist das Token ab, wenn dieser User-Agent nicht mitgeschickt wird. Senden Sie ihn nur mit der Formularanfrage. Geben Sie ihn nicht an den Browser-Launcher weiter, denn ein von Hand gesetzter User-Agent ist genau das, wovon die Stealth-Konfiguration abrät. Die vollständigen Parameterlisten stehen in der CapSkip-API-Dokumentation.
Schritt 4: das Token dorthin schreiben, wo die Seite es nutzen kann
Hier kommt der Teil, der für diese Bibliothek spezifisch ist. Die Response-Textarea ist mit display:none versteckt, es kann also nichts hineingetippt werden, und der Wert muss per JavaScript zugewiesen werden. Diese Zuweisung funktioniert aus dem isolierten Kontext heraus, weil das DOM geteilt wird. Bauen Sie den String mit json.dumps statt mit einem f-String, denn ein JSON-String-Literal ist zugleich ein gültiges JavaScript-String-Literal, samt Anführungszeichen und Escaping.
# A DOM write is fine from the isolated context.
import json
page.evaluate(
"document.getElementById('g-recaptcha-response').value = "
+ json.dumps(token)
)
page.click("button[type=submit]")Damit sind die Websites abgedeckt, deren Formular die Textarea beim Absenden liest. Viele Websites tun das nicht. Sie registrieren beim Widget einen Erfolgs-Callback und sehen die Textarea überhaupt nie an, das Token muss also an eine Funktion übergeben werden, die die Seite definiert hat. Der isolierte Kontext von Patchright hat sein eigenes JavaScript-Global, und das heißt: Die Funktionen der Seite und die reCAPTCHA-Client-Konfiguration fehlen dort schlicht. Der Aufruf scheitert mit einem Reference Error, und daran ändert auch beliebig häufiges Wiederholen nichts.
Die Antwort von Patchright ist ein zusätzliches Argument. Die Methoden evaluate, evaluate_handle und evaluate_all nehmen alle isolated_context entgegen, der Standardwert ist True, und wenn Sie ihn auf False setzen, läuft das Skript in der Main World der Seite selbst.
# The main world is where the page's own globals live.
page.evaluate(
"token => window.onRecaptchaSuccess(token)",
token,
isolated_context=False,
)Lesen Sie den Callback-Namen aus dem Markup der Seite aus, statt ihn zu raten. Verwenden Sie die Main World nur für die Injektion und für nichts sonst: Code, der dort läuft, ist für die Website sichtbar, und genau deshalb ist der isolierte Kontext die Voreinstellung. Beide Absende-Varianten sind darunter weiterhin ganz gewöhnliches reCAPTCHA v2, und beide beschreibt die reCAPTCHA-v2-Solver-Seite.
Vollständiges lauffähiges Beispiel
Alles Bisherige in einem Skript. Der Löser wird einmal erzeugt, der sitekey wird geprüft, bevor eine Lösung dafür verbraucht wird, und das Token wird überprüft, indem eine Länge zurückgegeben wird, statt innerhalb der Seite zu loggen.
# pip install capskip patchright
import json
from patchright.sync_api import sync_playwright
from capskip import CapSkip
PAGE_URL = "https://example.com/page-with-recaptcha"
solver = CapSkip(host="127.0.0.1", port=8080)
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
user_data_dir="C:\\profiles\\scraper",
channel="chrome",
headless=False,
no_viewport=True,
)
page = context.new_page()
page.goto(PAGE_URL)
holder = page.locator("div.g-recaptcha")
holder.wait_for(state="attached", timeout=15000)
sitekey = holder.get_attribute("data-sitekey")
if not sitekey:
raise RuntimeError("Widget found but data-sitekey was empty.")
token = solver.recaptcha(sitekey=sitekey, url=PAGE_URL)["code"]
page.evaluate(
"document.getElementById('g-recaptcha-response').value = "
+ json.dumps(token)
)
length = page.evaluate(
"document.getElementById('g-recaptcha-response').value.length"
)
print(length) # 0 means the injection did not land
page.click("button[type=submit]")
page.wait_for_load_state("networkidle")
context.close()Den Löser auf einer anderen Maschine betreiben
Patchright landet meist auf einer größeren Maschine als der, auf der Sie das Skript geschrieben haben, und das empfohlene Setup mit sichtbarem Fenster drängt einen recht schnell zu einer eigenen VM. Der Löser muss nicht mitumziehen.
CapSkip hat zwei Verbindungsmodi. Local bindet an 127.0.0.1 und antwortet nur diesem Gerät, was richtig ist, solange Sie das Skript auf der Maschine schreiben, auf der die App läuft. Server bindet an Ihre Netzwerk- oder öffentliche IP, sodass eine Scraping-VM, eine zweite Workstation oder ein ganzer Pool davon dieselbe Windows-Maschine über die API aufrufen. Eine statische öffentliche IP hält diese Adresse stabil. Im Code ändert sich nichts außer dem Host, und an den Kosten ändert sich ebenfalls nichts, denn es bleibt Hardware, die Ihnen gehört.
# Same SDK, same call. Only the host moves. solver = CapSkip(host="10.0.0.12", port=8080, apiKey="YOUR_API_KEY")
Aktivieren Sie die Schlüsselvalidierung, sobald der Löser auf einer Netzwerkadresse lauscht, und geben Sie jeder Maschine einen eigenen Schlüssel, damit sich einer widerrufen lässt, ohne die anderen anzutasten. Beide Modi finden Sie Schritt für Schritt erklärt in der CapSkip-Einrichtungsanleitung.
Häufige Fehler und was sie bedeuten
| Was Sie sehen | Ursache | Beheben |
|---|---|---|
| Die Textarea enthält das Token, aber das Formular scheitert | Die Seite verwendet einen Callback und liest die Textarea nie | Den Callback mit isolated_context=False aufrufen |
| Ein Reference Error, der eine Seitenfunktion nennt | Seiten-Globals existieren im isolierten Kontext nicht | Gleiche Lösung: diesen einen Aufruf in der Main World ausführen |
| Von einem Konsolen-Log in der Seite kommt nichts an | Patchright deaktiviert die Console API vollständig | Statt zu loggen einen Wert aus evaluate zurückgeben |
| Firefox oder WebKit verhält sich wie normales Playwright | Nur Chromium-Browser sind gepatcht | Den Chromium- oder Chrome-Channel verwenden |
| Trotz der Patches blockiert | Ein eigener User-Agent oder Header widerspricht dem Fingerprint | Entfernen Sie sie und nutzen Sie einen persistenten Kontext auf Chrome |
| ERROR_GOOGLEKEY | Ein leerer sitekey ist beim Löser angekommen | Prüfen Sie den Wert, bevor Sie recaptcha aufrufen |
| NetworkException | CapSkip läuft nicht, oder der Host ist falsch | Die App starten oder host auf die Serveradresse zeigen lassen |
| TimeoutException | Die Lösung hat recaptchaTimeout überdauert | Erhöhen Sie ihn über den Standardwert von 300 Sekunden |
FAQ
Kann ich mein Playwright-Skript unverändert übernehmen?
Ändern Sie den Import, und das meiste läuft. Locators, Kontexte, Routes, Waits und Navigation verhalten sich wie zuvor. Drei Dinge brauchen einen zweiten Blick: jeder evaluate-Aufruf, der etwas anfasst, das die Seite definiert hat, denn er braucht jetzt das zusätzliche Argument; alles, was auf Konsolenausgabe angewiesen war, denn die ist weg; und jedes Firefox- oder WebKit-Ziel, denn das ist ungepatcht. Das größere Playwright-Bild finden Sie auf der Playwright-Captcha-Solver-Seite.
Werde ich erkannt, wenn ich die Main World benutze?
Im Prinzip ja, denn die Seite kann Code sehen, der dort läuft. In der Praxis besteht die Exposition aus einem einzigen Funktionsaufruf von wenigen Mikrosekunden, und er sieht genauso aus wie das, was das Skript des Widgets selbst tut, wenn ein Mensch die Challenge besteht. Halten Sie alles andere im isolierten Kontext, erledigen Sie die Injektion in einem einzigen Aufruf statt in mehreren, und die Angriffsfläche bleibt klein.
Sollte ich das Turnstile-Widget anklicken, statt ein Token zu injizieren?
Manchmal. Eine Turnstile-Checkbox im Managed-Modus kann von allein durchgehen, wenn der Browser überzeugend wirkt, und genau dafür ist Patchright da. Es lohnt sich also, zuerst den Klick zu versuchen und erst danach auf eine Lösung zurückzufallen. Ein Klick auf eine reCAPTCHA-Checkbox öffnet nur eine Bild-Challenge, dort ist also nichts zu gewinnen. Für die Widget-Seite zuständig ist die Cloudflare-Turnstile-Löser-Seite.
Mein Crawler läuft unter Linux. Wohin kommt CapSkip?
Auf eine Windows-Maschine, die Sie kontrollieren, mit eingeschaltetem Server-Modus. Der Linux-Rechner ruft sie dann über die API auf wie jeden anderen internen Dienst, Patchright und der Löser müssen sich also weder ein Betriebssystem noch ein Netzwerksegment teilen. Richten Sie das host-Argument auf diese Adresse, aktivieren Sie die Schlüsselvalidierung und geben Sie dem Crawler einen eigenen Schlüssel.
Die Kurzfassung
Installieren Sie Patchright, starten Sie einen persistenten Kontext auf dem Chrome-Channel mit sichtbarem Fenster und ohne eigenen User-Agent, lesen Sie den sitekey mit einem gewöhnlichen Locator und lösen Sie ihn gegen CapSkip auf 127.0.0.1:8080. Schreiben Sie das Token direkt aus dem voreingestellten isolierten Kontext in die Textarea und greifen Sie nur dann zu isolated_context=False, wenn die Website einen aufgerufenen Callback erwartet. Mehr zum Rest der Python-Landschaft, einschließlich Selenium und Playwright, finden Sie auf der Python-Captcha-Solver-Seite.
Eines sollten Sie wissen, bevor Sie einen Crawl hochskalieren. CapSkip ist ein unbegrenzter Captcha-Löser der auf Hardware läuft, die Ihnen bereits gehört, und deshalb kostet das Wiederholen von tausend Seiten genau so viel wie das Wiederholen von zehn.
