So lösen Sie ALTCHA in Selenium, ohne den Netzwerkverkehr abzufangen

solve altcha in selenium - How to Solve ALTCHA in Selenium Without Network Interception

Um ALTCHA in Selenium zu lösen, warten Sie nie darauf, dass das Widget fertig wird. ALTCHA ist Proof of Work und keine Erkennung: Die Website gibt eine Hash-Aufgabe aus, und der Client muss die Zahl finden, die sie erfüllt, bevor er durchgelassen wird. Es gibt nichts zu sehen und nichts anzuklicken, der Browser ist also für den Rest des Ablaufs da und nicht für das Captcha. Selenium hat kein Gegenstück in einem einzigen Aufruf zum Response-Wait von Playwright, und das ist einer der seltenen Fälle, in denen Sie das nichts kostet: Das Widget nennt seinen eigenen Challenge-Endpunkt in einem Attribut, und mehr braucht der Solver nicht.

Was Sie brauchen

  • CapSkip 1.2.6 oder neuer, laufend auf einem Windows-Rechner. Die ALTCHA-Unterstützung kam mit diesem Release, ein älterer Build hat also keine Methode, die Sie aufrufen könnten.
  • Python 3.10 oder neuer, mit den installierten Paketen Selenium und CapSkip, dazu ein passender Browser.
  • Die URL der Seite, auf der das Widget sitzt. Einen sitekey gibt es nicht, denn ALTCHA kennt keinen.
  • Eine Adresse für den Solver. Der Local-Modus antwortet auf 127.0.0.1 und nur für dieses Gerät, und der Server-Modus lauscht auf Ihrer Netzwerkadresse oder öffentlichen IP, damit ein Grid-Knoten, ein CI-Runner oder ein anderer Rechner ihn über dieselbe API erreichen kann. Schritt 5 klärt, welcher Modus für Sie gilt, und beide finden Sie unter Verbindungseinstellungen.
# pip install selenium capskip
pip install selenium capskip

Schritt 1: Den Challenge-Endpunkt am Widget ablesen

Das Widget-Element nennt den Endpunkt, bei dem es seine Challenge anfordert. Welches Attribut ihn trägt, hängt von der Widget-Generation ab, lesen Sie also den Quelltext der Seite, statt es zu vermuten.

Widget-GenerationAttribut, das die Challenge benennt
v1 und v2challengeurl für einen Endpunkt, mit einem separaten Attribut challengejson, wenn die Challenge inline vorliegt
v3 und neuerchallenge, und dieses eine Attribut nimmt entweder eine URL oder die Challenge-Daten selbst

Zwei Selenium-Eigenheiten sind hier wichtig, und keine davon liegt auf der Hand. Selenium wartet nicht von selbst auf ein Element, fragen Sie das Widget also ab, bevor die Seite es in den DOM gestellt hat, dann bekommen Sie eine NoSuchElementException statt eines erneuten Versuchs. Und der gewöhnliche Attribut-Aufruf ist ein Zwitter: Er probiert zuerst die JavaScript-Eigenschaft dieses Namens und greift erst danach auf das Markup zurück. Ein Web Component definiert Eigenschaften für seine eigenen Attribute, was zurückkommt, hängt also vom Build des Widgets ab und nicht von dem HTML, das Sie lesen können. Der DOM-Attribut-Aufruf hat diesen Rückgriff nicht und liefert das, was im Markup steht, und genau das wollen Sie. Die Python-API-Referenz erklärt den Unterschied auf der WebElement-Seite.

# pip install selenium capskip
from urllib.parse import urljoin
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

PAGE_URL = "https://example.com/signup"

driver = webdriver.Chrome()
driver.get(PAGE_URL)

# Nothing auto-waits in Selenium, so wait for the element.
widget = WebDriverWait(driver, 15).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "altcha-widget"))
)

# v1 and v2 use challengeurl or challengejson; v3 uses challenge.
value = (widget.get_dom_attribute("challengeurl")
         or widget.get_dom_attribute("challengejson")
         or widget.get_dom_attribute("challenge"))
if value is None:
    raise SystemExit("no challenge attribute on the widget")

Zwei dieser drei Attribute liefern das Dokument selbst und nicht einen Zeiger darauf, prüfen Sie also, was Sie bekommen haben, bevor Sie es als URL behandeln. Eine URL braucht noch einen eigenen Schritt: Im Markup steht meist ein Pfad derselben Herkunft, etwa ein führender Schrägstrich und ein Ordnername, und den kann ein HTTP-Client nicht von sich aus abrufen. Lösen Sie ihn zuerst gegen die Seiten-URL auf.

import json

challenge, endpoint = None, None
if value.lstrip().startswith("{"):
    # challengejson, or a v3 challenge holding the data inline.
    challenge = json.loads(value)
else:
    # A path in the markup becomes an absolute URL here.
    endpoint = urljoin(PAGE_URL, value)

Die drei Interaktionsstile im type-Attribut des Widgets, native, checkbox und switch, sind rein visuell und erreichen den Solver nie, und das separate display-Attribut ist auf dieselbe Weise visuell. ALTCHA dokumentiert sie alle in seinem eigenen Widget-Leitfaden.

Schritt 2: Die Challenge mit der Sitzung des Browsers abrufen

Diesen Schritt können Sie überspringen, wann immer der Endpunkt öffentlich ist, und viele sind es. Übergeben Sie die URL an CapSkip, und es holt die Challenge selbst. Den Schritt gibt es, weil manche Websites die Challenge an die Sitzung binden, die danach gefragt hat, und der Solver holt sie von Ihrem Rechner ohne Cookies, was er zurückbekommt, gehört also niemandem. Dann stimmt die Antwort, und die Website weist sie trotzdem ab.

Die Lösung besteht darin, die Challenge innerhalb der Sitzung des Browsers abzurufen und das Dokument statt des Zeigers zu übergeben. Kopieren Sie die Cookies, die Selenium ohnehin schon hält, in einen HTTP-Client, nehmen Sie denselben User Agent mit und fragen Sie selbst danach.

import requests

if endpoint:
    session = requests.Session()
    for cookie in driver.get_cookies():
        session.cookies.set(cookie["name"], cookie["value"],
                            domain=cookie["domain"])
    session.headers["User-Agent"] = driver.execute_script(
        "return navigator.userAgent")

    # Referer matters on sites that check where the ask came from.
    challenge = session.get(endpoint,
                            headers={"Referer": PAGE_URL}).json()

Ein JSON-Decode-Fehler in dieser Zeile ist der nützliche Fehlschlag. Er bedeutet, dass der Endpunkt mit HTML geantwortet hat, normalerweise mit einer Login-Seite oder einer Consent-Wall, und das sagt Ihnen, dass die Anfrage als Fremder hinausging und dass dieser Schritt richtig war. Starten Sie das Lösen direkt nach dem Abruf. Das Zeitfenster ist kurz, und die Uhr läuft ab dem Moment, in dem die Website die Challenge ausgibt, nicht ab dem Moment, in dem Sie dazu kommen, sie zu verwenden.

Schritt 3: Die Challenge an die eine ALTCHA-Methode übergeben

Eine einzige Methode, vollständig beschrieben durch die ALTCHA-Solver-Seite, und diese Methode nimmt die Challenge in beiden Formen entgegen. Übergeben Sie das Dokument, dann geht überhaupt keine Anfrage hinaus. Übergeben Sie den Endpunkt, dann holt der Solver sie selbst, und zwar auch eine frische, wenn der Job lange genug in der Warteschlange lag, dass die erste abgelaufen ist.

from capskip import CapSkip

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

# The document from step 2, so nothing is fetched twice.
result = solver.altcha(url=PAGE_URL, challenge_json=challenge)

# Or hand over the endpoint and let CapSkip fetch it.
# result = solver.altcha(url=PAGE_URL, challenge_url=endpoint)

print(result["token"])    # base64 payload for the form field
print(result["number"])   # the counter that satisfied it

Zwei Schlüssel in diesem Ergebnis gibt es nur für ALTCHA. Der Schlüssel token enthält die base64-Nutzlast, die das Formular erwartet, und der Schlüssel number enthält den Zähler, der die Challenge gelöst hat. Der Schlüssel code trägt denselben String wie token, beide funktionieren also, aber token ist nach dem Feld benannt, in das er gehört. Die GeeTest-Schlüssel und der Turnstile-User-Agent fehlen hier.

Welche Algorithmen der Solver abdeckt

Das ältere Verfahren ist mit SHA-1, SHA-256, SHA-384 und SHA-512 abgedeckt, Proof-of-Work v2 mit PBKDF2 und iterativem SHA. PBKDF2 ist die Voreinstellung, die ALTCHA selbst empfiehlt, und damit die große Mehrheit der Websites im Netz. Argon2id und scrypt sind die Ausnahmen, und sie werden abgelehnt statt versucht: Eine Challenge, die eines von beiden verlangt, kommt nach etwa einer Drittelsekunde als ERROR_CAPTCHA_UNSOLVABLE zurück und wird nie wiederholt, denn eine speicherharte Funktion lässt sich durch einen erneuten Versuch nicht beheben. Bei diesem Typ deutet dieses Ergebnis auf den Algorithmus und nicht auf ein unlesbares Bild, und der Fehlercode hat einen eigenen Leitfaden.

Schritt 4: Den Token in das versteckte Feld schreiben

Das Widget schickt seine Payload in einem versteckten Input, dessen Name aus seinem eigenen name-Attribut stammt und standardmäßig altcha lautet. Lesen Sie dieses Attribut aus, statt es zu vermuten, genauso wie Sie das Challenge-Attribut gelesen haben. In diesem Durchlauf hat nichts das Widget verifiziert, also hat auch nichts dieses Feld gefüllt, und Sie füllen es selbst.

Selenium kann ein WebElement direkt in ein Skript übergeben, und damit ist der Button selbst der sauberste Weg zum richtigen Formular. Reichen Sie den Submit-Button hinüber und lesen Sie seine form-Eigenschaft: Eine Registrierungsseite trägt oft mehrere Formulare, und wenn Sie das Feld an das erste auf der Seite hängen, der Button aber zu einem anderen gehört, sieht der Server nie einen Wert.

SET_FIELD = """
const form = arguments[0].form || document.querySelector('form');
let field = form.querySelector('[name="' + arguments[1] + '"]');
if (!field) {
    field = document.createElement('input');
    field.type = 'hidden';
    field.name = arguments[1];
    form.appendChild(field);
}
field.value = arguments[2];
"""

button = driver.find_element(By.CSS_SELECTOR, "button[type=submit]")
field_name = widget.get_dom_attribute("name") or "altcha"

driver.execute_script(SET_FIELD, button, field_name, result["token"])
button.click()

Reichen Sie den String unverändert durch. Der Token ist base64 eines JSON-Dokuments, dessen Felder von der HMAC-Signatur des Servers abgedeckt sind, alles, was nach Aufräumen aussieht, macht ihn also kaputt: Whitespace entfernen, dekodieren und neu kodieren oder das JSON mit den Schlüsseln in anderer Reihenfolge neu aufbauen. Manche Integrationen lesen die Nutzlast aus einem JSON-Body-Feld statt aus einem Formularfeld, und das Widget lässt sich auch so konfigurieren, dass es sie in einem Cookie liefert, prüfen Sie also, was der Submit der Seite selbst sendet, und bilden Sie das nach.

Wird der Submit-Button nie klickbar, dann hängt die Seite ihn nicht daran auf, dass das Feld einen Wert hält, sondern daran, dass ihr eigenes Skript vom Erfolg des Widgets erfährt. Dazu gibt es zwei ehrliche Antworten. Finden Sie heraus, worauf die Seite lauscht, und liefern Sie es, oder lassen Sie den Button weg und posten Sie die Felder des Formulars direkt mit den Cookies, die der Browser schon hat, was meist kürzer und immer stabiler ist.

Schritt 5: Wo der Solver läuft, sobald Ihr Skript umzieht

Die Beispiele verwenden 127.0.0.1, weil das richtig ist, solange Ihr Skript und CapSkip sich einen Rechner teilen. Der Solver wird von Ihrem Python-Code aufgerufen, nicht vom Browser und nicht von der Seite, die Adresse hängt also davon ab, wo der Testprozess läuft, und nicht davon, wo Chrome läuft. Bei Selenium verdreht man das leicht, weil der Browser ohnehin so oft woanders sitzt.

Wo der Python-Prozess läuftWelcher Verbindungsmodus
Auf dem CapSkip-Rechner, mit Steuerung eines lokalen BrowsersLocal-Modus. 127.0.0.1 ist hier wirklich richtig
Auf Ihrem Rechner, mit einem entfernten WebDriver oder einem Grid-KnotenLokal-Modus. Der Browser spricht nie mit dem Solver
Auf einem anderen Rechner im selben NetzwerkServer-Modus, auf der privaten Adresse des Solver-Rechners
In einem Container, auf einem CI-Runner oder auf einem VPSServer-Modus mit einer statischen öffentlichen IP und einer Firewallregel

Stellen Sie CapSkip auf den Server-Modus um, dann lauscht es auf Ihrer Netzwerkadresse oder öffentlichen IP statt auf Loopback, und der Runner verbindet sich über dieselbe HTTP-API, die er auch lokal nutzen würde. Eine statische öffentliche IP ist zu empfehlen, wenn die Strecke über das Internet führt, dazu eine Firewall-Regel, die nur die Adressen zulässt, mit denen Sie rechnen. Der Server-Modus ändert nur, wo der Solver lauscht, und sonst nichts: Es ist weiterhin Ihre Hardware, und es wird weiterhin nicht pro Lösung abgerechnet. Die Tests selbst über ein Grid zu fahren, hat eigene Fallstricke, und die stehen im Selenium-Grid-Leitfaden.

Lesen Sie Host und Port aus der Umgebung, damit ein Skript an beiden Orten funktioniert. Der Client liest weder CAPSKIP_HOST noch CAPSKIP_PORT von sich aus, übergeben Sie beide Werte also an den Konstruktor, wie es das vollständige Beispiel unten tut.

Ein ALTCHA-spezifischer Hinweis zu Proxys. Ein Proxy wird hier unterstützt, aber er wird nur für das Abholen der Challenge verwendet. Es gibt keine Browser-Session, die darüber geleitet werden müsste, er hat also keine Wirkung auf den Proof of Work selbst, und er tut überhaupt nichts, wenn Sie das Challenge-Dokument inline übergeben.

Vollständiges lauffähiges Beispiel

import json, os, requests
from urllib.parse import urljoin
from capskip import CapSkip, ApiException, NetworkException, TimeoutException
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

PAGE_URL = "https://example.com/signup"

SET_FIELD = """
const form = arguments[0].form || document.querySelector('form');
let field = form.querySelector('[name="' + arguments[1] + '"]');
if (!field) {
    field = document.createElement('input');
    field.type = 'hidden';
    field.name = arguments[1];
    form.appendChild(field);
}
field.value = arguments[2];
"""

solver = CapSkip(
    host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
    port=int(os.environ.get("CAPSKIP_PORT", 8080)),
)

driver = webdriver.Chrome()
try:
    driver.get(PAGE_URL)
    widget = WebDriverWait(driver, 15).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, "altcha-widget"))
    )
    value = (widget.get_dom_attribute("challengeurl")
             or widget.get_dom_attribute("challengejson")
             or widget.get_dom_attribute("challenge"))
    if value is None:
        raise SystemExit("no challenge attribute on the widget")

    if value.lstrip().startswith("{"):
        challenge = json.loads(value)
    else:
        endpoint = urljoin(PAGE_URL, value)
        session = requests.Session()
        for cookie in driver.get_cookies():
            session.cookies.set(cookie["name"], cookie["value"],
                                domain=cookie["domain"])
        session.headers["User-Agent"] = driver.execute_script(
            "return navigator.userAgent")
        challenge = session.get(endpoint,
                                headers={"Referer": PAGE_URL}).json()

    try:
        result = solver.altcha(url=PAGE_URL, challenge_json=challenge)
    except ApiException:
        raise SystemExit("refused: Argon2id, scrypt, or an expired challenge")
    except NetworkException:
        raise SystemExit("solver unreachable: check host and connection mode")
    except TimeoutException:
        raise SystemExit("no answer inside defaultTimeout")

    button = driver.find_element(By.CSS_SELECTOR, "button[type=submit]")
    field_name = widget.get_dom_attribute("name") or "altcha"
    driver.find_element(By.NAME, "email").send_keys("[email protected]")
    driver.execute_script(SET_FIELD, button, field_name, result["token"])
    button.click()
finally:
    driver.quit()

Alle vier Exceptions leiten sich von CapSkipError ab, wer stattdessen diese eine fängt, behandelt also jeden Fehler, den das SDK werfen kann, in einem einzigen Block. Fangen Sie die spezifischen ab, wenn die Reaktion unterschiedlich ausfällt, wie oben, und CapSkipError, wenn sie gleich bleibt. Achten Sie darauf, welche TimeoutException Sie fangen: Die von CapSkip und die von Selenium teilen sich den Namen, importieren Sie also eine von beiden unter einem Alias, wenn beide im Sichtbarkeitsbereich liegen.

Die anderen Typen funktionieren vom selben Client aus genauso. reCAPTCHA und Turnstile nehmen einen Sitekey und eine Seiten-URL, GeeTest nimmt einen gt-Wert, eine Challenge und die Seiten-URL, und das Bild-Lösen nimmt einen Dateipfad, eine URL oder base64. Alle Methoden, die das Paket bereitstellt, finden Sie auf der Python-Captcha-Solver-Seite, und das größere Browser-Bild finden Sie auf die Selenium-Captcha-Solver-Seite.

Häufige Fehler und was sie bedeuten

Was Sie sehenUrsacheBeheben
NoSuchElementException beim WidgetDas Element war noch nicht in den DOM eingefügt, was passiert, wenn ein Skript das Markup baut, und Selenium wartet nicht von selbstVerpacken Sie die Suche in ein explizites Warten darauf, dass das Element vorhanden ist
Alle drei Attribut-Abfragen kommen als None zurückDas Widget wurde vollständig aus JavaScript konfiguriert, keiner der drei Namen existiert also im MarkupLesen Sie die Konfiguration aus dem Skript der Seite selbst, oder greifen Sie auf den Endpunkt zurück, den Sie im Netzwerk-Panel sehen
Ein leerer Attributwert, obwohl der Quelltext der Seite klar einen enthältDer gewöhnliche Attribut-Aufruf hat eine Eigenschaft geliefert, die das Web Component definiert und die leer istNehmen Sie den DOM-Attribut-Aufruf, der das Markup liest und keinen Rückgriff auf Eigenschaften hat
MissingSchema vom HTTP-Client, mit einem Wert, der mit einem Schrägstrich beginntIm Markup steht ein Pfad derselben Herkunft, und der DOM-Attribut-Aufruf gibt ihn genau so zurück, wie er dort stehtLösen Sie ihn gegen die Seiten-URL auf, bevor Sie abrufen und bevor Sie ihn an den Solver übergeben
Der Attributwert beginnt mit einer geschweiften KlammerEin challengejson-Attribut, oder ein v3-Widget, das das Dokument inline trägt statt einer URLParsen Sie ihn als JSON und übergeben Sie ihn als Inline-Challenge
Ein JSON-Decode-Fehler beim Abruf des EndpunktsDer Endpunkt hat mit HTML geantwortet, die Anfrage kam also ohne die Sitzung des Browsers anKopieren Sie Cookies und User Agent hinüber, bevor Sie abrufen
Eine ApiException bei einer Challenge, die Sie eben erst abgefangen habenDie Inline-Challenge war bereits abgelaufen, der Solver hat sie also abgelehnt statt sie zu hashenHolen und lösen Sie in einem Zug, oder übergeben Sie den Endpunkt, damit der Solver erneut abrufen kann, solange dieser Endpunkt nicht an die Sitzung gebunden ist
ERROR_CAPTCHA_UNSOLVABLE in einer ApiException, nach etwa einer DrittelsekundeDie Challenge verwendet Argon2id oder scryptNichts zu wiederholen. Diese beiden werden bewusst abgelehnt
Ein nackter Verifizierungsfehler, obwohl Ihr Log zeigt, dass der Token gelöst wurdeDie Challenge ist zwischen dem Lösen und dem Absenden abgelaufenAbfangen, lösen und absenden, ohne etwas Langsames dazwischen
Eine NetworkException beim ersten LösenCapSkip läuft nicht, oder das Skript liegt in einem Container und zeigt auf LoopbackStarten Sie CapSkip und entscheiden Sie sich dann zwischen Lokal-Modus und Server-Modus
Eine TimeoutException, die 120 Sekunden nenntDer Solver hat nicht innerhalb des voreingestellten Polling-Timeouts geantwortet, das ALTCHA statt des längeren reCAPTCHA-Timeouts verwendetPrüfen Sie, ob der Solver läuft und nicht ausgelastet ist. Die Obergrenze anzuheben verzögert nur dieselbe Antwort
Das Formular wird gesendet, aber der Server meldet einen fehlenden altcha-WertDas versteckte Feld wurde an ein anderes Formular der Seite angehängtLesen Sie die form-Eigenschaft am Submit-Button aus, wie es das Snippet tut
Der Submit-Button bleibt deaktiviertDie Seite gibt ihn erst frei, wenn ihr eigenes Script den Erfolg des Widgets siehtSenden Sie die Formularfelder direkt oder bedienen Sie das, worauf die Seite hört
Eine ValidationException beim AufrufEs wurde keine der beiden Challenge-Optionen angegeben, oder es wurde eine Option übergeben, die ALTCHA nicht nimmtÜbergeben Sie den Endpunkt oder das Dokument und lassen Sie alles andere weg

FAQ

Brauche ich überhaupt einen Browser, um ALTCHA zu lösen?

Nein. ALTCHA ist eine Hash-Aufgabe, sie wird also mit CPU beantwortet, und an der Antwort ist kein Browser beteiligt. War das Captcha der einzige Grund, Selenium zu öffnen, dann schließen Sie es: Holen Sie die Challenge mit einem HTTP-Client und posten Sie den Token zurück, was der Leitfaden für reines Python durchspielt. Selenium verdient sich seinen Platz, wenn der Rest des Ablaufs eine echte Seite braucht, etwa ein Login, das Cookies setzt, ein mehrstufiges Formular oder Markup, das erst existiert, nachdem ein Skript gelaufen ist.

Warum kommt das Widget-Attribut leer zurück?

Weil der gewöhnliche Attribut-Aufruf ein Zwitter ist. Er liefert die JavaScript-Eigenschaft, wenn das Element eine definiert, und greift erst danach auf das Markup zurück, und ein Web Component definiert Eigenschaften für seine eigenen Attribute, eine leere Eigenschaft gewinnt also gegen den Wert, den Sie im Quelltext sehen. Der DOM-Attribut-Aufruf hat diesen Rückgriff nicht und liefert das, was im Markup steht. Der andere Grund ist, dass Sie den falschen Namen für die Generation vor sich lesen, probieren Sie also alle drei durch, bevor Sie das Attribut für fehlend erklären.

Kann ein CI-Runner oder ein Container den Solver erreichen?

Ja, über den Server-Modus. Stellen Sie CapSkip in den Verbindungseinstellungen von Loopback auf Ihre Netzwerkadresse oder öffentliche IP um und richten Sie dann die Host-Umgebungsvariable darauf. Runner und Solver sprechen dann dieselbe HTTP-API wie auf einem einzigen Rechner. Nehmen Sie eine statische öffentliche IP, wenn die Strecke über das Internet führt, und schränken Sie sie mit einer Firewall-Regel ein. Ein entfernter Grid-Knoten ist eine andere Frage und braucht meist gar nichts, denn der Knoten führt den Browser aus, während Ihr Skript, das den Solver aufruft, dort bleibt, wo Sie es gestartet haben.

Schneiden die eigenen Timeouts von Selenium das Lösen ab?

Nein, denn das Lösen ist kein WebDriver-Befehl. Ein implizites Warten, ein Page-Load-Timeout und ein Script-Timeout decken alle Aufrufe ab, die Selenium an den Browser schickt, und Ihr Solver-Aufruf ist gewöhnliches Python zwischen zweien davon. Die Obergrenze, die gilt, ist das voreingestellte Polling-Timeout des Clients von 120 Sekunden, das ALTCHA statt des längeren reCAPTCHA-Timeouts verwendet, weil es CPU-Arbeit ist und keine Browser-Sitzung. Achten Sie stattdessen auf eine Grenze, die um den ganzen Test gelegt ist, etwa ein Plugin-Timeout pro Test oder ein Limit für den CI-Job.

Die Kurzfassung

Warten Sie auf das Widget, lesen Sie sein Challenge-Attribut mit dem DOM-Attribut-Aufruf, lösen Sie einen Pfad gegen die Seiten-URL auf und rufen Sie diesen Endpunkt innerhalb der Sitzung des Browsers ab, wenn die Website ihn an eine solche bindet. Übergeben Sie das Dokument oder die URL zusammen mit der Seiten-URL an die eine ALTCHA-Methode und schreiben Sie dann den Token in das versteckte Feld, dessen Namen das name-Attribut des Widgets vorgibt, und zwar in dem Formular, zu dem der Submit-Button gehört. Fassen Sie den Token unterwegs nicht an. Halten Sie Abruf, Lösen und Absenden dicht beieinander, denn das Fenster kann sich innerhalb von zwei Minuten schließen, und eine abgelaufene Challenge sieht genau aus wie eine falsche Antwort.

Noch eine letzte Sache, die verändert, wie Sie den erneuten Versuch gestalten. Weil ein unbegrenzter Captcha-Löser den Proof of Work auf Hardware berechnet, die Ihnen bereits gehört, kostet eine verworfene Challenge ein paar Millisekunden eigener CPU-Zeit und sonst nichts, die Seite für eine frische neu zu laden ist also immer billiger, als einen veralteten Token durch einen langen Test zu schleppen.