API-Tutorials

Graceful Degradation, wenn die CAPTCHA-Lösung fehlschlägt

Ob ein einzelner Solve fehlschlägt, entscheidet selten über den Erfolg einer Automatisierung – entscheidend ist, wie Ihre Pipeline darauf reagiert. Mit Graceful Degradation läuft der Prozess weiter, statt beim ersten Fehler abzustürzen: Sie überspringen die betroffene Aufgabe, stellen sie in eine Queue, drosseln die Frequenz oder weichen auf eine Alternative aus.

Timeouts, ungültige Parameter, ein aufgebrauchtes Guthaben oder Rate-Limiting treten im Dauerbetrieb zwangsläufig auf. Der Trick ist nicht, jeden Fehler zu verstecken, sondern pro Workflow die am wenigsten schädliche Reaktion zu wählen – ein nächtlicher Scraping-Batch auf Hetzner-Workern verträgt andere Kompromisse als ein Live-Checkout oder ein E2E-Test in der GitLab-CI-Pipeline.

Welches Fehlerverhalten passt zu welchem Workflow?

Bevor Sie Code schreiben, ordnen Sie den Workflow einem Muster zu. Die falsche Reaktion ist teuer: Ein Batch-Job, der bei jedem Fehler stoppt, verschenkt Durchsatz; ein Live-Dienst, der stur weiter-versucht, reißt sich selbst in die Tiefe.

Workflow Empfohlenes Muster Warum
Große Batch-Läufe mit tolerierbaren Einzelfehlern Überspringen und weitermachen Hält den Durchsatz hoch, statt alles zu blockieren
Wichtige Aufgaben, die später gelingen können Wiederholungs-Queue Sammelt transiente Fehler sauber für einen zweiten Anlauf
Dauerhaft laufende Dienste mit Kaskadenrisiko Degradierter Modus Verhindert, dass eine gestörte Solve-Kette den ganzen Dienst mitreißt
Kritische Pfade mit Ausweichoption Degradierter Modus + Fallback Erzwingt eine bewusste Ausweichstrategie statt stillem Totalausfall

Typische Fehlermodi und ihre passende Reaktion

Nicht jeder Fehler verdient dieselbe Behandlung. Ein ERROR_BAD_PARAMETERS wird auch beim zehnten Versuch scheitern, während ein Timeout beim nächsten Anlauf oft durchgeht. Die CaptchaAI-API meldet den Grund im request-Feld – werten Sie ihn aus, statt pauschal zu wiederholen.

Fehlermodus Fehlercode Empfohlene Reaktion
Timeout / Zeitüberschreitung CAPCHA_NOT_READY (Polling-Limit erreicht) Mit frischer Abfrage erneut versuchen
Ungültige Parameter ERROR_BAD_PARAMETERS Protokollieren und überspringen – Extraktion prüfen
Falscher Sitekey ERROR_WRONG_GOOGLEKEY Sitekey neu auslesen
Guthaben aufgebraucht ERROR_ZERO_BALANCE Pausieren, Alarm auslösen, auf Aufladung warten
Rate-Limiting ERROR_TOO_MUCH_REQUESTS Exponentielles Backoff
API nicht erreichbar Verbindungsfehler Circuit Breaker + erneuter Versuch

Muster 1: Überspringen und weitermachen

Für Batch-Vorgänge, bei denen einzelne Fehler akzeptabel sind, geben Sie bei einem Misserfolg schlicht None zurück und protokollieren die übersprungene URL – statt die gesamte Schleife abzubrechen:

import requests
import time

API_KEY = "YOUR_API_KEY"


def solve_or_skip(captcha_type, sitekey, page_url, max_retries=2):
    """Try to solve; return None on failure instead of crashing."""
    for attempt in range(max_retries):
        try:
            token = solve_captcha(captcha_type, sitekey, page_url)
            if token:
                return token
        except Exception as e:
            print(f"Attempt {attempt + 1} failed: {e}")

    return None  # Skip this item


def process_urls(urls):
    results = []
    skipped = []

    for url in urls:
        sitekey = extract_sitekey(url)
        if not sitekey:
            skipped.append({"url": url, "reason": "no_sitekey"})
            continue

        token = solve_or_skip("recaptcha_v2", sitekey, url)
        if token:
            data = submit_form(url, token)
            results.append({"url": url, "data": data})
        else:
            skipped.append({"url": url, "reason": "solve_failed"})

    print(f"Processed: {len(results)}, Skipped: {len(skipped)}")
    return results, skipped

Führen Sie die skipped-Liste sauber mit: Sie ist am Ende des Laufs Ihre Wiedervorlage. Bei einer nächtlichen Extraktion über mehrere tausend Seiten zählt die Quote der erfolgreichen Aufgaben, nicht die einzelne verlorene URL.


Muster 2: Wiederholungs-Queue

Fehlgeschlagene Aufgaben landen in einer Wiederholungs-Queue und werden später erneut verarbeitet – mit steigendem Backoff pro Versuch und einer Obergrenze, damit dauerhaft kaputte Aufgaben nicht ewig kreisen:

from collections import deque
import json

class RetryQueue:
    def __init__(self, max_retries=3, backoff_base=60):
        self.queue = deque()
        self.max_retries = max_retries
        self.backoff_base = backoff_base

    def add(self, task):
        task["retry_count"] = task.get("retry_count", 0) + 1
        if task["retry_count"] <= self.max_retries:
            task["retry_after"] = time.time() + (
                self.backoff_base * task["retry_count"]
            )
            self.queue.append(task)
            return True
        return False  # Exceeded max retries

    def get_ready(self):
        """Get tasks ready for retry."""
        ready = []
        remaining = deque()
        now = time.time()

        while self.queue:
            task = self.queue.popleft()
            if task["retry_after"] <= now:
                ready.append(task)
            else:
                remaining.append(task)

        self.queue = remaining
        return ready

    def save(self, filepath="retry_queue.json"):
        with open(filepath, "w") as f:
            json.dump(list(self.queue), f)

    def load(self, filepath="retry_queue.json"):
        try:
            with open(filepath) as f:
                self.queue = deque(json.load(f))
        except FileNotFoundError:
            pass


# Usage
retry_q = RetryQueue()

def process_with_retry(task):
    try:
        token = solve_captcha(task["type"], task["sitekey"], task["url"])
        if token:
            return submit_form(task["url"], token)
        else:
            retry_q.add(task)
    except Exception:
        retry_q.add(task)

# Process retry queue periodically
def drain_retry_queue():
    ready = retry_q.get_ready()
    for task in ready:
        process_with_retry(task)

Über save() und load() überdauert die Queue einen Neustart – ohne Persistenz gehen alle offenen Aufgaben verloren. Erreicht eine Aufgabe die maximale Versuchszahl, verschieben Sie sie in eine Dead-Letter-Queue, statt sie stillschweigend zu verwerfen.


Muster 3: Degradierter Modus

Häufen sich die Fehler, ist stures Weiter-Versuchen kontraproduktiv: Jeder Aufruf kostet Zeit und belastet einen ohnehin gestörten Dienst zusätzlich. Der degradierte Modus zählt aufeinanderfolgende Fehler und schaltet nach einer Schwelle für einige Minuten in ein eingeschränktes Verhalten:

class CaptchaSolver:
    def __init__(self, api_key):
        self.api_key = api_key
        self.degraded = False
        self.failure_count = 0
        self.failure_threshold = 5
        self.recovery_time = None

    def solve(self, captcha_type, sitekey, page_url):
        if self.degraded:
            if time.time() < self.recovery_time:
                return self._degraded_action(page_url)
            else:
                self.degraded = False
                self.failure_count = 0

        try:
            token = self._solve_api(captcha_type, sitekey, page_url)
            self.failure_count = 0
            return token
        except Exception as e:
            self.failure_count += 1
            if self.failure_count >= self.failure_threshold:
                self._enter_degraded_mode()
            raise

    def _enter_degraded_mode(self):
        self.degraded = True
        self.recovery_time = time.time() + 300  # 5 min
        print("Entering degraded mode for 5 minutes")
        # Send alert

    def _degraded_action(self, url):
        """What to do when solving is unavailable."""
        # Option A: Skip CAPTCHA pages entirely
        return None

        # Option B: Queue for later
        # retry_queue.add({"url": url, ...})
        # return None

        # Option C: Try alternative solver
        # return self._solve_with_backup_api(...)

    def _solve_api(self, captcha_type, sitekey, page_url):
        # Normal CaptchaAI API call
        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": page_url,
            "json": "1",
        }).json()

        if resp["status"] != 1:
            raise Exception(resp["request"])

        task_id = resp["request"]
        for _ in range(24):
            time.sleep(5)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key, "action": "get",
                "id": task_id, "json": "1"
            }).json()
            if result["status"] == 1:
                return result["request"]
            if result["request"] != "CAPCHA_NOT_READY":
                raise Exception(result["request"])

        raise Exception("TIMEOUT")

_degraded_action bündelt Ihre Ausweichoptionen: Aufgabe überspringen, für später in die Queue legen oder auf einen zweiten Anbieter umschalten. Wichtig ist, dass die Erholungszeit endlich ist – nach fünf Minuten läuft ein regulärer Versuch, der den Modus bei Erfolg automatisch beendet.


Node.js: Kombiniertes Muster

In JavaScript lassen sich Retry-Queue und degradierter Modus in einer Klasse zusammenführen. Der Sonderfall ERROR_ZERO_BALANCE verdient eine eigene, längere Sperre – ein erneuter Versuch bringt nichts, solange kein Guthaben nachgeladen ist:

class ResilientSolver {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.retryQueue = [];
    this.failureCount = 0;
    this.degraded = false;
  }

  async solve(type, sitekey, pageUrl) {
    if (this.degraded) {
      this.retryQueue.push({ type, sitekey, pageUrl, addedAt: Date.now() });
      return null;
    }

    try {
      const token = await this._callApi(type, sitekey, pageUrl);
      this.failureCount = 0;
      return token;
    } catch (err) {
      this.failureCount++;

      if (err.message === 'ERROR_ZERO_BALANCE') {
        this._enterDegraded(600000); // 10 min
        return null;
      }

      if (this.failureCount >= 5) {
        this._enterDegraded(300000); // 5 min
      }

      this.retryQueue.push({ type, sitekey, pageUrl, addedAt: Date.now() });
      return null;
    }
  }

  _enterDegraded(durationMs) {
    this.degraded = true;
    console.warn(`Degraded mode for ${durationMs / 1000}s`);
    setTimeout(() => {
      this.degraded = false;
      this.failureCount = 0;
      this.drainRetryQueue();
    }, durationMs);
  }

  async drainRetryQueue() {
    const tasks = this.retryQueue.splice(0);
    for (const task of tasks) {
      await this.solve(task.type, task.sitekey, task.pageUrl);
    }
  }

  async _callApi(type, sitekey, pageUrl) {
    // Standard submit + poll
    const axios = require('axios');
    const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
      params: { key: this.apiKey, method: 'userrecaptcha', googlekey: sitekey, pageurl: pageUrl, json: 1 },
    });
    if (submit.data.status !== 1) throw new Error(submit.data.request);

    const taskId = submit.data.request;
    for (let i = 0; i < 24; i++) {
      await new Promise(r => setTimeout(r, 5000));
      const poll = await axios.get('https://ocr.captchaai.com/res.php', {
        params: { key: this.apiKey, action: 'get', id: taskId, json: 1 },
      });
      if (poll.data.status === 1) return poll.data.request;
      if (poll.data.request !== 'CAPCHA_NOT_READY') throw new Error(poll.data.request);
    }
    throw new Error('TIMEOUT');
  }
}

Sobald der degradierte Modus endet, leert drainRetryQueue() die gesammelten Aufgaben in einem Rutsch. So verlieren Sie während der Störung keine Arbeit und fahren die Last kontrolliert wieder hoch.


Häufige Probleme und ihre Lösung

Problem Ursache Lösung
Alle Aufgaben werden übersprungen Degradierter Modus schlägt zu aggressiv an Fehlerschwelle erhöhen
Retry-Queue wächst endlos Aufgaben gelingen nie Maximale Versuche festlegen; in eine Dead-Letter-Queue verschieben
Erholung dauert zu lange Zu langes Degradations-Timeout Erholungszeit verkürzen; Health-Check-Probe ergänzen
Aufgaben gehen beim Neustart verloren In-Memory-Queue Queue in Datei oder Datenbank persistieren

FAQ

Wann sollte ich einen fehlgeschlagenen Solve erneut versuchen – und wann nicht?

Nur bei transienten Fehlern. Timeouts (CAPCHA_NOT_READY) und Rate-Limiting (ERROR_TOO_MUCH_REQUESTS) gehen beim nächsten Anlauf oft durch. ERROR_BAD_PARAMETERS und ERROR_WRONG_GOOGLEKEY sind dagegen deterministische Fehler – hier hilft nur, die Extraktion oder den Sitekey zu korrigieren, nicht ein weiterer Versuch.

Wie verhindere ich, dass meine Retry-Queue endlos wächst?

Setzen Sie eine harte Obergrenze pro Aufgabe (max_retries) und leiten Sie alles, was sie überschreitet, in eine Dead-Letter-Queue. So bleiben chronisch fehlerhafte Aufgaben zur manuellen Prüfung erhalten, blockieren aber nicht den laufenden Betrieb.

Bleiben offene Aufgaben bei einem Neustart erhalten?

Nur, wenn Sie die Queue persistieren. Eine reine In-Memory-Queue ist nach einem Neustart leer. Schreiben Sie den Zustand regelmäßig in eine Datei, Redis oder eine Datenbank und laden Sie ihn beim Start – so überlebt Ihr Fallback auch ein Deployment.

Ändert die Thread-basierte Abrechnung von CaptchaAI mein Fehlerbudget?

CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro Lösung – ein erneuter Versuch verursacht also keine zusätzlichen Kosten pro Solve. Ihr Durchsatz ist durch die Zahl der Threads Ihres Tarifs begrenzt; ein aggressives Backoff schont in erster Linie den Zieldienst, nicht Ihr Guthaben.


Robuste CAPTCHA-Automatisierung mit CaptchaAI aufbauen

Holen Sie sich Ihren API-Schlüssel unter captchaai.com und bauen Sie die Fallback-Muster von Anfang an in Ihre Pipeline ein.


Verwandte Leitfäden

Kommentare sind für diesen Artikel deaktiviert.