Referenz

CaptchaAI CLI-Tool: Lösen und Testen von CAPTCHAs über die Befehlszeile

Ein CAPTCHA lässt sich mit CaptchaAI in einer einzigen Terminal-Zeile lösen – ohne Anwendungscode, ohne Framework, ohne Projekt-Setup. Das CLI-Tool aus diesem Artikel übermittelt die Aufgabe an in.php, fragt das Ergebnis über res.php ab und schreibt am Ende nur das fertige Token nach stdout. Von dort holen es sich curl, ein Bash-Script oder ein CI-Job ohne Zwischenschritt ab.

Der größte Nutzen zeigt sich beim Debuggen. Scheitert ein Solve in der Anwendung, sind meist drei Ursachen im Spiel: falscher Sitekey, falsche Page-URL oder aufgebrauchtes Guthaben. Alle drei klären Sie im Terminal in unter einer Minute, ohne die Applikation neu zu deployen.

Was das CLI im Terminal abdeckt

Das Tool bleibt klein: zwei Befehle, wenige Optionen, außer requests (Python) keine Abhängigkeiten.

  • Guthaben abfragen – eine Zahl im Terminal statt eines Dashboard-Logins.
  • CAPTCHA lösen – reCAPTCHA v2, v3, Enterprise, Cloudflare Turnstile und Bild-CAPTCHAs.
  • Parameter verifizieren – Sitekey, Page-URL, action und Proxy einzeln durchprobieren.
  • Token weiterreichen – per Pipe oder $( … ) in jedes Shell-Script.

Befehle im Überblick

Befehl Wirkung Pflichtparameter
balance Guthaben ausgeben
solve recaptcha-v2 reCAPTCHA v2 lösen --sitekey, --pageurl
solve recaptcha-v3 reCAPTCHA v3 lösen --sitekey, --pageurl, --action
solve recaptcha-enterprise reCAPTCHA v2 Enterprise lösen --sitekey, --pageurl
solve turnstile Cloudflare Turnstile lösen --sitekey, --pageurl
solve image Bild-CAPTCHA per OCR lösen --image

Globale Optionen

Option Wirkung Standard
--key API-Schlüssel; alternativ CAPTCHAAI_API_KEY
--json Strukturierte Ausgabe statt reinem Token false
-v, --verbose Fortschritt und Task-ID nach stderr false
--interval Abstand zwischen zwei Statusabfragen (Sekunden) 5
--timeout Maximale Wartezeit (Sekunden) 300

Wichtig für Scripts: Nutzdaten gehen nach stdout, Erklärendes nach stderr; Exit-Code 0 bedeutet gelöstes Token.

Das Python-CLI aufsetzen

Skript ablegen und ausführbar machen

Legen Sie den Code als captchaai_cli.py ab und setzen Sie das Ausführungsrecht (chmod +x captchaai_cli.py). Der Ablauf: Aufgabe an in.php übermitteln, Task-ID entgegennehmen, res.php in Intervallen abfragen, Token ausgeben.

#!/usr/bin/env python3
"""CaptchaAI CLI — command-line CAPTCHA solving and API testing."""

import argparse
import json
import os
import sys
import time
import requests

API_BASE = "https://ocr.captchaai.com"

def get_api_key(args):
    """Get API key from argument or environment variable."""
    key = args.key or os.environ.get("CAPTCHAAI_API_KEY")
    if not key:
        print("Error: API key required. Use --key or set CAPTCHAAI_API_KEY", file=sys.stderr)
        sys.exit(1)
    return key

def cmd_balance(args):
    """Check account balance."""
    key = get_api_key(args)
    response = requests.get(
        f"{API_BASE}/res.php",
        params={"key": key, "action": "getbalance", "json": 1},
        timeout=10,
    )
    result = response.json()
    if args.json:
        print(json.dumps(result, indent=2))
    else:
        print(f"${result.get('request', 'unknown')}")

def cmd_solve(args):
    """Submit and poll a CAPTCHA task."""
    key = get_api_key(args)

    # Build submit parameters
    params = {"key": key, "json": 1}

    if args.type == "recaptcha-v2":
        params.update({"method": "userrecaptcha", "googlekey": args.sitekey, "pageurl": args.pageurl})
    elif args.type == "recaptcha-v3":
        params.update({
            "method": "userrecaptcha", "googlekey": args.sitekey, "pageurl": args.pageurl,
            "version": "v3", "action": args.action or "verify",
        })
    elif args.type == "recaptcha-enterprise":
        params.update({
            "method": "userrecaptcha", "googlekey": args.sitekey,
            "pageurl": args.pageurl, "enterprise": 1,
        })
    elif args.type == "turnstile":
        params.update({"method": "turnstile", "sitekey": args.sitekey, "pageurl": args.pageurl})
    elif args.type == "image":
        import base64
        with open(args.image, "rb") as f:
            params.update({"method": "base64", "body": base64.b64encode(f.read()).decode()})

    if args.proxy:
        params["proxy"] = args.proxy
        params["proxytype"] = args.proxy_type or "HTTP"

    # Submit
    if args.verbose:
        safe_params = {k: v for k, v in params.items() if k != "key"}
        print(f"Submitting: {json.dumps(safe_params)}", file=sys.stderr)

    response = requests.post(f"{API_BASE}/in.php", data=params, timeout=30)
    result = response.json()

    if result.get("status") != 1:
        print(f"Error: {result.get('request', 'unknown')}", file=sys.stderr)
        sys.exit(1)

    task_id = result["request"]
    if args.verbose:
        print(f"Task ID: {task_id}", file=sys.stderr)

    # Poll
    interval = args.interval or 5
    timeout = args.timeout or 300
    start = time.monotonic()

    while time.monotonic() - start < timeout:
        time.sleep(interval)
        response = requests.get(
            f"{API_BASE}/res.php",
            params={"key": key, "action": "get", "id": task_id, "json": 1},
            timeout=15,
        )
        result = response.json()

        if result.get("request") == "CAPCHA_NOT_READY":
            elapsed = time.monotonic() - start
            if args.verbose:
                print(f"Waiting... ({elapsed:.0f}s)", file=sys.stderr)
            continue

        if result.get("status") == 1:
            elapsed = time.monotonic() - start
            if args.json:
                print(json.dumps({
                    "token": result["request"],
                    "task_id": task_id,
                    "solve_time": round(elapsed, 1),
                }, indent=2))
            else:
                print(result["request"])

            if args.verbose:
                print(f"Solved in {elapsed:.1f}s", file=sys.stderr)
            sys.exit(0)

        print(f"Error: {result.get('request', 'unknown')}", file=sys.stderr)
        sys.exit(1)

    print(f"Timeout after {timeout}s", file=sys.stderr)
    sys.exit(1)

def main():
    parser = argparse.ArgumentParser(description="CaptchaAI CLI Tool")
    parser.add_argument("--key", help="API key (or set CAPTCHAAI_API_KEY env var)")
    parser.add_argument("--json", action="store_true", help="Output as JSON")
    parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")

    subparsers = parser.add_subparsers(dest="command", required=True)

    # Balance command
    subparsers.add_parser("balance", help="Check account balance")

    # Solve command
    solve_parser = subparsers.add_parser("solve", help="Solve a CAPTCHA")
    solve_parser.add_argument(
        "type",
        choices=["recaptcha-v2", "recaptcha-v3", "recaptcha-enterprise", "turnstile", "image"],
        help="CAPTCHA type",
    )
    solve_parser.add_argument("--sitekey", help="Site key for the CAPTCHA")
    solve_parser.add_argument("--pageurl", help="Page URL where the CAPTCHA appears")
    solve_parser.add_argument("--image", help="Path to image file (for image type)")
    solve_parser.add_argument("--proxy", help="Proxy URL (e.g., http://user:pass@host:port)")
    solve_parser.add_argument("--proxy-type", choices=["HTTP", "HTTPS", "SOCKS4", "SOCKS5"], default="HTTP")
    solve_parser.add_argument("--action", help="reCAPTCHA v3 action parameter")
    solve_parser.add_argument("--interval", type=int, default=5, help="Poll interval in seconds")
    solve_parser.add_argument("--timeout", type=int, default=300, help="Max wait time in seconds")

    args = parser.parse_args()

    if args.command == "balance":
        cmd_balance(args)
    elif args.command == "solve":
        cmd_solve(args)

if __name__ == "__main__":
    main()

Typische Aufrufe im Terminal

Der API-Schlüssel wandert einmal in die Umgebung, danach bleiben die Aufrufe kurz. Als Smoke-Test genügt balance: Kommt eine Zahl zurück, stimmen Schlüssel und Netzwerkpfad.

# Set API key as environment variable
export CAPTCHAAI_API_KEY="YOUR_API_KEY"

# Check balance
python captchaai_cli.py balance

# Solve reCAPTCHA v2
python captchaai_cli.py solve recaptcha-v2 \
  --sitekey "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" \
  --pageurl "https://www.google.com/recaptcha/api2/demo"

# Solve reCAPTCHA v3 with verbose output
python captchaai_cli.py -v solve recaptcha-v3 \
  --sitekey "SITE_KEY" \
  --pageurl "https://example.com" \
  --action "login"

# Solve Turnstile with JSON output
python captchaai_cli.py --json solve turnstile \
  --sitekey "0x4AAAAAAA..." \
  --pageurl "https://example.com"

# Solve image CAPTCHA
python captchaai_cli.py solve image --image captcha.png

# Use with proxy
python captchaai_cli.py solve recaptcha-v2 \
  --sitekey "SITE_KEY" \
  --pageurl "https://example.com" \
  --proxy "http://user:[email protected]:8080"

# Pipe token to another command
TOKEN=$(python captchaai_cli.py solve recaptcha-v2 \
  --sitekey "SITE_KEY" --pageurl "https://example.com")
echo "Got token: ${TOKEN:0:20}..."

Node.js-Variante für JavaScript-Stacks

Wer ohnehin mit Node.js arbeitet, spart sich die Python-Laufzeit auf dem Build-Agent. Diese Variante nutzt parseArgs und fetch aus der Standardbibliothek, braucht also keine zusätzlichen Pakete. Befehle, Optionen und Ausgabeformat bleiben identisch.

#!/usr/bin/env node
/**

 * CaptchaAI CLI — command-line CAPTCHA solving and API testing.
 */

const fs = require("fs");
const { parseArgs } = require("util");

const API_BASE = "https://ocr.captchaai.com";

function getApiKey(values) {
  const key = values.key || process.env.CAPTCHAAI_API_KEY;
  if (!key) {
    console.error("Error: API key required. Use --key or set CAPTCHAAI_API_KEY");
    process.exit(1);
  }
  return key;
}

async function cmdBalance(values) {
  const key = getApiKey(values);
  const url = new URL(`${API_BASE}/res.php`);
  url.searchParams.set("key", key);
  url.searchParams.set("action", "getbalance");
  url.searchParams.set("json", "1");

  const response = await fetch(url);
  const result = await response.json();

  if (values.json) {
    console.log(JSON.stringify(result, null, 2));
  } else {
    console.log(`$${result.request}`);
  }
}

async function cmdSolve(values) {
  const key = getApiKey(values);
  const params = { key, json: 1 };
  const type = values.type;

  if (type === "recaptcha-v2") {
    Object.assign(params, { method: "userrecaptcha", googlekey: values.sitekey, pageurl: values.pageurl });
  } else if (type === "recaptcha-v3") {
    Object.assign(params, {
      method: "userrecaptcha", googlekey: values.sitekey, pageurl: values.pageurl,
      version: "v3", action: values.action || "verify",
    });
  } else if (type === "turnstile") {
    Object.assign(params, { method: "turnstile", sitekey: values.sitekey, pageurl: values.pageurl });
  } else if (type === "image") {
    const imageData = fs.readFileSync(values.image);
    Object.assign(params, { method: "base64", body: imageData.toString("base64") });
  }

  if (values.proxy) {
    params.proxy = values.proxy;
    params.proxytype = values["proxy-type"] || "HTTP";
  }

  if (values.verbose) {
    const safeParams = { ...params };
    delete safeParams.key;
    console.error(`Submitting: ${JSON.stringify(safeParams)}`);
  }

  // Submit
  const submitResponse = await fetch(`${API_BASE}/in.php`, {
    method: "POST",
    body: new URLSearchParams(params),
  });
  const submitResult = await submitResponse.json();

  if (submitResult.status !== 1) {
    console.error(`Error: ${submitResult.request || "unknown"}`);
    process.exit(1);
  }

  const taskId = submitResult.request;
  if (values.verbose) console.error(`Task ID: ${taskId}`);

  // Poll
  const interval = parseInt(values.interval) || 5;
  const timeout = parseInt(values.timeout) || 300;
  const start = Date.now();

  while ((Date.now() - start) / 1000 < timeout) {
    await new Promise((r) => setTimeout(r, interval * 1000));

    const pollUrl = new URL(`${API_BASE}/res.php`);
    pollUrl.searchParams.set("key", key);
    pollUrl.searchParams.set("action", "get");
    pollUrl.searchParams.set("id", taskId);
    pollUrl.searchParams.set("json", "1");

    const pollResponse = await fetch(pollUrl);
    const pollResult = await pollResponse.json();

    if (pollResult.request === "CAPCHA_NOT_READY") {
      if (values.verbose) {
        console.error(`Waiting... (${((Date.now() - start) / 1000).toFixed(0)}s)`);
      }
      continue;
    }

    if (pollResult.status === 1) {
      const elapsed = ((Date.now() - start) / 1000).toFixed(1);
      if (values.json) {
        console.log(JSON.stringify({ token: pollResult.request, task_id: taskId, solve_time: parseFloat(elapsed) }, null, 2));
      } else {
        console.log(pollResult.request);
      }
      if (values.verbose) console.error(`Solved in ${elapsed}s`);
      process.exit(0);
    }

    console.error(`Error: ${pollResult.request || "unknown"}`);
    process.exit(1);
  }

  console.error(`Timeout after ${timeout}s`);
  process.exit(1);
}

// Parse arguments
const { values, positionals } = parseArgs({
  allowPositionals: true,
  options: {
    key: { type: "string" },
    json: { type: "boolean", default: false },
    verbose: { type: "boolean", short: "v", default: false },
    sitekey: { type: "string" },
    pageurl: { type: "string" },
    image: { type: "string" },
    proxy: { type: "string" },
    "proxy-type": { type: "string", default: "HTTP" },
    action: { type: "string" },
    interval: { type: "string", default: "5" },
    timeout: { type: "string", default: "300" },
    type: { type: "string" },
  },
});

const command = positionals[0];
values.type = values.type || positionals[1];

if (command === "balance") {
  cmdBalance(values);
} else if (command === "solve") {
  cmdSolve(values);
} else {
  console.error("Usage: captchaai-cli <balance|solve> [options]");
  process.exit(1);
}

Token direkt in Bash-Workflows weiterreichen

Das Token landet als einzige Zeile auf stdout und lässt sich per Kommandosubstitution direkt an den nächsten Request hängen. Entscheidend ist das Timing: Ein reCAPTCHA-Token bleibt rund 120 Sekunden gültig, gehört also unmittelbar vor das Absenden des Formulars und nicht an den Anfang eines langen Scripts.

Der Feldname richtet sich nach dem CAPTCHA-Typ – bei reCAPTCHA g-recaptcha-response, bei Cloudflare Turnstile cf-turnstile-response. Unter falschem Namen mitgeschickt, antwortet die Zielanwendung mit einer Validierungsmeldung, obwohl der Solve fehlerfrei war.

#!/bin/bash
# Solve a CAPTCHA and use the token in a curl request

TOKEN=$(python captchaai_cli.py solve recaptcha-v2 \
  --sitekey "$SITEKEY" \
  --pageurl "$TARGET_URL" 2>/dev/null)

if [ $? -eq 0 ]; then
  curl -X POST "$TARGET_URL/submit" \
    -d "g-recaptcha-response=$TOKEN" \
    -d "name=test"
else
  echo "CAPTCHA solve failed" >&2
  exit 1
fi

Praxisbeispiel: nächtlicher Regressionslauf in GitLab CI

Ein Berliner SaaS-Team prüft nachts sein eigenes Login-Formular unter https://staging.example-app.test/login, geschützt durch reCAPTCHA v2. Die Pipeline läuft in GitLab CI auf einem selbst gehosteten Runner bei Hetzner – in deutschen Teams eine verbreitete Kombination.

Der Ablauf im Job:

  1. Der API-Schlüssel liegt als maskierte CI/CD-Variable CAPTCHAAI_API_KEY im Projekt, nicht im Repository.
  2. Ein before_script ruft captchaai_cli.py balance auf und bricht unterhalb eines Schwellenwerts ab – das erspart eine Nacht voller Folgefehler.
  3. Der Testschritt löst das Token und übergibt es dem HTTP-Client.
  4. Mit --json wandert die Ausgabe in ein Artefakt; Task-ID und Lösungszeit bleiben auswertbar.

Wer --proxy einsetzt, denkt den Datenschutz mit: IP-Adressen gelten nach DSGVO als personenbezogene Daten. Prüfen Sie, wohin Ihr Traffic geleitet wird.

Wartezeiten, Threads und Kosten realistisch einschätzen

Die Standardwerte --interval 5 und --timeout 300 sind bewusst konservativ. Für die Feinjustierung helfen die Obergrenzen pro Typ:

CAPTCHA-Typ Lösungszeit Sinnvolles --interval
Bild-CAPTCHA (OCR) < 0,5 s 1
reCAPTCHA v3 < 4 s 2
Cloudflare Turnstile < 10 s 3
GeeTest v3 < 12 s 3
reCAPTCHA v2 < 60 s 5

Beim Durchsatz zählt die Zahl der parallelen Prozesse, nicht die Zahl der Solves: CaptchaAI rechnet Thread-basiert ab, pro Thread sind die Lösungen im Abrechnungsmonat unbegrenzt. BASIC (15 $/Monat, 5 Threads) trägt eine überschaubare Testsuite, ADVANCE (90 $/Monat, 50 Threads) deckt parallele Läufe über mehrere Branches ab. Starten Sie mehr CLI-Instanzen, als Threads verfügbar sind, warten die überzähligen Aufgaben – der Job wird langsamer, nicht teurer. Preise in US-Dollar.

Fehlerbehebung im Terminal

Symptom Ursache Vorgehen
Error: API key required Weder --key übergeben noch CAPTCHAAI_API_KEY gesetzt Variable exportieren oder Schlüssel explizit übergeben
ERROR_WRONG_USER_KEY Schlüssel unvollständig kopiert oder mit Zeilenumbruch Wert im Dashboard erneut kopieren und ohne Umbruch setzen
ERROR_ZERO_BALANCE Guthaben aufgebraucht Aufladen; captchaai_cli.py balance zeigt den Stand
Timeout after 300s Wartezeit überschritten oder Parameter passen nicht --timeout erhöhen, Sitekey und Page-URL prüfen
Ausgabe enthält Zusatztext --verbose mischt Meldungen in die Ausgabe stderr mit 2>/dev/null unterdrücken oder --json auswerten
Solve läuft, Formular lehnt ab Token abgelaufen oder falscher Feldname Token direkt vor dem Absenden lösen, Feldnamen prüfen

Reproduzieren Sie einen Produktionsfehler zuerst im Terminal. Gelingt der Solve dort mit denselben Parametern, liegt die Ursache in der Anwendung – bei Headern, Session-Handling oder dem Zeitpunkt der Übermittlung.

Häufige Fragen

Brauche ich Python und Node.js parallel?

Nein, eines von beiden genügt. Beide Skripte sprechen dieselben Endpunkte an und liefern identische Ausgaben; wählen Sie die Laufzeit, die auf Ihrem Build-Agent ohnehin installiert ist.

Welche CAPTCHA-Typen deckt das CLI ab?

Die Skripte lösen reCAPTCHA v2, v3, v2 Enterprise, Cloudflare Turnstile und Bild-CAPTCHAs. Die API unterstützt zusätzlich GeeTest v3, Cloudflare Challenge, Rasterbild- und BLS-CAPTCHAs sowie CaptchaFox, Friendly Captcha und Lemin (Beta) – ein weiterer elif-Zweig genügt. Nicht unterstützt werden hCaptcha und FunCaptcha; GeeTest v4 ist als bald verfügbar angekündigt.

Wie lange bleibt ein gelöstes Token gültig?

Rund 120 Sekunden. Deshalb gehört der Solve ans Ende der Vorbereitung und nicht an den Anfang: Erst Formulardaten und Session aufbauen, dann das Token holen, dann absenden.

Wie viele Solves kann ich gleichzeitig anstoßen?

So viele, wie Ihr Tarif Threads bereitstellt – ein Thread entspricht einer laufenden Aufgabe. Mehrere Hintergrundprozesse (python captchaai_cli.py solve … &) funktionieren für kleine Läufe gut; ab einigen Dutzend gleichzeitigen Aufgaben ist eine Warteschlange im Anwendungscode die stabilere Lösung.

Warum bricht das Polling nach 300 Sekunden ab?

Das ist der Standardwert von --timeout. Greift er regelmäßig, prüfen Sie zuerst, ob Sitekey und Page-URL exakt zur aufgerufenen Seite passen und ob dort derselbe CAPTCHA-Typ ausgeliefert wird.


Verwandte Leitfäden

Kommentare sind für diesen Artikel deaktiviert.