So lösen Sie ALTCHA in Python, ohne einen Browser zu starten

solve altcha in python - How to Solve ALTCHA in Python Without Running a Browser

Sie können ALTCHA in Python mit einem HTTP-Client und sonst nichts lösen. ALTCHA ist Proof of Work und keine Erkennung: Die Website stellt eine Challenge aus, und wer durch will, muss so lange hashen, bis er die Zahl findet, die sie erfüllt. Es muss nichts angeschaut werden, also sind kein Browser, kein WebDriver und kein User-Agent beteiligt, und die Antwort wird berechnet und nicht geraten. Das bedeutet auch, dass ein Lösen deterministisch ist. Entweder wird die Zahl in Millisekunden gefunden, oder die Challenge selbst war fehlerhaft oder bereits abgelaufen. CapSkip hat den Typ in Version 1.2.6 hinzugefügt, und das Python SDK stellt ihn als eine Methode bereit.

Was Sie brauchen

  • CapSkip 1.2.6 oder neuer auf einem Windows-Rechner. Mit diesem Release kam ALTCHA dazu.
  • Python 3.10 oder neuer und das CapSkip-Paket.
  • Die URL der Seite, die das Widget trägt, plus der Endpunkt, von dem das Widget seine Challenge abruft.
  • Eine Adresse für den Solver. Der Local-Modus lauscht auf 127.0.0.1 und bedient nur dieses Gerät; der Server-Modus lauscht auf Ihrer Netzwerkadresse oder öffentlichen IP, damit ein anderer Rechner ihn erreichen kann. Beide finden Sie unter Verbindungseinstellungen.
# pip install capskip
pip install capskip requests

Schritt 1: Der Aufruf zum Lösen und welche Adresse er ansprechen muss

Übergeben Sie die Seiten-URL und den Challenge-Endpunkt. CapSkip ruft die Challenge selbst ab, hasht, bis es den Zähler findet, und gibt die Payload zurück, die das Formular erwartet.

# pip install capskip
from capskip import CapSkip

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

# CapSkip fetches the challenge, then brute-forces the counter.
result = solver.altcha(
    url="https://example.com/signup",
    challenge_url="https://example.com/altcha/challenge",
)

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

Zwei Schlüssel in diesem Dictionary gibt es nur bei ALTCHA. Der Schlüssel token enthält die base64-Payload, und der Schlüssel number enthält den Zähler, der die Challenge gelöst hat. Der Schlüssel code trägt dieselbe Zeichenfolge wie token, es funktioniert also beides, aber der Schlüssel token ist nach dem Feld benannt, in das er gehört.

Der Zähler wird für beide ALTCHA-Generationen gemeldet, was nützlicher ist, als es klingt, denn ihre Payloads sind sich nicht einig, wo er steht. Eine Legacy-Payload trägt ihn auf oberster Ebene, eine Proof-of-Work-v2-Payload nicht, sie hält ihn stattdessen in einem solution-Objekt. CapSkip liest ihn in seiner eigenen API-Antwort aus dem solution-Objekt aus, Sie bekommen also ein Feld, ganz gleich welche Generation die Website betreibt.

Loopback ist nur richtig, solange Sie sich einen Rechner teilen

Dieses Host-Argument ist die eine Zeile, die Sie ändern müssen, wenn das Skript umzieht. 127.0.0.1 ist richtig, solange der Python-Prozess und der Solver auf demselben Rechner sitzen. Sobald das Skript in einem Container, auf einem VPS, auf einem CI-Runner oder als geplanter Job auf einer Managed-Plattform läuft, zeigt Loopback stattdessen auf diese Umgebung, und der erste Aufruf löst eine NetworkException aus.

Der Server-Modus ist die Antwort. Er lässt CapSkip auf Ihrer Netzwerkadresse oder öffentlichen IP statt auf Loopback lauschen, sodass jede dieser Umgebungen denselben Solver über die API erreicht. Verwenden Sie eine statische öffentliche IP, wenn der Verkehr über das Internet geht, mit einer Firewall-Regel, die auf die erwarteten Adressen beschränkt ist. Sonst ändert sich nichts: Der Solver läuft weiter auf Ihrer eigenen Hardware und bleibt ohne Abrechnung pro Lösung, nur seine Adresse ist anders. Lesen Sie den Wert aus der Umgebung, statt ihn fest zu verdrahten, denn der Client sucht ohnehin nach CAPSKIP_HOST, CAPSKIP_PORT und CAPSKIP_API_KEY.

Schritt 2: Woher die Challenge kommt und die zwei Wege, sie zu übergeben

Öffnen Sie die DevTools, wechseln Sie auf den Tab Network und laden Sie die Seite neu. Das Widget stellt eine Anfrage für seine Challenge, meist an einen Pfad, der altcha enthält. Diese URL ist das, was Sie übergeben. Das JSON, das sie zurückgibt, ist das Challenge-Dokument, und Sie können stattdessen dieses übergeben.

Lesen Sie den Seitenquelltext, statt das Attribut zu erraten, denn es hat sich zwischen den Widget-Generationen geändert.

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 akzeptiert entweder eine URL oder die Challenge-Daten

Das Styling des Widgets ist hier irrelevant. Die Varianten native, checkbox und switch senden alle dieselbe Payload, und der Unterschied erreicht den Solver nie, es gibt also nichts zu erkennen. Für die feldweise Beschreibung einer Challenge pflegt ALTCHA seine eigene Widget- und Server-Dokumentation.

Wenn Ihr Scraper das Dokument schon hat, übergeben Sie es direkt und überspringen den Abruf ganz. Der Parameter nimmt ein Dictionary und serialisiert es für Sie, oder einen JSON-String, falls Sie genau den vorliegen haben.

# The document is already here, so no request goes out.
result = solver.altcha(
    url="https://example.com/signup",
    challenge_json={
        "algorithm": "SHA-256",
        "challenge": "YOUR_CHALLENGE_HASH",
        "salt": "YOUR_SALT",
        "signature": "YOUR_SIGNATURE",
        "maxnumber": 1000000,
    },
)

Beides zu übergeben ist erlaubt, und das Inline-Dokument gewinnt, weil ein Abruf nur noch einmal holen würde, was Sie gerade geliefert haben. Die beiden Wege unterscheiden sich in einem Punkt, der zählt, wenn sich Arbeit anstaut: Eine Inline-Challenge, die bereits abgelaufen ist, wird sofort abgelehnt, statt sinnlos gehasht zu werden, während ein Endpunkt dem Solver erlaubt, eine frische Challenge zu holen, falls die erste gestorben ist, während der Auftrag gewartet hat.

Welche Algorithmen der Solver abdeckt

Eine Methode bedient beide Generationen. Das Legacy-Verfahren ist mit SHA-1, SHA-256, SHA-384 und SHA-512 abgedeckt, Proof of Work v2 mit PBKDF2 und iterativem SHA. PBKDF2 ist der Standard, den ALTCHA selbst empfiehlt, das ist also die große Mehrheit der Live-Websites.

Argon2id und scrypt sind die Ausnahmen. 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.

Schritt 3: Den Token unangetastet zurücksenden, bevor er abläuft

Das Widget sendet seine Payload in einem Formularfeld namens altcha. Schicken Sie die Zeichenfolge genau so, wie sie angekommen ist.

import requests

# No strip(), no re-encoding, no rebuilding the JSON.
r = requests.post("https://example.com/signup", data={
    "email": "someone@example.com",
    "altcha": result["token"],
})

Diese Payload ist base64 eines JSON-Dokuments, dessen Felder von der HMAC-Signatur des Servers abgedeckt werden, jede Bearbeitung macht sie also ungültig. strip darauf aufzurufen, sie zu dekodieren und neu zu kodieren oder das Dictionary mit den Schlüsseln in anderer Reihenfolge neu aufzubauen, ergibt jeweils einen Token, den die Website ablehnt. Manche Integrationen lesen sie aus einem JSON-Body-Feld statt aus einem Formularfeld, prüfen Sie also, was das Absenden der Seite selbst sendet, und machen Sie es genauso.

Wenn eine Website einen Token ablehnt, den Ihr Log als gelöst ausweist, ist eine abgelaufene Challenge wahrscheinlicher als eine beschädigte. Challenge-Fenster sind kurz, manche schließen sich innerhalb von zwei Minuten, und eine abgelaufene Challenge kommt als nackter Verifizierungsfehler zurück, der genauso aussieht wie eine falsche Antwort. Rufen Sie ab, lösen und senden Sie in einer Arbeitseinheit, und halten Sie niemals einen Token, während ein Mensch ein Formular ausfüllt.

Die Polling-Timeouts des Clients sind hier nicht das, was Sie begrenzt, denn das Challenge-Fenster schließt sich lange vor beiden. Es lohnt sich trotzdem zu wissen, welches gilt, denn ALTCHA liegt auf der kurzen Seite der Aufteilung. Es ist CPU-Arbeit und keine Browser-Sitzung, es nutzt also das Standard-Polling-Timeout und nicht das längere für reCAPTCHA.

Konstruktor-OptionStandardWas sie abdeckt
defaultTimeout120 SekundenPolling für ALTCHA und Bild-Captchas
recaptchaTimeout300 SekundenPolling für reCAPTCHA, Turnstile und GeeTest
pollingIntervalMaximal 5 SekundenDas Polling beginnt bei 0,25 Sekunden und steigt per Backoff bis auf diesen Wert

Schritt 4: Einen Batch lösen, ohne dass jede Challenge veraltet

Python ist das einzige SDK, bei dem der asynchrone Client eine eigene Implementierung ist: AsyncCapSkip ist hier echtes asyncio. In den Paketen für Node.js und .NET ist dieser Name ein Alias für den gewöhnlichen Client, dessen Methoden ohnehin schon asynchron sind, und PHP ist durchgehend synchron.

Die Grenze ist nicht die Nebenläufigkeit, sondern die Frische. Hundert Challenges abzurufen und sie dann zu lösen, ist der falsche Aufbau, denn die frühesten laufen ab, während der Batch abgearbeitet wird. Rufen Sie stattdessen innerhalb desselben Tasks ab und lösen Sie dort, ein Task pro Seite, und überlassen Sie dem gather die Verteilung.

# pip install capskip
import asyncio
from capskip import AsyncCapSkip

async def solve_one(solver, page_url, challenge_url):
    # One fresh challenge per page, fetched and solved together.
    result = await solver.altcha(url=page_url, challenge_url=challenge_url)
    return page_url, result["token"]

async def main():
    solver = AsyncCapSkip()
    pages = [
        ("https://example.com/signup", "https://example.com/altcha/challenge"),
        ("https://example.com/contact", "https://example.com/altcha/challenge"),
    ]
    for page_url, token in await asyncio.gather(
        *(solve_one(solver, p, c) for p, c in pages)
    ):
        print(page_url, token[:24])

asyncio.run(main())

Proof of Work ist CPU-gebunden, die Obergrenze sind also Kerne und nicht offene Verbindungen, und es lohnt sich, auf dem Rechner zu messen, den Sie tatsächlich betreiben, statt zu raten. Das allgemeine Muster für das Lösen in großen Mengen, einschließlich der Typen, bei denen die Wartezeit eine Browser-Sitzung und kein Hashing ist, steht in der Anleitung zum parallelen Lösen von Captchas.

Vollständiges lauffähiges Beispiel

# pip install capskip
import os
import requests
from capskip import CapSkip, ApiException, NetworkException, TimeoutException

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

try:
    result = solver.altcha(
        url="https://example.com/signup",
        challenge_url="https://example.com/altcha/challenge",
    )
    # Submit here, while the challenge is still fresh.
    r = requests.post("https://example.com/signup", data={
        "email": "someone@example.com",
        "altcha": result["token"],
    })
    print(r.status_code, "solved with counter", result["number"])
except ApiException as exc:
    # ERROR_CAPTCHA_UNSOLVABLE here means Argon2id or scrypt.
    print("refused:", exc)
except NetworkException:
    print("CapSkip is not answering on that host and port")
except TimeoutException:
    print("gave up waiting, which on this type means something is wrong")

Die anderen Typen haben dieselbe Form mit einer anderen Methode. Es gibt einen Aufruf für reCAPTCHA v2, v3 und Enterprise, einen für Turnstile, einen für GeeTest v3 und einen für Bild-Captchas, und die vollständige Liste finden Sie auf der Python-Captcha-Solver-Seite.

Die Pakete für Node.js, PHP und .NET stellen dieselben Methodennamen bereit. Eine Beschreibung jedes CapSkip SDK finden Sie auf der SDK-Seite.

Häufige Fehler und was sie bedeuten

Was Sie sehenUrsacheBeheben
Ein nackter Verifizierungsfehler von der Website, bei einem Token, der sauber gelöst wurdeDie Challenge ist abgelaufen, bevor das Formular abgeschickt wurdeAbrufen, lösen und absenden innerhalb einer Funktion
ERROR_CAPTCHA_UNSOLVABLE in einer ApiException nach einer DrittelsekundeDie Challenge verlangt Argon2id oder scryptNichts zu wiederholen. Diese beiden werden absichtlich abgelehnt
Eine ValidationException beim AufrufKeiner der beiden Challenge-Parameter wurde übergeben, oder es war einer dabei, den ALTCHA nicht akzeptiertÜbergeben Sie den Challenge-Endpunkt oder das Challenge-Dokument und lassen Sie den Rest weg
Eine NetworkException, bevor überhaupt gehasht wirdCapSkip läuft nicht, oder Host und Port sind falschStarten Sie CapSkip und prüfen Sie dann, ob es im Local-Modus oder im Server-Modus laufen soll
Ein KeyError beim Schlüssel tokenDiesen Schlüssel trägt nur ein ALTCHA-ErgebnisRufen Sie die Methode altcha auf oder lesen Sie den Schlüssel code, der dieselbe Zeichenfolge enthält
Die ersten Lösungen in einem Batch scheitern und die letzten gehen durchDie Challenges wurden vorab abgerufen und liefen in der Warteschlange abRufen Sie innerhalb jedes Tasks ab, nicht vor dem gather
Ein Token, den die Website jedes Mal ablehntIrgendetwas hat die Payload beschnitten oder neu kodiertGeben Sie die Zeichenfolge unverändert weiter

FAQ

Brauche ich Selenium oder Playwright für eine ALTCHA-Seite?

Für den ALTCHA-Teil nicht. Die Challenge ist ein Hashing-Problem, und die Antwort ist eine Zeichenfolge, die Sie in ein Formularfeld schreiben, also genügen requests oder httpx, und das Lösen ist in Millisekunden fertig. Einen Browser brauchen Sie weiterhin, wenn der Rest der Seite einen verlangt, etwa wenn ein Session-Cookie per JavaScript gesetzt wird oder das Formular clientseitig gerendert wird. Behalten Sie in diesem Fall den Browser für die Navigation und rufen Sie für den Token direkt den Solver auf, statt zu versuchen, das Widget zum Laufen zu bringen.

Kann ein Skript auf einer gehosteten Plattform den Solver erreichen?

Ja. Schalten Sie in den Verbindungseinstellungen den Server-Modus ein, damit CapSkip auf einer Netzwerkadresse statt auf Loopback lauscht, und richten Sie dann CAPSKIP_HOST darauf. Ein Docker-Container, ein VPS, ein geplanter Job auf einer Managed-Plattform und ein CI-Runner verbinden sich alle auf dieselbe Weise, über dieselbe HTTP-API. Führt der Weg über das Internet, verwenden Sie eine statische öffentliche IP und eine Firewall-Regel, die nur die erwarteten Adressen zulässt. Der Solver läuft in beiden Fällen weiter auf Ihrer Hardware, die Lizenz und das unbegrenzte Lösen bleiben also unberührt.

Warum lässt sich ALTCHA so viel schneller lösen als reCAPTCHA?

Weil die beiden nach verschiedenen Dingen fragen. reCAPTCHA und Turnstile wollen Belege dafür, dass ein Browser mit einer plausiblen Vorgeschichte anwesend ist, und das braucht eine echte Sitzung und echte Zeit. ALTCHA will nur den Nachweis, dass CPU-Zeit aufgewendet wurde, die Arbeit ist also eine Hash-Schleife mit einer bekannten Abbruchbedingung. Auch deshalb ist die Antwort deterministisch und kein Urteil: Es gibt eine Zahl, die die Challenge erfüllt, und entweder wird sie gefunden, oder die Challenge war defekt. Der Preis dafür ist, dass die Zahl eine oder zwei Minuten später wertlos ist.

Kann ich einen Token über mehrere Anfragen hinweg wiederverwenden?

Nein, und es lohnt sich, deutlich zu sagen, warum. Die Payload ist gegen genau eine Challenge signiert, diese Challenge wird einmal ausgegeben, und der Server verfolgt sie, ein zweites Absenden desselben Tokens ist also genau der Replay, den das Design verhindern soll. Lösen Sie einmal pro Absendung. Das ist hier bezahlbar, anders als bei einem Dienst mit Abrechnung pro Lösung, denn die Arbeit sind ein paar Millisekunden Ihrer eigenen CPU und kein berechneter Aufruf, es gibt also keinen Grund, etwas zwischenzuspeichern, das Sie einfach neu berechnen können.

Die Kurzfassung

Lesen Sie den Challenge-Endpunkt vom Widget ab, übergeben Sie ihn mit der Seiten-URL an die eine ALTCHA-Methode und schreiben Sie den Token ohne Bearbeitung in das Feld namens altcha. Halten Sie Abruf, Lösen und Absenden zusammen, denn eine Challenge kann innerhalb von zwei Minuten sterben, und eine abgelaufene wird als einfacher Verifizierungsfehler gemeldet. Verteilen Sie einen Batch mit dem async-Client und rufen Sie jede Challenge in ihrem eigenen Task ab, statt sie zuerst zu sammeln. ERROR_CAPTCHA_UNSOLVABLE ist nur bei Argon2id und scrypt zu erwarten.

Eine Konsequenz sollten Sie in Ihre Retry-Logik einbauen. Lokal ausgeführt erledigt ein Captcha-Löser das Hashing auf einem Rechner, der Ihnen bereits gehört, ein verschwendeter Lösungsversuch kostet also Millisekunden statt Guthaben. Die richtige Reaktion auf eine veraltete Challenge ist daher, eine neue zu holen und es erneut zu versuchen.