Captcha in Camoufox mit Main World Eval lösen

camoufox captcha - How to Solve CAPTCHA in Camoufox Using Main World Eval

Ein Captcha-Schritt in Camoufox sieht aus wie jeder andere: den sitekey lesen, ihn an einen Löser schicken, das Token in die Seite schreiben. Beim dritten Zug ist Camoufox anders. Es führt das JavaScript, das Sie an evaluate übergeben, in einem isolierten Scope aus, den die Seite nicht sehen kann, und ein isolierter Scope kann das DOM der Seite nicht verändern. Ihr Schreibvorgang kehrt ohne Fehler zurück, die Textarea bleibt leer, und das Formular fällt bei der Validierung durch. Die Lösung besteht aus einer Startoption und einem zwei Zeichen langen Präfix.

Was Sie brauchen

  • Python 3.10 oder neuer. Camoufox 0.5 legt Playwright selbst fest, überlassen Sie die Auflösung also pip.
  • Der Camoufox-Browser, einmalig mit dem fetch-Befehl heruntergeladen. Es ist ein Firefox-Build, nicht Chromium.
  • 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.
# The geoip extra is optional and worth having if you use proxies.
pip install -U capskip "camoufox[geoip]"

# Downloads the browser itself. Run once per machine.
camoufox fetch

Warum die übliche Token-Injektion hier nichts bewirkt

Camoufox ist ein gepatchtes Firefox mit einem dünnen Python-Wrapper um Playwright. Die Wrapper-Klasse ist ein Playwright-Kontextmanager, Sie bekommen also ein ganz normales Playwright-Browser-Objekt zurück, und jeder Locator, jeder Klick und jede Navigation, die Sie bereits kennen, funktionieren unverändert. Das ist die gute Nachricht, und es ist der größte Teil der Bibliothek.

Die Ausnahme ist die Skriptausführung. Camoufox führt sämtliches JavaScript in einem isolierten Scope aus, der für die Seite unsichtbar ist, und genau dafür gibt es die Bibliothek: Eine Website kann die Automatisierung nicht dabei beobachten, wie sie an ihr herumstochert. Lesen ist davon nicht betroffen, denn die eigenen Locator-Methoden von Playwright laufen über das Browser-Protokoll statt über diesen Scope. Schreiben ist betroffen, und diesen Satz sollten Sie sich merken: Ein isolierter Scope kann das DOM nicht verändern. Eine Token-Zuweisung dort wird stillschweigend verworfen.

Die Antwort von Camoufox ist eine Hintertür in die Main World. Übergeben Sie beim Start main_world_eval und stellen Sie jedem Skript, das die echte Seite anfassen muss, mw: voran, dann läuft es im Scope der Seite selbst. Zwei Dinge gehören dazu. Die Website kann Code erkennen, der dort läuft, nutzen Sie es also für die Injektion und für sonst nichts. Und Sie können keine Element-Referenzen aus der Main World zurückgeben, nur Werte, die als JSON überleben.

Schritt 1: mit aktivierter Main World starten

Die Option ist standardmäßig aus und muss beim Start gesetzt werden. Es gibt keine Möglichkeit, sie später für einen einzelnen Aufruf einzuschalten.

# pip install camoufox[geoip]
from camoufox.sync_api import Camoufox

with Camoufox(main_world_eval=True, headless=True) as browser:
    page = browser.new_page()
    page.goto("https://example.com/page-with-recaptcha")

    # browser is a normal Playwright Browser from here on.
    print(page.title())

Zwei benachbarte Optionen sollten Sie kennen, bevor Sie weitergehen. humanize bewegt den Cursor auf einer menschlich wirkenden Bahn und braucht dafür bis zu etwa 1,5 Sekunden quer über das Fenster, was zählt, wenn Sie das Widget selbst anklicken, statt ein Token zu injizieren. Und disable_coop entfernt die Cross-Origin-Opener-Policy, damit Elemente in Cross-Origin-iframes, darunter die Checkbox von Cloudflare Turnstile, überhaupt anklickbar sind.

Schritt 2: Den Sitekey von der Seite lesen

Der sitekey steht im Host-Dokument, nicht im iframe des Widgets. Das Markup von Google legt ihn als data-sitekey-Attribut auf einen Container, und ein einfacher Playwright-Locator liest ihn aus. Ein Main-World-Präfix ist dafür nicht nötig, denn dieser Weg führt nie durch den isolierten Scope.

# Locators wait by default, 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)   # 6Lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Manche Websites geben den sitekey nie auf der Host-Seite preis und übergeben ihn nur in der iframe-URL des Widgets. Lesen Sie ihn in diesem Fall aus dem Query-String aus.

# 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]

Prüfen Sie den Wert, bevor Sie dafür eine Lösung verbrauchen. Ein leerer sitekey wandert den ganzen Weg bis zum Löser, und zurück kommt ERROR_GOOGLEKEY, weit weg von dem Locator, der tatsächlich fehlgeschlagen ist.

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 Sie diesen User-Agent nicht mitschicken. Vollständige Parameterlisten stehen in der CapSkip-API-Dokumentation.

Camoufox liefert auch eine async-Klasse mit, und AsyncCapSkip im Python SDK ist eine echte asyncio-Implementierung und kein Alias für die synchrone Variante. Kombinieren Sie beide, wenn Sie mehrere Kontexte gleichzeitig steuern, damit eine Lösung auf einer Seite die anderen nicht ausbremst.

Schritt 4: das Token in der Main World injizieren

Hier kommt der Teil, der für diesen Browser spezifisch ist. Die Antwort-Textarea ist mit display:none versteckt, kein Automatisierungswerkzeug kann also hineintippen, und Sie müssen den Wert per JavaScript zuweisen. Stellen Sie dem Skript mw: voran, damit es im Scope der Seite selbst läuft, und 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 Quoting und Escaping.

# The mw: prefix is what makes this write land.
import json

page.evaluate(
    "mw:document.getElementById('g-recaptcha-response').value = "
    + json.dumps(token)
)

page.click("button[type=submit]")

Beim Nachprüfen gibt es einen Haken. Sie können kein Element aus der Main World herausreichen, geben Sie also einen Wert zurück statt des Knotens. Eine Länge genügt und sagt Ihnen genau das, was Sie wissen wollen.

# Return a number, never the element itself.
length = page.evaluate(
    "mw:document.getElementById('g-recaptcha-response').value.length"
)
print(length)   # 0 means the injection did not land

Wenn die Website beim Absenden des Formulars einen Erfolgs-Callback definiert, statt die Textarea zu lesen, rufen Sie diesen Callback auf, nachdem Sie den Wert gesetzt haben. Das ist der Fall, in dem die Main World nicht nur bequem, sondern zwingend nötig ist, denn eine Funktion, die die Seite definiert hat, existiert im isolierten Scope überhaupt nicht. Lesen Sie den Funktionsnamen aus dem Markup der Seite selbst, statt ihn zu raten. Beide Einreichungsarten sind im Kern gewöhnliches reCAPTCHA v2, und beide beschreibt ausführlich die reCAPTCHA-v2-Solver-Seite.

Vollständiges lauffähiges Beispiel

Alles Bisherige in einem Skript. Der Löser wird einmal erzeugt, der Browser schließt sich am Ende des Blocks selbst, und der sitekey wird geprüft, bevor eine Lösung dafür verbraucht wird.

# pip install capskip camoufox[geoip]
import json
from camoufox.sync_api import Camoufox
from capskip import CapSkip

PAGE_URL = "https://example.com/page-with-recaptcha"

solver = CapSkip(host="127.0.0.1", port=8080)

with Camoufox(main_world_eval=True, headless=True) as browser:
    page = browser.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.")

    result = solver.recaptcha(sitekey=sitekey, url=PAGE_URL)
    page.evaluate(
        "mw:document.getElementById('g-recaptcha-response').value = "
        + json.dumps(result["code"])
    )

    page.click("button[type=submit]")
    page.wait_for_load_state("networkidle")
    print(page.url)   # where you land after submitting

Den Löser auf einer anderen Maschine betreiben

Camoufox landet meistens auf einer größeren Maschine als der, auf der Sie das Skript geschrieben haben, und dann ist ein Plattformdetail wichtig. headless als String virtual zu übergeben startet ein Xvfb-Display, und das ist eine reine Linux-Funktion: Unter Windows und macOS löst dieser Wert stattdessen einen Not-supported-Fehler aus. Einfaches headless funktioniert überall, nutzen Sie also den Boolean, sofern Sie nicht bewusst unter Linux arbeiten.

Der Löser muss nicht mit dem Browser mitreisen. CapSkip hat zwei Verbindungsmodi. Der Lokal-Modus bindet an 127.0.0.1 und antwortet nur diesem Gerät, und das ist die richtige Einstellung, solange Sie das Skript auf der Maschine schreiben, auf der die App läuft. Der Server-Modus bindet an Ihre Netzwerk- oder öffentliche IP, sodass eine Scraping-VM, eine zweite Workstation oder eine Linux-Maschine mit Camoufox dieselbe Windows-Maschine über die API aufruft. Eine statische öffentliche IP hält diese Adresse stabil. Am Code ändert sich nichts außer dem Host, und an den Kosten ändert sich ebenfalls nichts, denn es bleibt Ihre Hardware.

# 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 sehenUrsacheBeheben
Das Token ist gesetzt, aber das Formular schlägt weiterhin fehlDer Schreibvorgang lief im isolierten Scope und wurde verworfenMit main_world_eval starten und dem Skript mw: voranstellen
evaluate meldet einen nicht unterstützten RückgabewertAus der Main World wurde eine Knoten-Referenz zurückgegebenGeben Sie eine Länge oder einen String zurück statt des Elements
Die Callback-Funktion ist undefinedGlobale Variablen der Seite existieren im isolierten Scope nichtRufen Sie sie mit demselben Präfix aus der Main World auf
Die Turnstile-Checkbox lässt sich nicht anklickenSie sitzt in einem Cross-Origin-iframeMit disable_coop starten oder ein Token injizieren statt zu klicken
ERROR_GOOGLEKEYEin leerer sitekey ist beim Löser angekommenPrüfen Sie den Wert, bevor Sie recaptcha aufrufen
NetworkExceptionCapSkip läuft nicht, oder der Host ist falschDie App starten oder host auf die Serveradresse zeigen lassen
TimeoutExceptionDie Lösung hat recaptchaTimeout überdauertErhöhen Sie ihn über den Standardwert von 300 Sekunden
Virtuelles Display nicht unterstütztheadless wurde außerhalb von Linux auf virtual gesetztVerwenden Sie stattdessen headless=True

FAQ

Macht mich Main World Eval leichter erkennbar?

Alles, was in der Main World läuft, ist für die Seite sichtbar, im Prinzip also ja. In der Praxis besteht die Angriffsfläche aus einer einzigen Zuweisung, die Mikrosekunden dauert und genauso aussieht wie das, was das Skript des Widgets selbst tut, wenn ein Mensch die Challenge besteht. Halten Sie jedes andere Skript im isolierten Scope, erledigen Sie die Injektion in einem Aufruf statt in mehreren, und Sie geben nicht viel preis.

Kann ich meinen vorhandenen Playwright-Code weiterverwenden?

Fast vollständig. Der Launcher gibt einen echten Playwright-Browser zurück, Locators, Kontexte, Routen und Waits verhalten sich also wie immer. Zwei Dinge sollten Sie sich ansehen: jeden Aufruf, der über evaluate ins DOM schreibt und deshalb das Präfix braucht, und alles, was Chromium vorausgesetzt hat, denn hier läuft Firefox. Das größere Bild zu dieser Engine finden Sie auf der Playwright-Captcha-Solver-Seite.

Soll ich das Widget anklicken, statt ein Token zu injizieren?

Nur bei Turnstile, und auch dort nur manchmal. Eine Turnstile-Checkbox im Managed-Modus kann von allein durchgehen, wenn der Browser überzeugend wirkt, und genau dafür ist Camoufox da; zum Anklicken muss vorher die Cross-Origin-Opener-Policy fallen. Ein Klick auf eine reCAPTCHA-Checkbox öffnet nur eine Bild-Challenge, dort ist also nichts zu gewinnen. Einzelheiten zur Widget-Seite liefert die Cloudflare-Turnstile-Löser-Seite.

Mein Crawler läuft auf einem Linux-VPS. Wohin kommt CapSkip?

Auf eine Windows-Maschine, die Sie kontrollieren, mit eingeschaltetem Server-Modus. Der VPS ruft sie dann über die API auf wie jeden internen Dienst, Camoufox und der Löser müssen sich also weder ein Betriebssystem noch ein Netzsegment teilen. Richten Sie das host-Argument auf diese Adresse, aktivieren Sie die Schlüsselvalidierung und geben Sie dem VPS einen eigenen Schlüssel.

Die Kurzfassung

Starten Sie Camoufox mit eingeschaltetem main_world_eval, lesen Sie den sitekey mit einem gewöhnlichen Locator, lösen Sie ihn mit CapSkip auf 127.0.0.1:8080 und injizieren Sie das Token dann über einen evaluate-Aufruf mit dem Präfix mw:, bevor Sie absenden. Das Präfix ist der ganze Trick, denn ohne es landet Ihr Schreibvorgang in einem Scope, den die Seite nie sieht. Den Rest der Python-Landschaft samt Selenium und Playwright finden Sie auf der Python-Captcha-Solver-Seite.

Eines sollten Sie noch wissen, bevor Sie einen Crawl hochskalieren. CapSkip ist ein lokaler Captcha-Löser auf Hardware, die Ihnen bereits gehört, und deshalb kostet ein Lauf, der tausend Seiten wiederholt, genau so viel wie einer, der zehn wiederholt.