Tutorials

Testen von CaptchaAI vor der vollständigen Migration: Parallellaufhandbuch

Sie können einen neuen CAPTCHA-Anbieter vollständig bewerten, bevor Sie Ihre Produktion dauerhaft umstellen: Lassen Sie CaptchaAI und Ihren bisherigen Dienst dieselben CAPTCHA-Abfragen gleichzeitig lösen und vergleichen Sie Erfolgsquote, Geschwindigkeit und Kosten anhand Ihres echten Traffics. Genau das leistet ein Parallellauf – ein kontrollierter Test, der aus einer Migration eine Entscheidung mit Zahlen statt einer Vermutung macht.

Dieser Leitfaden zeigt den kompletten Aufbau: eine austauschbare Anbieter-Schnittstelle, einen Runner, der beide Dienste gleichzeitig misst, einen schrittweisen Traffic-Split für die Produktion und die Kennzahlen, auf die es beim Umstieg wirklich ankommt. Die Beispiele liegen in Python und JavaScript vor.

Warum Marketing-Benchmarks Ihre Migration nicht absichern

Die Zahlen auf einer Anbieter-Website beschreiben deren Traffic, nicht Ihren. Ihre Erfolgsquote hängt von Faktoren ab, die kein Fremd-Benchmark kennt: Ihre Sitekeys, Ihre Proxy-Konfiguration, die geografische Verteilung Ihrer Anfragen und die Lastspitzen im Tagesverlauf. Erst ein Test mit Ihrer tatsächlichen Arbeitslast zeigt die realen Unterschiede.

Ein Hinweis für DACH-Teams: Führen Sie den Testlauf von einem Server in der Zielregion aus – etwa einer Hetzner- oder netcup-Instanz in Deutschland – und nicht vom lokalen Rechner, damit die Latenz Ihrer Produktion entspricht. Beim Testen mit Residential-Proxys gilt zudem die DSGVO: IP-Adressen sind personenbezogene Daten.

Der Testplan in vier Phasen

Migrieren Sie nicht in einem Schritt, sondern erhöhen Sie den Anteil von CaptchaAI stufenweise – von der reinen Kompatibilitätsprüfung bis zur vollständigen Umstellung:

  • Validierung – nur parallel, kein Live-Traffic; prüfen, ob die API-Antworten kompatibel sind.
  • Schattentest – ein kleiner Anteil auf CaptchaAI mit Fallback, um Basismetriken zu sammeln.
  • Hochfahren – den Anteil stufenweise erhöhen und auf jeder Stufe überwachen.
  • Vollständige Umstellung – 100 % auf CaptchaAI, alten Anbieter außer Betrieb nehmen.
Phase Dauer Verkehrsaufteilung Ziel
1. Validierung 1 Tag 0 % live, nur parallel API-Kompatibilität überprüfen
2. Schattentest 3 Tage 5 % auf CaptchaAI (mit Fallback) Basismetriken sammeln
3. Hochfahren 1 Woche 25 % → 50 % → 75 % Auf jeder Stufe überwachen
4. Vollständige Umstellung 100 % CaptchaAI Alten Anbieter außer Betrieb nehmen

Welche Metriken Sie vergleichen sollten

Die Erfolgsquote allein entscheidet nichts – erst zusammen mit Lösungszeit, Fehlerverteilung und Kosten pro Lösung ergibt sich ein belastbares Bild. Diese Kennzahlen erhebt der Runner in den folgenden Abschnitten automatisch.

Metrik So messen Sie
Erfolgsquote successful_solves / total_attempts × 100
Durchschnittliche Lösungszeit Zeit von der Übermittlung bis zum Erhalt der Lösung
P95-Lösungszeit 95. Perzentil der Lösungszeiten
Fehlerquote nach Typ Jeden Fehlercode separat zählen
Kosten pro Lösung Gesamtausgaben / erfolgreiche Lösungen
Token-Gültigkeit Funktioniert das zurückgegebene Token tatsächlich auf der Zielseite?

Architektur des Testaufbaus

                    ┌──────────────┐
                    │ Your App     │
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │ CAPTCHA      │
                    │ Router       │
                    └──┬───────┬───┘
                       │       │
              ┌────────▼──┐ ┌──▼────────┐
              │ Current   │ │ CaptchaAI │
              │ Provider  │ │           │
              └────────┬──┘ └──┬────────┘
                       │       │
                    ┌──▼───────▼──┐
                    │ Metrics     │
                    │ Collector   │
                    └─────────────┘

Python-Implementierung

Anbieter als austauschbare Schnittstelle

Kapseln Sie beide Dienste hinter einer einzigen Klasse. Da CaptchaAI die vertrauten in.php- und res.php-Endpunkte nutzt, unterscheiden sich die Anbieter nur in Basis-URL und API-Schlüssel.

import os
import time
import requests
from dataclasses import dataclass, field
from typing import Optional
from concurrent.futures import ThreadPoolExecutor


@dataclass
class SolveResult:
    provider: str
    success: bool
    solution: Optional[str] = None
    error: Optional[str] = None
    elapsed: float = 0.0
    cost: float = 0.0


class CaptchaProvider:
    def __init__(self, name, submit_url, result_url, api_key):
        self.name = name
        self.submit_url = submit_url
        self.result_url = result_url
        self.api_key = api_key
        self.session = requests.Session()

    def solve_recaptcha(self, sitekey, pageurl):
        start = time.time()

        resp = self.session.post(self.submit_url, data={
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": pageurl,
            "json": 1
        })
        data = resp.json()
        if data.get("status") != 1:
            return SolveResult(
                provider=self.name, success=False,
                error=data.get("request"), elapsed=time.time() - start
            )

        captcha_id = data["request"]

        for _ in range(60):
            time.sleep(5)
            result = self.session.get(self.result_url, params={
                "key": self.api_key, "action": "get",
                "id": captcha_id, "json": 1
            }).json()

            if result.get("status") == 1:
                return SolveResult(
                    provider=self.name, success=True,
                    solution=result["request"], elapsed=time.time() - start
                )
            if result.get("request") != "CAPCHA_NOT_READY":
                return SolveResult(
                    provider=self.name, success=False,
                    error=result.get("request"), elapsed=time.time() - start
                )

        return SolveResult(
            provider=self.name, success=False,
            error="TIMEOUT", elapsed=time.time() - start
        )

Beide Anbieter parallel ausführen

Der Runner schickt jede Abfrage gleichzeitig an beide Dienste und erzeugt aus Erfolg, Lösungszeit und Fehlercode einen Vergleichsbericht.

class ParallelTestRunner:
    def __init__(self, primary, challenger):
        self.primary = primary
        self.challenger = challenger
        self.results = {"primary": [], "challenger": []}

    def run_test(self, sitekey, pageurl, num_runs=20):
        print(f"Running {num_runs} parallel solves...")

        for i in range(num_runs):
            with ThreadPoolExecutor(max_workers=2) as executor:
                primary_future = executor.submit(
                    self.primary.solve_recaptcha, sitekey, pageurl
                )
                challenger_future = executor.submit(
                    self.challenger.solve_recaptcha, sitekey, pageurl
                )

                primary_result = primary_future.result()
                challenger_result = challenger_future.result()

            self.results["primary"].append(primary_result)
            self.results["challenger"].append(challenger_result)

            print(f"  Run {i+1}/{num_runs}: "
                  f"{self.primary.name}={'OK' if primary_result.success else 'FAIL'} "
                  f"({primary_result.elapsed:.1f}s) | "
                  f"{self.challenger.name}={'OK' if challenger_result.success else 'FAIL'} "
                  f"({challenger_result.elapsed:.1f}s)")

        return self.generate_report()

    def generate_report(self):
        report = {}
        for label, results in self.results.items():
            total = len(results)
            successes = sum(1 for r in results if r.success)
            times = [r.elapsed for r in results if r.success]
            errors = [r.error for r in results if not r.success]

            report[label] = {
                "provider": results[0].provider if results else "unknown",
                "total": total,
                "successes": successes,
                "success_rate": (successes / total * 100) if total else 0,
                "avg_time": sum(times) / len(times) if times else 0,
                "min_time": min(times) if times else 0,
                "max_time": max(times) if times else 0,
                "errors": errors
            }

        return report


# Usage
current = CaptchaProvider(
    name="CurrentProvider",
    submit_url="https://current-provider.com/in.php",
    result_url="https://current-provider.com/res.php",
    api_key="current_key"
)

captchaai = CaptchaProvider(
    name="CaptchaAI",
    submit_url="https://ocr.captchaai.com/in.php",
    result_url="https://ocr.captchaai.com/res.php",
    api_key=os.environ["CAPTCHAAI_API_KEY"]
)

runner = ParallelTestRunner(primary=current, challenger=captchaai)
report = runner.run_test(
    sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
    pageurl="https://example.com/form",
    num_runs=20
)

for label, stats in report.items():
    print(f"\n{stats['provider']}:")
    print(f"  Success rate: {stats['success_rate']:.1f}%")
    print(f"  Avg time: {stats['avg_time']:.1f}s")
    print(f"  Min/Max: {stats['min_time']:.1f}s / {stats['max_time']:.1f}s")
    if stats['errors']:
        print(f"  Errors: {stats['errors']}")

Datenverkehr schrittweise umleiten

Sobald die Messung überzeugt, leiten Sie in der Produktion zunächst nur einen kleinen Prozentsatz des Verkehrs an CaptchaAI – mit automatischem Fallback auf den bisherigen Anbieter bei einem Fehlschlag.

import random


class TrafficSplitter:
    def __init__(self, primary, challenger, challenger_pct=10):
        self.primary = primary
        self.challenger = challenger
        self.challenger_pct = challenger_pct

    def solve(self, sitekey, pageurl):
        if random.randint(1, 100) <= self.challenger_pct:
            result = self.challenger.solve_recaptcha(sitekey, pageurl)
            if not result.success:
                # Fall back to primary on failure
                return self.primary.solve_recaptcha(sitekey, pageurl)
            return result
        return self.primary.solve_recaptcha(sitekey, pageurl)


# Start with 10%, increase as confidence builds
splitter = TrafficSplitter(current, captchaai, challenger_pct=10)
result = splitter.solve(sitekey="...", pageurl="...")

JavaScript-Implementierung

Wenn Ihr Stack auf Node.js läuft, bildet dieselbe Logik den Parallellauf ab: eine CaptchaProvider-Klasse pro Dienst und ein Promise.all, das beide Anbieter gleichzeitig aufruft und die Ergebnisse gegenüberstellt.

const axios = require("axios");

class CaptchaProvider {
  constructor(name, submitUrl, resultUrl, apiKey) {
    this.name = name;
    this.submitUrl = submitUrl;
    this.resultUrl = resultUrl;
    this.apiKey = apiKey;
  }

  async solveRecaptcha(sitekey, pageurl) {
    const start = Date.now();
    try {
      const submit = await axios.post(this.submitUrl, null, {
        params: { key: this.apiKey, method: "userrecaptcha", googlekey: sitekey, pageurl, json: 1 },
      });
      if (submit.data.status !== 1) {
        return { provider: this.name, success: false, error: submit.data.request, elapsed: (Date.now() - start) / 1000 };
      }

      const captchaId = submit.data.request;
      for (let i = 0; i < 60; i++) {
        await new Promise((r) => setTimeout(r, 5000));
        const poll = await axios.get(this.resultUrl, {
          params: { key: this.apiKey, action: "get", id: captchaId, json: 1 },
        });
        if (poll.data.status === 1) {
          return { provider: this.name, success: true, solution: poll.data.request, elapsed: (Date.now() - start) / 1000 };
        }
        if (poll.data.request !== "CAPCHA_NOT_READY") {
          return { provider: this.name, success: false, error: poll.data.request, elapsed: (Date.now() - start) / 1000 };
        }
      }
      return { provider: this.name, success: false, error: "TIMEOUT", elapsed: (Date.now() - start) / 1000 };
    } catch (err) {
      return { provider: this.name, success: false, error: err.message, elapsed: (Date.now() - start) / 1000 };
    }
  }
}

async function parallelTest(current, captchaai, sitekey, pageurl, runs = 20) {
  const results = { current: [], captchaai: [] };

  for (let i = 0; i < runs; i++) {
    const [currentResult, captchaaiResult] = await Promise.all([
      current.solveRecaptcha(sitekey, pageurl),
      captchaai.solveRecaptcha(sitekey, pageurl),
    ]);

    results.current.push(currentResult);
    results.captchaai.push(captchaaiResult);

    console.log(`Run ${i + 1}/${runs}: ${current.name}=${currentResult.success ? "OK" : "FAIL"} ` +
      `(${currentResult.elapsed.toFixed(1)}s) | ${captchaai.name}=${captchaaiResult.success ? "OK" : "FAIL"} ` +
      `(${captchaaiResult.elapsed.toFixed(1)}s)`);
  }

  for (const [label, data] of Object.entries(results)) {
    const successes = data.filter((r) => r.success).length;
    const times = data.filter((r) => r.success).map((r) => r.elapsed);
    const avgTime = times.length ? times.reduce((a, b) => a + b, 0) / times.length : 0;
    console.log(`\n${label}: ${successes}/${runs} success (${((successes / runs) * 100).toFixed(1)}%), avg ${avgTime.toFixed(1)}s`);
  }
}

// Run
const currentProvider = new CaptchaProvider("CurrentProvider", "https://current-provider.com/in.php", "https://current-provider.com/res.php", "current_key");
const captchaai = new CaptchaProvider("CaptchaAI", "https://ocr.captchaai.com/in.php", "https://ocr.captchaai.com/res.php", process.env.CAPTCHAAI_API_KEY);

parallelTest(currentProvider, captchaai, "SITE_KEY", "https://example.com", 20);

Typische Probleme und Lösungen

Problem Ursache Lösung
Token wird erzeugt, aber vom Ziel abgelehnt Sitekey, pageurl oder Session-Kontext stimmen nicht überein Erfassen Sie die Parameter erneut und verwenden Sie das Token in derselben Browser- oder HTTP-Sitzung
Polling endet im Timeout Intervall, Wartezeit oder Fehlerbehandlung sind zu eng gesetzt Fragen Sie den Status alle 5–10 Sekunden ab, trennen Sie das Timeout von echten Fehlercodes und protokollieren Sie die Ursache
Beispiel funktioniert lokal, aber nicht im Workflow Callback, Formularfeld oder die Token-Übergabe fehlt in der echten Zielkette Prüfen Sie den genauen Übergabepfad vom Solver bis zur finalen Zielanfrage – vom Token bis zum abgesendeten Formular

Häufige Fragen

Kann ich CaptchaAI testen, ohne meinen Produktionsverkehr zu gefährden?

Ja. Im Schattenmodus läuft CaptchaAI zunächst nur mit, ohne Ihre echten Anfragen zu beeinflussen. Erst wenn die Kennzahlen stimmen, leiten Sie über den TrafficSplitter schrittweise 5 %, 25 % und mehr des Verkehrs um – jederzeit mit Fallback.

Welche CAPTCHA-Typen kann ich im Parallellauf vergleichen?

CaptchaAI löst reCAPTCHA v2 und v3, Cloudflare Turnstile und Challenge, GeeTest v3 sowie Bild-, OCR- und Raster-CAPTCHAs; CaptchaFox, Friendly Captcha und Lemin sind in der Beta. Testen Sie die Typen, die in Ihrem Traffic vorkommen, und gewichten Sie die Ergebnisse nach Ihrer realen Verteilung.

Was kostet CaptchaAI während der Testphase?

CaptchaAI rechnet Thread-basiert ab, nicht pro Lösung: Der Tarif BASIC (15 $/Monat, 5 Threads) genügt für die meisten Paralleltests. Da jeder Thread unbegrenzt viele Lösungen im Abrechnungsmonat abdeckt, bleiben die Kosten unabhängig von der Zahl der Läufe planbar.

Muss ich meinen Integrationscode für die Migration umschreiben?

In den meisten Fällen nicht. CaptchaAI nutzt die vertrauten in.php- und res.php-Endpunkte; im Code tauschen Sie im Wesentlichen Basis-URL und API-Schlüssel aus. Deshalb reicht im Parallellauf dieselbe CaptchaProvider-Klasse für beide Dienste.


Verwandte Leitfäden

Kommentare sind für diesen Artikel deaktiviert.