Captchas in Scrapling mit page_action lösen

Eine Captcha-Aufgabe in Scrapling teilt sich sauber in zwei Hälften, und zu wissen, in welcher Sie gerade stecken, spart einen Nachmittag. Cloudflare hat seinen eigenen Schalter: Übergeben Sie solve_cloudflare an StealthyFetcher, und die Turnstile- oder Interstitial-Challenge wird für Sie erledigt. Alles andere ist Ihre Sache. reCAPTCHA v2, reCAPTCHA v3, GeeTest und Bild-Captchas werden außerhalb des Fetchers gelöst und über den page_action-Hook in die Seite injiziert. Dieser Artikel geht diese zweite Hälfte durch, samt dem einen Detail, das sie stillschweigend zerbricht.
Was Sie brauchen
- Python 3.10 oder neuer. Scrapling setzt das voraus.
- Scrapling, installiert mit dem Extra fetchers, plus dem einmaligen Browser-Download-Schritt.
- Die URL der geschützten Seite. Den sitekey brauchen Sie nicht im Voraus, denn der erste Durchgang liest ihn aus.
- CapSkip im Local-Modus, wenn Scraper und Löser sich eine Maschine teilen, oder im Server-Modus, wenn nicht. Beide werden beschrieben unter Verbindungseinstellungen.
# pip install capskip pip install capskip pip install "scrapling[fetchers]" # Scrapling needs one more step. The bare package is the parser # engine only, and importing from scrapling.fetchers without this # raises ModuleNotFoundError. scrapling install
Was solve_cloudflare abdeckt und was nicht
Hier lohnt sich Genauigkeit, denn der Name klingt breiter als die Funktion. Das Argument solve_cloudflare von StealthyFetcher arbeitet die Turnstile- und Interstitial-Challenges von Cloudflare ab, bevor es Ihnen die Response übergibt. Das ist eine echte Erleichterung, und Sie sollten sie nutzen. Es ist zugleich die ganze Liste.
| Challenge auf der Seite | Wer sie übernimmt |
|---|---|
| Cloudflare Turnstile und das seitenfüllende Interstitial | Scrapling, über das Argument solve_cloudflare |
| reCAPTCHA v2, einschließlich der Invisible- und Enterprise-Varianten | Sie, über einen page_action-Hook |
| reCAPTCHA v3 mit einem Action-Namen | Sie, über einen page_action-Hook |
| GeeTest v3, der Slider mit drei Feldern in der Antwort | Sie, über einen page_action-Hook |
| Ein Bild-Captcha mit verzerrtem Text | Sie, über einen page_action-Hook |
Die Form der Arbeit ist also jedes Mal dieselbe. Holen Sie die Challenge-Parameter aus der Seite, lösen Sie sie anderswo, setzen Sie die Antwort dann zurück in das DOM und lassen Sie das seiteneigene Formular sie tragen. Scrapling gibt Ihnen genau eine Stelle für diesen Mittelteil, und das ist page_action.
Schritt 1: den sitekey lesen, ohne einen Browser zu starten
Scrapling hat drei Fetcher, und sie sind nicht austauschbar. Fetcher ist schlichtes HTTP. DynamicFetcher steuert einen Playwright-Browser. StealthyFetcher ist der gehärtete, und seit Version 0.3.13 läuft er auf Patchright statt auf dem zuvor genutzten Camoufox-Build, was Sie wissen sollten, wenn Sie einer älteren Anleitung folgen.
Für einen sitekey brauchen Sie fast nie einen Browser. Der Wert steht als Data-Attribut im Markup, der günstige Fetcher kann ihn also auslesen.
# pip install capskip
from scrapling.fetchers import Fetcher
PAGE_URL = "https://example.com/page-with-recaptcha"
# Plain HTTP. No browser starts, so this costs almost nothing
# and tells you which CAPTCHA you are actually facing.
page = Fetcher.get(PAGE_URL)
sitekey = page.css("[data-sitekey]::attr(data-sitekey)").get()
print(sitekey)Kommt das leer zurück, wird das Widget per JavaScript eingefügt und steht nicht im ausgelieferten HTML. Holen Sie dieselbe Seite einmal mit DynamicFetcher und lesen Sie ihn aus dem gerenderten DOM, oder öffnen Sie sie im Browser und tragen Sie den Wert fest ein. Ein sitekey ist öffentlich und stabil, das feste Eintragen ist hier also keine Abkürzung, für die Sie sich entschuldigen müssten.
Schritt 2: auf Ihrer eigenen Hardware lösen
Ein Aufruf. Jede reCAPTCHA-Variante ist dieselbe Methode mit anderen Keyword-Argumenten: invisible auf 1, enterprise auf 1, oder version auf v3 mit einem Action-Namen. Turnstile und GeeTest haben eigene Methoden derselben Form, und die vollständige Parameterliste steht in der CapSkip-API-Dokumentation.
# CapSkip listens on your own machine, so this is a loopback call. from capskip import CapSkip solver = CapSkip(host="127.0.0.1", port=8080) token = solver.recaptcha(sitekey=sitekey, url=PAGE_URL)["code"]
Dieser Aufruf blockiert, während er pollt, und das Polling läuft nicht in einem festen Intervall: Das SDK startet bei 250 Millisekunden und nähert sich schrittweise pollingInterval an, kehrt also meist früher zurück als eine handgeschriebene Schleife gegen den rohen Endpunkt. Die Obergrenze ist recaptchaTimeout, standardmäßig 300 Sekunden.
Schritt 3: das Token in page_action injizieren
page_action nimmt eine Funktion entgegen, erhält das Playwright-Page-Objekt und läuft nach der Navigation und nach dem network_idle-Warten, aber vor wait_selector. Genau diese Reihenfolge ist das Nützliche daran: Das Widget ist bereits gerendert, wenn Ihre Funktion läuft, und alles, worauf Sie danach warten, sieht das Ergebnis Ihrer Aktion.
from playwright.sync_api import Page
def inject_token(page: Page):
# The response textarea is hidden, so set the value directly
# rather than trying to type into it.
page.evaluate(
"(t) => document.getElementById('g-recaptcha-response').value = t",
token,
)
page.click("button[type=submit]")Die Funktion muss nichts zurückgeben. Ältere Versionen von Scrapling wollten das Page-Objekt zurück, die aktuelle Dokumentation nicht mehr, nichts zurückzugeben ist also in beiden Fällen die sichere Schreibweise.
Der isolierte Ausführungskontext, und genau der beißt
StealthyFetcher wertet Ihr JavaScript standardmäßig im isolierten Ausführungskontext von Patchright aus. Das DOM ist gemeinsam, Elemente lesen und schreiben funktioniert also genau wie erwartet. Die seiteneigenen JavaScript-Globals sind nicht gemeinsam, alles, was die Website auf window gelegt hat, fehlt daher schlicht.
Dieser Unterschied entscheidet, ob Ihre Injektion funktioniert. Den Wert der Response-Textarea zu setzen berührt nur das DOM und klappt in der isolierten Welt problemlos. Den reCAPTCHA-Callback der Website aufzurufen klappt nicht, denn dieser Callback hängt an einem Global, das das seiteneigene Skript angelegt hat. Websites, die einen Callback statt eines gewöhnlichen Formular-Submits nutzen, wirken dann so, als hätten sie ein völlig gültiges Token ignoriert.
def inject_and_fire_callback(page: Page):
# isolated_context=False drops into the page's own world,
# where the site's grecaptcha object actually exists.
page.evaluate(
"""(t) => {
document.getElementById('g-recaptcha-response').value = t;
window.onCaptchaSuccess(t);
}""",
token,
isolated_context=False,
)Lesen Sie den Callback-Namen am Widget ab, bevor Sie das schreiben. Es ist das, was das data-callback-Attribut am reCAPTCHA-Element angibt, und es unterscheidet sich je nach Website.
Vollständiges lauffähiges Beispiel
Ein günstiger HTTP-Durchgang liest den sitekey, dann eine Lösung, dann ein einziger Stealth-Fetch, der injiziert und absendet. Beachten Sie: Die Lösung passiert vor dem Fetch, das Token liegt also schon vor, wenn page_action läuft.
# pip install capskip
from capskip import CapSkip
from playwright.sync_api import Page
from scrapling.fetchers import Fetcher, StealthyFetcher
PAGE_URL = "https://example.com/page-with-recaptcha"
solver = CapSkip(host="127.0.0.1", port=8080)
sitekey = Fetcher.get(PAGE_URL).css("[data-sitekey]::attr(data-sitekey)").get()
token = solver.recaptcha(sitekey=sitekey, url=PAGE_URL)["code"]
def inject_token(page: Page):
page.evaluate(
"(t) => document.getElementById('g-recaptcha-response').value = t",
token,
)
page.click("button[type=submit]")
page = StealthyFetcher.fetch(
PAGE_URL,
headless=True,
network_idle=True,
page_action=inject_token,
wait_selector=".dashboard",
)
print(page.css(".dashboard h1::text").get())Zwei Argumente sind dort tragend. network_idle lässt den Fetcher warten, bis es 500 Millisekunden lang keine Netzwerkverbindungen gab, was meist reicht, damit das Widget rendert. wait_selector belegt, dass das Absenden geklappt hat: Richten Sie es auf etwas, das es nur jenseits des Formulars gibt, dann wird aus einem gescheiterten Absenden ein Timeout statt eines stillen Erfolgs.
Mehrere Seiten mit einer Session abarbeiten
Jeder Fetch auf Klassenebene startet einen Browser und wirft ihn wieder weg. Ab mehr als ein paar Seiten öffnen Sie stattdessen eine Session und behalten den Browser zwischen den Requests. Die meisten Fetch-Argumente lassen sich einmal an der Session setzen und pro Request überschreiben, darunter page_action, wait_selector und solve_cloudflare.
from scrapling.fetchers import StealthySession
# solve_cloudflare here handles the Cloudflare layer for every
# page in the session. Anything else still goes through
# page_action, per request.
with StealthySession(headless=True, solve_cloudflare=True) as session:
for url in urls:
page = session.fetch(url, page_action=inject_token)
print(page.css("title::text").get())Lösen Sie so spät wie möglich. Ein reCAPTCHA-Token ist etwa zwei Minuten lang gültig; zwanzig davon im Voraus zu lösen und dann eine Queue abzuarbeiten lässt die meisten verfallen. Lösen Sie innerhalb der Schleife, direkt vor dem Fetch, der das Token nutzt. Was diese Lebensdauer in der Praxis bedeutet, steht in dem Leitfaden zum Ablaufen von reCAPTCHA-Tokens.
Den Löser auf einer anderen Maschine betreiben
Scraper wandern. Ein VPS, ein Container oder ein geplanter Worker ist nicht die Maschine mit dem Löser darauf, und das Loopback zeigt dort auf den Worker selbst, wo nichts lauscht.
CapSkip hat genau dafür zwei Verbindungsmodi. Local bindet sich an 127.0.0.1 und antwortet nur diesem Gerät. Server bindet sich an Ihre Netzwerkadresse oder öffentliche IP, sodass ein Scraper von überall dieselbe Windows-Maschine über die API erreicht. Eine statische öffentliche IP hält die Adresse stabil. Es ist so oder so Ihre Hardware und so oder so ohne Verbrauchsabrechnung, ein Lauf mit fünfzigtausend gelösten Challenges kostet also dasselbe wie einer mit fünfzig.
# The SDK reads these three itself, so the same script runs # whether the solver is on this box or on another one: # CAPSKIP_HOST=192.0.2.10 # CAPSKIP_PORT=8080 # CAPSKIP_API_KEY=your-key solver = CapSkip()
Schalten Sie die Schlüsselprüfung ein, sobald der Löser auf einer Netzwerkadresse lauscht, und geben Sie jedem Worker einen eigenen Schlüssel, damit einer widerrufen werden kann, ohne die übrigen zu stören. Beide Modi werden Schritt für Schritt erklärt unter CapSkip-Einrichtungsanleitung.
Proxys auf beiden Seiten
Wenn die Website prüft, ob das Token von der Adresse kam, die es absendet, müssen Lösung und Fetch denselben Ausgang nehmen. Scrapling nimmt an den Fetchern ein proxy-Argument entgegen, und CapSkip nimmt für reCAPTCHA, Turnstile und GeeTest eines pro Aufgabe. Bild-Captchas akzeptieren keinen Proxy und brauchen auch keinen.
PROXY = "http://user:[email protected]:3128" token = solver.recaptcha( sitekey=sitekey, url=PAGE_URL, proxy={"type": "HTTP", "uri": "user:[email protected]:3128"}, )["code"] page = StealthyFetcher.fetch(PAGE_URL, proxy=PROXY, page_action=inject_token)
Die beiden Schreibweisen unterscheiden sich mit Absicht: Der Löser nimmt Typ und URI als getrennte Felder, der Fetcher nimmt einen einzigen URL-String. Eines davon falsch zu setzen bedeutet, dass die Lösung von Ihrer echten Adresse ausgeht, während der Fetch vom Proxy ausgeht, was genau wie ein ungültiges Token aussieht und keines ist. Die Wahl des Proxy-Typs wird behandelt in dem Leitfaden zu rotierenden Proxys beim Lösen.
Häufige Fehler und was sie bedeuten
| Was Sie sehen | Ursache | Beheben |
|---|---|---|
| ModuleNotFoundError beim Import von fetchers | Das nackte Paket enthält nur die Parser-Engine | Mit dem Extra fetchers installieren, dann den Befehl scrapling install ausführen |
| Der sitekey-Selektor liefert nichts | Das Widget wird per JavaScript injiziert und fehlt daher im ausgelieferten HTML | Einmal mit DynamicFetcher auslesen oder fest eintragen |
| Das Token landet im Feld, aber das Formular wird nie abgesendet | Die Website nutzt einen Callback, und dieses Global fehlt in der isolierten Welt | Beim evaluate-Aufruf isolated_context auf False setzen |
| Ein Timeout bei wait_selector nach einer sauberen Lösung | Das Absenden schlug fehl, das Element jenseits davon erschien also nie | In page_action einen Screenshot machen und ansehen, was die Seite tatsächlich sagt |
| Ein gültiges Token wird abgelehnt | Lösung und Fetch gingen von verschiedenen Adressen aus | Denselben Proxy auf beide setzen, jeweils in der eigenen Schreibweise |
| NetworkException vom Löser | CapSkip läuft nicht, oder Host und Port sind falsch | Die App starten oder die Host-Umgebungsvariable auf die Serveradresse richten |
| ValidationException vom Löser | Ein Argument, das dieser Captcha-Typ nicht akzeptiert | Prüfen Sie den Typ. Ein action bei v2 oder invisible bei v3 löst sie aus |
FAQ
Übernimmt solve_cloudflare auch reCAPTCHA?
Nein. Es deckt das Turnstile-Widget von Cloudflare und die Interstitial-Challenge-Seite von Cloudflare ab, also Cloudflare-Produkte. reCAPTCHA gehört Google, GeeTest steht für sich, und ein Bild-Captcha ist das, was die Website gezeichnet hat. Diese drei laufen über page_action mit einem Token, das Sie selbst gelöst haben.
Basiert StealthyFetcher weiterhin auf Camoufox?
Seit Version 0.3.13 nicht mehr. Er läuft jetzt auf Patchright, also einem Chromium-Stack statt einem Firefox-Stack. Die Dokumentation beschreibt weiterhin ein optionales Rezept, um per Subklasse der Session zu Camoufox zurückzukehren, aber die Voreinstellung hat sich geändert; eine Anleitung, die StealthyFetcher als Camoufox-Wrapper bezeichnet, beschreibt ein älteres Release.
Kann ich innerhalb von page_action lösen statt vor dem Fetch?
Ja, und bei reCAPTCHA v3 oder GeeTest müssen Sie das oft, weil die Parameter erst existieren, wenn die Seite gelaufen ist. Denken Sie nur daran, dass der Browser während der gesamten Lösung untätig wartet. Steht der sitekey im ausgelieferten HTML, hält erst lösen und dann injizieren den Browser nur einen Bruchteil der Zeit offen.
Ändert der Async-Fetcher daran etwas?
Nur die Schreibweise. Verwenden Sie die async-Fetch-Methode und machen Sie page_action zu einer async-Funktion, die die async Playwright-Page entgegennimmt. Auf der Löser-Seite verwenden Sie AsyncCapSkip, in Python eine echte async-Implementierung und kein Alias, sie teilt sich also Ihre Event Loop, statt einen Thread zu parken.
Die Kurzfassung
Überlassen Sie Cloudflare mit solve_cloudflare Scrapling und erledigen Sie alles andere selbst in page_action. Lesen Sie den sitekey mit dem einfachen HTTP-Fetcher, lösen Sie ihn, und setzen Sie dann das Response-Feld aus dem Hook heraus. Feuert die Website einen Callback, statt ein Formular abzusenden, evaluieren Sie mit abgeschaltetem isoliertem Kontext, sonst landet das Token in einer Welt, die die Seite nicht sieht. Richten Sie wait_selector auf etwas, das es nur nach einem erfolgreichen Absenden gibt, damit ein Fehlschlag laut ist.
Die umfassendere Python-Übersicht finden Sie auf der Python-Captcha-Solver-Seite, die Besonderheiten der Checkbox-Challenge auf die reCAPTCHA-v2-Solver-Seite, und die Cloudflare-Seite auf die Cloudflare-Turnstile-Löser-Seite. Dieselben drei Aufrufe gibt es in Node.js, PHP und C#, aufgelistet auf Die Seite zu den SDKs fürs Captcha-Lösen.
Eines sollten Sie abwägen, bevor Sie das auf einen echten Crawl richten. CapSkip ist ein unbegrenzter Captcha-Löser der auf Hardware läuft, die Ihnen bereits gehört, sodass die Captcha-Kosten eines großen Scrapes ein Fixkostenposten sind statt einer Abrechnung pro Lösung.
