Scarnox · Blog

Fehlerbehandlung für Captcha-Solver: Wiederholungen, Backoff und was man nie wiederholt

Ein Captcha-Lösungsaufruf ist ein Netzwerkaufruf an ein System, das Sie nicht kontrollieren, gegen eine Challenge, die sich ohne Vorwarnung ändern kann. Fehler gut zu behandeln entscheidet darüber, ob eine Pipeline sanft degradiert oder in einem Wiederholungs-Sturm Zeit verschwendet.

Bei Scarnox hilft Ihnen die Wirtschaftlichkeit: Fehlgeschlagene Lösungen sind kostenlos, eine Wiederholung kostet also nur Zeit und sonst nichts. Aber einen falschen Fehler zu wiederholen verschwendet trotzdem diese Zeit, und zu hartnäckiges Wiederholen kann eigene Probleme verursachen. Dieser Leitfaden gibt Ihnen eine Wiederholungsstrategie, die Sie in einen Python-Job einfügen können.

Scarnox löst heute PopularCaptcha. Dieselbe Fehlerbehandlung gilt für jeden Captcha-Solver, die Strategie unten hängt also nicht vom Captcha-Typ ab.

Immer zuerst errorId prüfen

Eine Lösungsantwort hat ein Feld errorId. 0 bedeutet Erfolg, und das Token steht unter solution.token. Jeder andere Wert bedeutet, dass die Lösung fehlgeschlagen ist. Eine fehlgeschlagene Antwort enthält außerdem errorCode und errorDescription. Protokollieren Sie beides: Den Code suchen Sie später, und die Beschreibung lesen Sie, wenn ein Lauf schiefgeht.

{"errorId": 1, "errorCode": "...", "errorDescription": "..."}

Gehen Sie nie davon aus, dass eine Antwort erfolgreich ist, weil der HTTP-Status 200 lautet. Prüfen Sie errorId jedes Mal.

Fehler, die Sie wiederholen sollten

Manche Fehler sind vorübergehend. Wiederholen Sie diese mit exponentiellem Backoff und einer kleinen Zufallsverzögerung:

Diese Fehler lohnen eine Wiederholung, weil der nächste Versuch ohne Änderung auf Ihrer Seite gelingen kann.

Fehler, die Sie vor einer Wiederholung beheben müssen

Andere Fehler verschwinden nicht durch Warten. Eine Wiederholung mit derselben Eingabe scheitert genauso. Beheben Sie diese zuerst:

Wenn ein Fehler auf keiner der beiden Listen steht, behandeln Sie ihn wie einen Fehler, der zuerst behoben werden muss. Lesen Sie den Fehlertext, entscheiden Sie und nehmen Sie den Code in die richtige Liste in Ihrem Code auf. Halten Sie die Listen kurz und regelmäßig überprüft.

Eine Richtlinie zum Einfügen

import os
import random
import time

import requests

API_URL = "https://api.scarnox.com/api/tasks/create"
HEADERS = {"Authorization": f"Bearer {os.environ['SCARNOX_KEY']}"}
MAX_ATTEMPTS = 4

class SolveError(Exception):
    def __init__(self, code: str, description: str, retryable: bool):
        super().__init__(f"{code}: {description}")
        self.code = code
        self.retryable = retryable

def solve_once(payload: dict) -> str:
    try:
        r = requests.post(API_URL, headers=HEADERS, json=payload, timeout=185)
    except requests.RequestException as exc:
        # Netzwerkfehler oder Timeout auf unserer Seite: Wiederholen ist sicher.
        raise SolveError("NETWORK", str(exc), retryable=True)

    if r.status_code == 429 or r.status_code >= 500:
        raise SolveError(f"HTTP_{r.status_code}", r.text[:200], retryable=True)

    try:
        data = r.json()
    except ValueError:
        raise SolveError(f"HTTP_{r.status_code}", r.text[:200], retryable=False)

    if data.get("errorId") == 0:
        return data["solution"]["token"]

    # Die API hat einen Fehler gemeldet. Nur Codes wiederholen, die Sie als vorübergehend erkannt haben.
    code = data.get("errorCode", "UNKNOWN")
    retryable = code in TRANSIENT_CODES
    raise SolveError(code, data.get("errorDescription", ""), retryable=retryable)

# Fügen Sie API-Fehlercodes hier erst hinzu, nachdem Sie in Ihren Logs gesehen haben, dass sie vorübergehend sind.
TRANSIENT_CODES: set[str] = set()

def solve(payload: dict) -> str:
    for attempt in range(MAX_ATTEMPTS):
        try:
            return solve_once(payload)
        except SolveError as err:
            last_attempt = attempt == MAX_ATTEMPTS - 1
            if not err.retryable or last_attempt:
                raise
            delay = min(30, 2 ** attempt) + random.random()  # Backoff + Jitter
            print(f"attempt {attempt + 1} failed ({err.code}), retrying in {delay:.1f}s")
            time.sleep(delay)
    raise RuntimeError("unreachable")

Die Richtlinie wiederholt nie einen Fehler, der zuerst behoben werden muss, und sie stoppt nach einer festen Zahl von Versuchen. Die Menge TRANSIENT_CODES startet absichtlich leer: Nehmen Sie einen Code erst auf, nachdem Ihre Logs zeigen, dass er vorübergehend ist.

Warum Jitter wichtig ist

Wenn fünfzig Worker gleichzeitig fehlschlagen und alle exakt zwei Sekunden warten, wiederholen sie alle gemeinsam und scheitern gemeinsam erneut. Ein zufälliger Bruchteil einer Sekunde verteilt sie. Das ist derselbe Rat, den die meisten großen API-Anbieter für ihre eigenen Clients geben.

Timeouts

Setzen Sie Ihr HTTP-Timeout deutlich über der typischen Lösungszeit, etwa 185 Sekunden. Die Anfrage blockiert, bis das Token bereit ist. Ein kurzes Client-Timeout kann einen langsamen Erfolg in einen Fehler verwandeln, den Sie dann sinnlos wiederholen. Ein Client-Timeout, das länger ist als die Lösung, ist die sichere Einstellung.

Tokens sind nicht cachebar

Ein Token läuft innerhalb von etwa zwei Minuten ab. Speichern Sie Tokens nicht zwischen Läufen und verwenden Sie keines für eine zweite Absendung. Wenn ein Absenden fehlschlägt, nachdem das Token verbraucht wurde, lösen Sie neu, statt das alte Token erneut abzuschicken. Der Leitfaden Ablauf von Captcha-Tokens behandelt das Timing ausführlicher.

Parallelität und Backoff über mehrere Worker

Teilen sich viele Worker einen Schlüssel, verlangsamt der Backoff eines Workers die anderen nicht. Fügen Sie ein gemeinsames Limit für gleichzeitige Lösungen hinzu, etwa einen Semaphor oder eine Warteschlange, damit ein Schwall von Wiederholungen nicht zu einem Schwall von Anfragen wird. Eine einfache Begrenzung der Parallelität verhindert die meisten Ratenbegrenzungsfehler, bevor sie entstehen.

Protokollieren, was hilft

Protokollieren Sie für jeden fehlgeschlagenen Versuch den errorCode, die Versuchsnummer, den Sitekey, den Host der Seite und ob der Fehler wiederholt wurde. Protokollieren Sie nicht den API-Schlüssel oder ein Proxy-Passwort. Ein Dashboard, das Codes pro Stunde zählt, zeigt Ihnen schnell, ob ein Problem in Ihrem Code, im Proxy oder beim Dienst liegt.

Nächste Schritte

Testen Sie Ihre Fehlerbehandlung mit einer kostenlosen Testphase: Erstellen Sie ein Konto und starten Sie mit einer kostenlosen Testphase, ganz ohne Karte.