Anwendungsfälle

Mehrstufige Workflow-Automatisierung mit CaptchaAI

Mehrstufige Automatisierungen scheitern selten am Datenexport – sie scheitern an Schritt 1. Die Anmeldemaske zeigt ein reCAPTCHA v2 oder ein Cloudflare Turnstile, und alles Weitere läuft ins Leere. Die Antwort ist keine Sonderbehandlung für CAPTCHAs, sondern eine Pipeline, in der das Lösen ein normaler Schritt neben Sitzung, Aktion und Export ist.

Dieser Leitfaden zeigt den Aufbau in Python: Registry für Zugangsdaten, Sitzungsverwaltung für Cookies und Proxys, Lösungsschritt über die CaptchaAI-API, Executor für den parallelen Lauf. Voraussetzung: Sie sind für die angesprochenen Systeme autorisiert – eigene Mandanten, eigene Marken-Accounts, eigene Staging-Umgebungen.


Warum der Anmeldeschritt die ganze Kette blockiert

Bei einer einzelnen Abfrage ist ein fehlgeschlagenes CAPTCHA ein verlorener Request. In einer Kette ist es ein verlorener Zustand: Sitzung halb aufgebaut, Cookie-Set unvollständig, Job in der Mitte stehen geblieben. Drei Konsequenzen prägen das Design.

  • Zustand persistieren statt neu erzeugen. Wer nach jedem Fehler von vorn anmeldet, erzeugt bei jedem Lauf neue CAPTCHA-Abfragen.
  • Eigenes Timeout für den Lösungsschritt. CaptchaAI löst reCAPTCHA v2 in unter 60 Sekunden, Cloudflare Turnstile in unter 10 Sekunden – die Wiederholungslogik darf nicht früher abbrechen.
  • Tokens sind kurzlebig. Lösen Sie unmittelbar vor dem Absenden, nicht Minuten vorher auf Vorrat.

Wofür sich mehrstufige Automatisierung lohnt

Szenario Konkreter Ablauf
Agenturbetrieb Beiträge über betreute Marken-Accounts pflegen
Shop-Operations Preise und Bestände in mehreren Shopware- oder JTL-Mandanten pflegen
Datenerhebung Reports aus Portalen mit eigenem Zugang ziehen
QA-Läufe Anmeldeflüsse mit Test-Accounts verschiedener Rollen prüfen
Betriebsüberwachung Kontostatus, Guthaben und Benachrichtigungen nächtlich abfragen

Architektur der Workflow-Pipeline: vier Bausteine

Der Ablauf gliedert sich in vier getrennte Verantwortlichkeiten:

┌────────────────┐     ┌──────────────┐     ┌───────────┐     ┌───────────────┐
│ Account        │────▶│ Session      │────▶│ CAPTCHA   │────▶│ Workflow      │
│ Registry       │     │ Manager      │     │ Solver    │     │ Executor      │
│ (credentials)  │     │ (cookies,    │     │           │     │ (per-account  │
│                │     │  proxies)    │     │           │     │  actions)     │
└────────────────┘     └──────────────┘     └───────────┘     └───────────────┘

Jeder Baustein kennt nur seine eigene Aufgabe. So lässt sich später ein zweiter CAPTCHA-Typ ergänzen, ohne den Rest anzufassen.


Die Bausteine im Code

Registry: Zugangsdaten und Zustand an einer Stelle

Die Registry hält je Zugang Plattform, Anmeldedaten, optionalen Proxy, Cookies und den Zeitpunkt der letzten Anmeldung. Der Status (active, login_failed) zeigt, welche Einträge aus dem Lauf fallen.

import json
from dataclasses import dataclass, field, asdict
from typing import Optional


@dataclass
class Account:
    id: str
    platform: str
    username: str
    password: str
    proxy: Optional[str] = None
    cookies: dict = field(default_factory=dict)
    last_login: Optional[float] = None
    status: str = "active"


class AccountRegistry:
    def __init__(self, filepath="accounts.json"):
        self.filepath = filepath
        self.accounts = {}
        self._load()

    def _load(self):
        try:
            with open(self.filepath, "r") as f:
                data = json.load(f)
                for item in data:
                    acct = Account(**item)
                    self.accounts[acct.id] = acct
        except FileNotFoundError:
            pass

    def save(self):
        data = [asdict(a) for a in self.accounts.values()]
        with open(self.filepath, "w") as f:
            json.dump(data, f, indent=2)

    def add(self, account):
        self.accounts[account.id] = account
        self.save()

    def get(self, account_id):
        return self.accounts.get(account_id)

    def get_by_platform(self, platform):
        return [a for a in self.accounts.values() if a.platform == platform and a.status == "active"]

    def update_status(self, account_id, status):
        if account_id in self.accounts:
            self.accounts[account_id].status = status
            self.save()

Sitzungsverwaltung: Cookies und Proxys je Zugang

Der Sitzungsmanager macht jeden weiteren Lauf billiger als den ersten: Gültige Cookies werden auf die Platte geschrieben und beim Start wieder eingelesen. Ein hinterlegter proxy gilt nur für diese eine Sitzung.

import time
import requests
import pickle
import os


class SessionManager:
    def __init__(self, sessions_dir="sessions"):
        self.sessions_dir = sessions_dir
        os.makedirs(sessions_dir, exist_ok=True)
        self.sessions = {}

    def get_session(self, account):
        """Get or create a requests session for an account."""
        if account.id in self.sessions:
            return self.sessions[account.id]

        session = requests.Session()

        # Set proxy if configured
        if account.proxy:
            session.proxies = {
                "http": account.proxy,
                "https": account.proxy,
            }

        # Load saved cookies
        cookie_file = os.path.join(self.sessions_dir, f"{account.id}.cookies")
        if os.path.exists(cookie_file):
            with open(cookie_file, "rb") as f:
                session.cookies = pickle.load(f)

        self.sessions[account.id] = session
        return session

    def save_session(self, account):
        """Persist session cookies."""
        if account.id in self.sessions:
            cookie_file = os.path.join(self.sessions_dir, f"{account.id}.cookies")
            with open(cookie_file, "wb") as f:
                pickle.dump(self.sessions[account.id].cookies, f)

    def clear_session(self, account_id):
        if account_id in self.sessions:
            del self.sessions[account_id]
        cookie_file = os.path.join(self.sessions_dir, f"{account_id}.cookies")
        if os.path.exists(cookie_file):
            os.remove(cookie_file)

Anmeldung mit automatischer CAPTCHA-Lösung

Hier laufen zwei Dinge zusammen: Der CaptchaSolver übermittelt die Abfrage an in.php und fragt das Ergebnis über res.php ab; der LoginHandler holt zuerst die Anmeldeseite (CSRF-Token, Startcookies), trägt das Token in das Formularfeld ein und sendet ab. reCAPTCHA v2 nutzt die Methode userrecaptcha mit googlekey, Cloudflare Turnstile die Methode turnstile mit sitekey; das Zielfeld steuert captcha_field.

import time
import requests


class CaptchaSolver:
    BASE = "https://ocr.captchaai.com"

    def __init__(self, api_key):
        self.api_key = api_key

    def solve(self, params, initial_wait=10):
        params["key"] = self.api_key
        params["json"] = 1
        resp = requests.post(f"{self.BASE}/in.php", data=params).json()
        if resp["status"] != 1:
            raise Exception(resp["request"])
        task_id = resp["request"]
        time.sleep(initial_wait)
        for _ in range(60):
            result = requests.get(
                f"{self.BASE}/res.php",
                params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
            ).json()
            if result["request"] == "CAPCHA_NOT_READY":
                time.sleep(5)
                continue
            if result["status"] == 1:
                return result["request"]
            raise Exception(result["request"])
        raise TimeoutError("Timed out")


class LoginHandler:
    def __init__(self, captcha_solver):
        self.solver = captcha_solver

    def login(self, session, account, login_config):
        """
        login_config: {
            "url": login page URL,
            "submit_url": login form action URL,
            "captcha_type": "recaptcha_v2" | "turnstile" | None,
            "sitekey": "...",
            "username_field": "username",
            "password_field": "password",
            "captcha_field": "g-recaptcha-response",
        }
        """
        # Get login page (for CSRF token / cookies)
        session.get(login_config["url"])

        payload = {
            login_config.get("username_field", "username"): account.username,
            login_config.get("password_field", "password"): account.password,
        }

        # Solve CAPTCHA if present
        captcha_type = login_config.get("captcha_type")
        if captcha_type:
            if captcha_type == "recaptcha_v2":
                token = self.solver.solve({
                    "method": "userrecaptcha",
                    "googlekey": login_config["sitekey"],
                    "pageurl": login_config["url"],
                })
            elif captcha_type == "turnstile":
                token = self.solver.solve({
                    "method": "turnstile",
                    "sitekey": login_config["sitekey"],
                    "pageurl": login_config["url"],
                })
            else:
                raise ValueError(f"Unknown captcha type: {captcha_type}")

            captcha_field = login_config.get("captcha_field", "g-recaptcha-response")
            payload[captcha_field] = token

        submit_url = login_config.get("submit_url", login_config["url"])
        resp = session.post(submit_url, data=payload, allow_redirects=True)

        # Check login success
        success = resp.status_code == 200 and "login" not in resp.url.lower()
        if success:
            account.last_login = time.time()
        return success

Executor: derselbe Ablauf über einen Thread-Pool

Der Executor klammert alles zusammen: Anmeldung nur, wenn die letzte älter als eine Stunde ist, danach die Workflow-Funktion, Fehler je Zugang abgefangen. max_workers begrenzt die Parallelität – und bestimmt die Tarifwahl.

import time
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed

logger = logging.getLogger("workflow")


class WorkflowExecutor:
    def __init__(self, api_key, max_workers=5):
        self.solver = CaptchaSolver(api_key)
        self.registry = AccountRegistry()
        self.sessions = SessionManager()
        self.login_handler = LoginHandler(self.solver)
        self.max_workers = max_workers

    def run_for_account(self, account, workflow_fn, login_config):
        """Execute a workflow for a single account."""
        session = self.sessions.get_session(account)

        # Login if needed
        if not account.last_login or time.time() - account.last_login > 3600:
            logger.info(f"Logging in: {account.id}")
            if not self.login_handler.login(session, account, login_config):
                logger.error(f"Login failed: {account.id}")
                self.registry.update_status(account.id, "login_failed")
                return {"account": account.id, "status": "login_failed"}

            self.sessions.save_session(account)
            self.registry.save()

        # Execute workflow
        try:
            result = workflow_fn(session, account)
            return {"account": account.id, "status": "success", "data": result}
        except Exception as e:
            logger.error(f"Workflow failed for {account.id}: {e}")
            return {"account": account.id, "status": "error", "error": str(e)}

    def run_for_all(self, platform, workflow_fn, login_config):
        """Execute a workflow across all accounts on a platform."""
        accounts = self.registry.get_by_platform(platform)
        results = []

        with ThreadPoolExecutor(max_workers=self.max_workers) as pool:
            futures = {
                pool.submit(self.run_for_account, acct, workflow_fn, login_config): acct
                for acct in accounts
            }
            for future in as_completed(futures):
                result = future.result()
                results.append(result)
                logger.info(f"  {result['account']}: {result['status']}")

        return results

Vollständiges Beispiel: Benachrichtigungen prüfen

Das Skript konfiguriert die Anmeldung einmal und lässt den Workflow über alle aktiven Zugänge laufen. YOUR_API_KEY ersetzen Sie durch Ihren Schlüssel aus dem Dashboard.

executor = WorkflowExecutor("YOUR_API_KEY", max_workers=3)

# Define platform login config
login_config = {
    "url": "https://platform.example.com/login",
    "submit_url": "https://platform.example.com/api/login",
    "captcha_type": "recaptcha_v2",
    "sitekey": "6Le-wvkSAAAA...",
    "username_field": "email",
    "password_field": "password",
    "captcha_field": "g-recaptcha-response",
}


# Define workflow
def check_notifications(session, account):
    resp = session.get("https://platform.example.com/api/notifications")
    data = resp.json()
    return {
        "unread": data.get("unread_count", 0),
        "latest": data.get("notifications", [])[:5],
    }


# Run across all accounts
results = executor.run_for_all("example_platform", check_notifications, login_config)

for r in results:
    if r["status"] == "success":
        print(f"{r['account']}: {r['data']['unread']} unread notifications")
    else:
        print(f"{r['account']}: {r['status']}")

Weitere Schritte für dieselbe Pipeline

Ein Workflow ist hier nur eine Funktion mit der Signatur (session, account). Die Pipeline wächst, ohne dass Anmelde- oder Sitzungslogik angefasst wird.

Daten exportieren

def export_data(session, account):
    resp = session.get("https://platform.example.com/api/export")
    filename = f"export_{account.id}.json"
    with open(filename, "w") as f:
        f.write(resp.text)
    return {"file": filename, "size": len(resp.text)}

Status abfragen

def check_status(session, account):
    resp = session.get("https://platform.example.com/api/account/status")
    return resp.json()

Einstellungen schreiben

def update_settings(session, account):
    resp = session.post(
        "https://platform.example.com/api/settings",
        json={"timezone": "UTC", "notifications": True},
    )
    return {"updated": resp.status_code == 200}

Von max_workers zur richtigen Tarifstufe

max_workers bestimmt, wie viele CAPTCHA-Abfragen gleichzeitig offen sind – und genau darauf bezieht sich die Abrechnung: CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro Lösung; die Zahl der Lösungen je Thread ist unbegrenzt.

Tarifstufe Preis/Monat Threads Typischer Einsatz
BASIC 15 $ 5 Ein Mandant, nächtlicher Lauf
STANDARD 30 $ 15 Agenturbetrieb mit mehreren Marken-Accounts
ADVANCE 90 $ 50 Parallele Workflows über mehrere Plattformen
PREMIUM 170 $ 100 Dauerbetrieb mit kurzen Intervallen

Darüber liegen CORPORATE (240 $ für 150 Threads), ENTERPRISE (300 $ für 200 Threads) sowie VIP-1 (1.500 $ für 1.000 Threads), VIP-2 (4.500 $ für 3.000 Threads) und VIP-3 (7.500 $ für 5.000 Threads). Alle Preise in US-Dollar.

Faustregel: max_workers nie höher setzen als die Thread-Zahl Ihrer Stufe. Ein nächtlicher Lauf über 40 Zugänge mit max_workers=5 braucht kein größeres Kontingent – er dauert nur länger. Taktgeber ist die Lösungszeit: unter 10 Sekunden bei Cloudflare Turnstile, unter 60 Sekunden bei reCAPTCHA v2, jeweils mit hoher Erfolgsquote auf den unterstützten Typen.


Betrieb im DACH-Umfeld: Zeitplan, Hosting, Datenschutz

Für den produktiven Betrieb hat sich dieses Muster bewährt:

  • Zeitplanung über die vorhandene CI. Ein schedule-Job in GitLab CI oder GitHub Actions genügt. Der API-Schlüssel gehört in maskierte CI-Variablen, nie ins Repository.
  • Kleiner Worker genügt. Ein VPS bei Hetzner, IONOS oder netcup reicht aus; der Lösungsschritt läuft ohnehin auf der CaptchaAI-Seite.
  • DSGVO-Blick auf die Datenflüsse. IP-Adressen und Anmeldedaten sind personenbezogene Daten. Prüfen Sie, welche Felder Ihr Export enthält, wie lange Cookie-Dateien in sessions/ liegen und ob eine Rechtsgrundlage dokumentiert ist – Sorgfaltspflicht auf Ihrer Seite, keine Zusage des Dienstes.
  • Zugriffsrahmen festhalten. Anmeldungen nur auf eigenen oder freigegebenen Systemen; die AGB der Plattform sind die Grenze.

Fehlerbehebung

Problem Ursache Lösung
Alle Anmeldungen scheitern Sitekey ausgetauscht Sitekey neu aus dem Quelltext auslesen
Sitzung nicht akzeptiert Cookies abgelaufen Sitzung löschen, neu anmelden
Antwort 429 Zu viele gleichzeitige Anmeldungen max_workers senken, Wartezeit einbauen
Zugang gesperrt Zu viele Fehlversuche Anmeldedaten prüfen, Intervall verlängern
ERROR_CAPTCHA_UNSOLVABLE Falscher Sitekey oder falsche Page-URL Beide Werte gegen die geladene Seite abgleichen

Häufige Fragen

Welche CAPTCHA-Typen deckt der Anmeldeschritt ab?

Die im Login üblichen: reCAPTCHA v2 inklusive Invisible und Enterprise, reCAPTCHA v3, Cloudflare Turnstile und Cloudflare Challenge, GeeTest v3 sowie Bild-, Raster- und BLS-CAPTCHAs. Hinzu kommen CaptchaFox (Beta), Friendly Captcha (Beta) und Lemin (Beta). Nicht unterstützt werden hCaptcha und FunCaptcha, GeeTest v4 ist nur als „bald verfügbar“ angekündigt. Weitere Typen ergänzen Sie im LoginHandler mit der jeweiligen method.

Was passiert, wenn das Token abläuft, bevor das Formular abgesendet wird?

Das Zielsystem verwirft die Anmeldung, und der Workflow bricht mit einem scheinbaren Passwortfehler ab. CAPTCHA-Tokens sind nur rund zwei Minuten gültig. Lösen Sie deshalb direkt vor dem session.post(...), nicht schon beim Aufbau der Warteschlange.

Wie lange dauert ein Lauf über 50 Zugänge?

Das hängt fast ausschließlich vom Anmeldeanteil ab. Mit persistierten Cookies besteht der Lauf nur aus HTTP-Anfragen – Minuten statt Stunden. Ohne gültige Sitzungen fällt je Zugang ein CAPTCHA an; bei max_workers=5 und reCAPTCHA v2 sind das rund zehn Wellen von jeweils bis zu 60 Sekunden.

Wie halte ich Zugangsdaten aus dem Repository heraus?

Über Umgebungsvariablen oder einen Secrets-Manager wie Vault oder den GitLab-CI-Variablenspeicher. Die Datei accounts.json gehört in .gitignore; laden Sie Benutzername und Passwort beim Start in die Account-Objekte.

Lässt sich die Pipeline unbeaufsichtigt betreiben?

Ja, sofern drei Dinge protokolliert werden: Ergebnisstatus je Zugang, Zahl der gelösten CAPTCHAs und das verbleibende Guthaben im Dashboard. Als Alarmkriterium genügt meist der Abgleich der Statusverteilung mit dem Vortag.


Verwandte Leitfäden


Den CAPTCHA-Schritt aus Ihrer Pipeline nehmen – mit CaptchaAI lösen.

Kommentare sind für diesen Artikel deaktiviert.