API-Tutorials

Circuit-Breaker-Muster für CAPTCHA-API-Aufrufe

Wenn die CAPTCHA-Lösungs-API langsam ist oder Fehler zurückgibt, verschwendet das weitere Senden von Anfragen Zeit und Geld. Das Circuit-Breaker-Muster stoppt den Aufruf einer fehlerhaften API, wartet auf die Wiederherstellung und wird automatisch fortgesetzt – wodurch kaskadierende Fehler in Ihrer Pipeline verhindert werden.


Wie Leistungsschalter funktionieren

Drei Staaten:

  1. Geschlossen – Normaler Betrieb. Anfragen gehen durch. Ausfälle werden gezählt.
  2. Offen – Zu viele Fehler. Alle Anfragen werden sofort abgelehnt, ohne die API aufzurufen.
  3. Halboffen – Nach einer Abklingzeit wird eine Testanfrage durchgelassen. Gelingt dies, schließt sich der Stromkreis. Fällt dies aus, öffnet sich der Stromkreis erneut.

Python-Implementierung

import time
import threading
import requests

SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
API_KEY = "YOUR_API_KEY"


class CircuitBreaker:
    def __init__(self, failure_threshold=5, recovery_timeout=60):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.failure_count = 0
        self.last_failure_time = 0
        self.state = "closed"  # closed, open, half-open
        self._lock = threading.Lock()

    def call(self, func, *args, **kwargs):
        with self._lock:
            if self.state == "open":
                if time.time() - self.last_failure_time > self.recovery_timeout:
                    self.state = "half-open"
                    print("[circuit] State: half-open — testing one request")
                else:
                    remaining = self.recovery_timeout - (
                        time.time() - self.last_failure_time
                    )
                    raise CircuitOpenError(
                        f"Circuit open — retry in {remaining:.0f}s"
                    )

        try:
            result = func(*args, **kwargs)
            with self._lock:
                self.failure_count = 0
                if self.state == "half-open":
                    print("[circuit] State: closed — API recovered")
                self.state = "closed"
            return result
        except Exception as e:
            with self._lock:
                self.failure_count += 1
                self.last_failure_time = time.time()
                if self.failure_count >= self.failure_threshold:
                    self.state = "open"
                    print(
                        f"[circuit] State: open — "
                        f"{self.failure_count} failures"
                    )
            raise


class CircuitOpenError(Exception):
    pass


def solve_captcha(sitekey, page_url):
    resp = requests.post(SUBMIT_URL, data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "json": "1",
    }, timeout=15)
    data = resp.json()
    if data["status"] != 1:
        raise Exception(f"Submit error: {data['request']}")

    task_id = data["request"]
    for _ in range(24):
        time.sleep(5)
        poll = requests.get(RESULT_URL, params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": "1",
        }, timeout=15).json()
        if poll["status"] == 1:
            return poll["request"]
        if poll["request"] != "CAPCHA_NOT_READY":
            raise Exception(f"Poll error: {poll['request']}")
    raise TimeoutError(f"Task {task_id} timed out")


# Usage
breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=30)

for i in range(10):
    try:
        token = breaker.call(
            solve_captcha, "6Le-SITEKEY", "https://example.com"
        )
        print(f"[task-{i}] Solved: {token[:40]}...")
    except CircuitOpenError as e:
        print(f"[task-{i}] Skipped: {e}")
    except Exception as e:
        print(f"[task-{i}] Failed: {e}")

Erwartete Ausgabe:

[task-0] Solved: 03AGdBq26ZfPxL...
[task-1] Solved: 03AGdBq27AbCdE...
[task-2] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[task-3] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[task-4] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[circuit] State: open — 3 failures
[task-5] Skipped: Circuit open — retry in 28s
[task-6] Skipped: Circuit open — retry in 25s
...
[circuit] State: half-open — testing one request
[task-8] Solved: 03AGdBq28FgHiJ...
[circuit] State: closed — API recovered

JavaScript-Implementierung

class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.recoveryTimeout = options.recoveryTimeout || 60000;
    this.failureCount = 0;
    this.lastFailureTime = 0;
    this.state = 'closed';
  }

  async call(fn, ...args) {
    if (this.state === 'open') {
      if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
        this.state = 'half-open';
        console.log('[circuit] State: half-open');
      } else {
        const remaining = this.recoveryTimeout - (Date.now() - this.lastFailureTime);
        throw new Error(`Circuit open — retry in ${Math.ceil(remaining / 1000)}s`);
      }
    }

    try {
      const result = await fn(...args);
      this.failureCount = 0;
      if (this.state === 'half-open') {
        console.log('[circuit] State: closed — recovered');
      }
      this.state = 'closed';
      return result;
    } catch (error) {
      this.failureCount++;
      this.lastFailureTime = Date.now();
      if (this.failureCount >= this.failureThreshold) {
        this.state = 'open';
        console.log(`[circuit] State: open — ${this.failureCount} failures`);
      }
      throw error;
    }
  }
}

// Usage
const axios = require('axios');

const API_KEY = 'YOUR_API_KEY';
const breaker = new CircuitBreaker({ failureThreshold: 3, recoveryTimeout: 30000 });

async function solveCaptcha(sitekey, pageurl) {
  const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
    params: { key: API_KEY, method: 'userrecaptcha', googlekey: sitekey, 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: API_KEY, 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');
}

(async () => {
  for (let i = 0; i < 10; i++) {
    try {
      const token = await breaker.call(solveCaptcha, '6Le-SITEKEY', 'https://example.com');
      console.log(`[task-${i}] Solved: ${token.substring(0, 40)}...`);
    } catch (err) {
      console.log(`[task-${i}] ${err.message}`);
    }
  }
})();

Schwellenwerte wählen

Parameter Geringer Verkehr (< 10/min) Hoher Traffic (> 100/min)
failure_threshold 3 10
recovery_timeout 30er Jahre 60er Jahre

Legen Sie den Fehlerschwellenwert hoch genug fest, um intermittierende Fehler zu tolerieren (ein einziger Timeout sollte den Schaltkreis nicht auslösen), aber niedrig genug, um eine fehlerhafte API nicht mehr zu belasten.


Kombination mit Wiederholungslogik

Verwenden Sie die Wiederholungslogik innerhalb des Leistungsschalters. Der Leistungsschalter zählt die endgültigen Ausfälle (nachdem die Wiederholungsversuche ausgeschöpft sind):

def solve_with_retry(sitekey, page_url, max_retries=2):
    for attempt in range(max_retries + 1):
        try:
            return solve_captcha(sitekey, page_url)
        except Exception:
            if attempt == max_retries:
                raise
            time.sleep(2 ** attempt)

# Circuit breaker wraps the retry function
token = breaker.call(solve_with_retry, "6Le-SITEKEY", "https://example.com")

Fehlerbehebung

Problem Ursache Lösung
Der Stromkreis öffnet sich zu schnell Schwelle zu niedrig Erhöhen Sie failure_threshold
Der Stromkreis erholt sich nie wieder recovery_timeout zu lang Auf 30-60 Sekunden reduzieren
Racebedingung im Multithread-Einsatz Kein Lock-on-Status Verwenden Sie threading.Lock (Python) oder atomare Operationen
Alle Anfragen wurden während eines teilweisen Ausfalls blockiert Ein einziger Leistungsschalter für alle Endpunkte Verwenden Sie separate Leistungsschalter für Submit- und Poll-Endpunkte

FAQ

Sollte ich für Übermittlung und Abfrage separate Leistungsschalter verwenden?

Ja, für Großanlagen. Der Übermittlungsendpunkt schlägt möglicherweise fehl, während die Abfrage noch funktioniert (oder umgekehrt). Separate Leistungsschalter sorgen für eine feinere Kontrolle.

Was soll ich tun, wenn der Stromkreis offen ist?

Stellen Sie die CAPTCHA-Aufgabe für später in die Warteschlange, zeigen Sie eine Fallback-Benutzeroberfläche an oder überspringen Sie den Vorgang. SehenAnmutige Verschlechterung, wenn die Lösung fehlschlägt.


Erstellen Sie belastbare CAPTCHA-Workflows mit CaptchaAI

Holen Sie sich Ihren API-Schlüssel unter captchaai.com.


Verwandte Leitfäden

Diskussionen (0)

Noch keine Kommentare.