Für ein brauchbares Nutzungs-Dashboard brauchen Sie weder eine Zeitreihendatenbank noch einen Agenten auf jedem Worker: eine CSV-Zeile pro Lösungsversuch, drei Auswertungsfunktionen und eine Abfrage des Guthabens genügen. Dieser Leitfaden baut genau das in Python. Am Ende beantworten Ihre eigenen Zahlen die drei Fragen, die im Betrieb zählen: Läuft es stabil, wie lange dauert eine Lösung gerade, und reicht der gebuchte Plan für die Last?
Aus der Praxis: Ein Team in Hamburg betreibt seine Scraping-Worker auf einem Hetzner-VPS und deployt über GitLab CI. Gebucht ist ADVANCE (90 $/Monat, 50 Threads). Die Frage in der Monatsrunde lautet nie „Wie viele CAPTCHAs haben wir gelöst?“, sondern „Waren die 50 Threads jemals gleichzeitig belegt?“ – ohne eigene Messwerte reine Raterei.
Warum Sie die CaptchaAI-Nutzung trotz Thread-Abrechnung überwachen sollten
CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro Lösung, und jeder Plan enthält unbegrenzte Lösungen pro Thread. Die Frage „Was kostet uns eine einzelne Lösung?“ verliert damit an Gewicht – dafür rücken drei andere nach vorn:
- Auslastung: Wie viele Threads waren gleichzeitig belegt? Nur das entscheidet, ob ein Plan-Wechsel etwas bringt.
- Qualität: Wie entwickelt sich die Erfolgsquote je Methode? Ein Einbruch bei genau einem Typ ist fast immer ein Integrationsproblem.
- Latenz: Wie lange dauert eine Lösung im Median und im P95? Diese Zahl bestimmt den Durchsatz Ihrer Pipeline.
Keine dieser Fragen beantwortet die Kontoansicht eines Anbieters – nur Ihr eigenes Log.
Welche Kennzahlen ein CaptchaAI-Dashboard abdecken muss
| Kennzahl | Wofür Sie sie brauchen |
|---|---|
| Anzahl der Lösungen | Volumen pro Tag und Methode |
| Erfolgsquote | Einbrüche erkennen, bevor Jobs abbrechen |
| Lösungszeit (Median, P95) | Verlangsamungen sichtbar machen |
| Ausgaben pro Zeitraum | Verbrauchsverlauf und Budgetplanung |
| Fehlerverteilung | Parameterfehler von echten Ausfällen trennen |
| Guthaben | Stillstand durch ein leeres Konto verhindern |
| Methodenaufschlüsselung | userrecaptcha, turnstile, post getrennt beurteilen |
Alles davon steckt in einer Zeile pro Versuch: Zeitstempel, Methode, Dauer, Status, Fehlercode, Task-ID.
Schritt 1: Der Metriksammler
Der Sammler schreibt jeden Versuch sofort in eine CSV und hält eine Zusammenfassung der laufenden Sitzung im Speicher. Das Sperren über threading.Lock ist Pflicht, sobald mehrere Threads gleichzeitig lösen – bei Thread-basierter Abrechnung der Normalfall.
import time
import csv
import datetime
import threading
from collections import defaultdict
class MetricsCollector:
"""Collect and store CaptchaAI solve metrics."""
def __init__(self, log_file="captchaai_metrics.csv"):
self.log_file = log_file
self.lock = threading.Lock()
self.session_stats = defaultdict(lambda: {
"count": 0, "success": 0, "error": 0,
"timeout": 0, "total_time": 0,
})
self._init_log()
def _init_log(self):
try:
with open(self.log_file, "r"):
pass
except FileNotFoundError:
with open(self.log_file, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow([
"timestamp", "method", "duration_s",
"status", "error_code", "task_id",
])
def record(self, method, duration, status, error_code="", task_id=""):
"""Record a solve attempt."""
with self.lock:
# Update in-memory stats
stats = self.session_stats[method]
stats["count"] += 1
stats["total_time"] += duration
if status == "success":
stats["success"] += 1
elif status == "timeout":
stats["timeout"] += 1
else:
stats["error"] += 1
# Write to CSV
with open(self.log_file, "a", newline="") as f:
writer = csv.writer(f)
writer.writerow([
datetime.datetime.utcnow().isoformat(),
method, f"{duration:.2f}",
status, error_code, task_id,
])
def get_session_summary(self):
"""Get current session statistics."""
summary = {}
for method, stats in self.session_stats.items():
avg_time = (
stats["total_time"] / stats["count"]
if stats["count"] > 0 else 0
)
success_rate = (
stats["success"] / stats["count"] * 100
if stats["count"] > 0 else 0
)
summary[method] = {
"total": stats["count"],
"success": stats["success"],
"errors": stats["error"],
"timeouts": stats["timeout"],
"success_rate": f"{success_rate:.1f}%",
"avg_time": f"{avg_time:.1f}s",
}
return summary
Zwei Details tragen die ganze Auswertung: Die Datei wird beim ersten Start mit Kopfzeile angelegt, und jeder Versuch wird geschrieben, auch der fehlgeschlagene. Ein Log, das nur Erfolge kennt, zeigt später eine makellose Erfolgsquote – und ist wertlos.
Schritt 2: Den Solver instrumentieren
Damit keine Lösung am Log vorbeiläuft, kapseln Sie Übermittlung und Polling in einer Klasse. Der finally-Block ist das Herzstück: Er protokolliert auch dann, wenn ein Timeout eintrat oder die API einen Fehlercode zurückgab.
import requests
import time
class MonitoredSolver:
"""Solver with automatic metric collection."""
def __init__(self, api_key, metrics=None):
self.api_key = api_key
self.base = "https://ocr.captchaai.com"
self.metrics = metrics or MetricsCollector()
def solve(self, method, **params):
start = time.time()
task_id = ""
status = "error"
error_code = ""
try:
# Submit
data = {"key": self.api_key, "method": method, "json": 1}
data.update(params)
resp = requests.post(
f"{self.base}/in.php", data=data, timeout=30,
)
result = resp.json()
if result.get("status") != 1:
error_code = result.get("request", "UNKNOWN")
raise RuntimeError(f"Submit error: {error_code}")
task_id = result["request"]
# Poll
token = self._poll(task_id)
status = "success"
return token
except TimeoutError:
status = "timeout"
raise
except Exception as e:
error_code = str(e)[:50]
raise
finally:
duration = time.time() - start
self.metrics.record(method, duration, status, error_code, task_id)
def _poll(self, task_id, timeout=120):
start = time.time()
while time.time() - start < timeout:
time.sleep(5)
resp = requests.get(f"{self.base}/res.php", params={
"key": self.api_key, "action": "get",
"id": task_id, "json": 1,
}, timeout=15)
data = resp.json()
if data["request"] != "CAPCHA_NOT_READY":
if data.get("status") == 1:
return data["request"]
raise RuntimeError(f"Solve error: {data['request']}")
raise TimeoutError("Poll timeout")
def print_summary(self):
"""Print current session metrics."""
summary = self.metrics.get_session_summary()
print("\n=== CaptchaAI Usage Summary ===")
for method, stats in summary.items():
print(f"\n{method}:")
for key, value in stats.items():
print(f" {key}: {value}")
# Usage
metrics = MetricsCollector()
solver = MonitoredSolver("YOUR_API_KEY", metrics)
# Solve some CAPTCHAs
for i in range(10):
try:
token = solver.solve(
"userrecaptcha",
googlekey="SITE_KEY",
pageurl="https://example.com",
)
except Exception as e:
print(f"Failed: {e}")
# Print results
solver.print_summary()
Die Methode ist bewusst generisch: method und die typspezifischen Parameter reichen Sie durch – für reCAPTCHA v2 googlekey und pageurl, für Cloudflare Turnstile sitekey und pageurl. So landen alle Typen in derselben Auswertung.
Schritt 3: Berichte aus den Rohdaten erzeugen
Die Auswertung liest dieselbe CSV und aggregiert sie in drei Blickrichtungen: pro Tag, pro Methode und pro Fehlercode. Damit ist die Trendfrage („wird es schlechter?“) sauber von der Ursachenfrage („woran liegt es?“) getrennt.
import csv
import datetime
from collections import defaultdict
class UsageReport:
"""Generate usage reports from metrics CSV."""
def __init__(self, log_file="captchaai_metrics.csv"):
self.log_file = log_file
def _load_data(self, days=None):
"""Load metrics, optionally filtered by date range."""
cutoff = None
if days:
cutoff = datetime.datetime.utcnow() - datetime.timedelta(days=days)
records = []
with open(self.log_file, "r") as f:
reader = csv.DictReader(f)
for row in reader:
ts = datetime.datetime.fromisoformat(row["timestamp"])
if cutoff and ts < cutoff:
continue
row["_ts"] = ts
row["_duration"] = float(row["duration_s"])
records.append(row)
return records
def daily_summary(self, days=7):
"""Summarize usage per day."""
records = self._load_data(days=days)
by_day = defaultdict(lambda: {"count": 0, "success": 0, "total_time": 0})
for rec in records:
day = rec["_ts"].date().isoformat()
by_day[day]["count"] += 1
if rec["status"] == "success":
by_day[day]["success"] += 1
by_day[day]["total_time"] += rec["_duration"]
print(f"=== Daily Summary (last {days} days) ===")
print(f"{'Date':<12} {'Total':>6} {'Success':>8} {'Rate':>7} {'Avg Time':>9}")
for day in sorted(by_day.keys()):
stats = by_day[day]
rate = stats["success"] / stats["count"] * 100 if stats["count"] > 0 else 0
avg = stats["total_time"] / stats["count"] if stats["count"] > 0 else 0
print(f"{day:<12} {stats['count']:>6} {stats['success']:>8} {rate:>6.1f}% {avg:>8.1f}s")
def method_breakdown(self, days=30):
"""Summarize usage by CAPTCHA type."""
records = self._load_data(days=days)
by_method = defaultdict(lambda: {"count": 0, "success": 0, "total_time": 0})
for rec in records:
method = rec["method"]
by_method[method]["count"] += 1
if rec["status"] == "success":
by_method[method]["success"] += 1
by_method[method]["total_time"] += rec["_duration"]
print(f"\n=== Method Breakdown (last {days} days) ===")
print(f"{'Method':<25} {'Total':>6} {'Success':>8} {'Rate':>7} {'Avg Time':>9}")
for method in sorted(by_method.keys()):
stats = by_method[method]
rate = stats["success"] / stats["count"] * 100
avg = stats["total_time"] / stats["count"]
print(f"{method:<25} {stats['count']:>6} {stats['success']:>8} {rate:>6.1f}% {avg:>8.1f}s")
def error_breakdown(self, days=7):
"""Show error distribution."""
records = self._load_data(days=days)
errors = defaultdict(int)
for rec in records:
if rec["status"] != "success" and rec["error_code"]:
errors[rec["error_code"]] += 1
if errors:
print(f"\n=== Error Breakdown (last {days} days) ===")
for error, count in sorted(errors.items(), key=lambda x: -x[1]):
print(f" {error}: {count}")
# Usage
report = UsageReport()
report.daily_summary(days=7)
report.method_breakdown(days=30)
report.error_breakdown(days=7)
Lesen Sie die Ausgabe in dieser Reihenfolge: Tagesbericht (bricht die Erfolgsquote an einem Tag ein?), Methodenaufschlüsselung (alle Typen oder nur einer?), Fehlerverteilung. Häufen sich Parameterfehler, etwa durch einen veralteten Sitekey, liegt es an Ihrer Integration; häufen sich Timeouts bei nur einem Typ, sind eher Zielseite oder Polling-Intervall die Ursache.
Schritt 4: Guthabenverlauf und Ausgaben verfolgen
Das Guthaben fragen Sie über res.php mit action=getbalance ab. Ein Job, der den Wert stündlich in eine zweite CSV schreibt, ergibt nach wenigen Tagen eine Verbrauchskurve – damit erkennen Sie Lastspitzen und setzen einen Alarm, bevor das Konto leer ist.
import requests
import time
import csv
import datetime
class BalanceDashboard:
"""Track balance over time for spending analysis."""
def __init__(self, api_key, log_file="balance_history.csv"):
self.api_key = api_key
self.log_file = log_file
def record(self):
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key,
"action": "getbalance",
"json": 1,
})
balance = float(resp.json()["request"])
with open(self.log_file, "a", newline="") as f:
writer = csv.writer(f)
writer.writerow([
datetime.datetime.utcnow().isoformat(),
f"{balance:.4f}",
])
return balance
def get_spending(self, hours=24):
"""Calculate spending over time period."""
cutoff = datetime.datetime.utcnow() - datetime.timedelta(hours=hours)
balances = []
try:
with open(self.log_file, "r") as f:
reader = csv.reader(f)
for row in reader:
ts = datetime.datetime.fromisoformat(row[0])
if ts > cutoff:
balances.append(float(row[1]))
except FileNotFoundError:
return 0
if len(balances) < 2:
return 0
return balances[0] - balances[-1]
Rufen Sie record() zeitgesteuert auf, nicht bei jeder Lösung: Stündlich reicht, und die Datei bleibt klein genug für Monate.
Schwellenwerte, die einen Alarm rechtfertigen
Ein Dashboard ohne Schwellenwerte sieht sich nach zwei Wochen niemand mehr an. Als Ausgangspunkt dienen die dokumentierten Richtwerte je Typ: unter 60 s für reCAPTCHA v2, unter 10 s für Cloudflare Turnstile, unter 12 s für GeeTest v3 und unter 0,5 s für Bild-CAPTCHAs. Liegt Ihr Median dauerhaft darüber, prüfen Sie zuerst Polling-Intervall und Thread-Auslastung.
Beginnen Sie mit zwei Alarmen statt mit zehn: Erfolgsquote einer Methode 30 Minuten unter Ihrem Normalwert, oder Guthaben für rechnerisch weniger als 48 Stunden. Alles Weitere ergänzen Sie, sobald ein Vorfall ein fehlendes Signal offenlegt.
Monitoring skalieren: von der CSV zu Prometheus und Grafana
Solange ein einzelner Worker läuft, ist die CSV die richtige Antwort: kein zusätzlicher Dienst, jederzeit in der Tabellenkalkulation zu öffnen. Arbeiten mehrere Worker auf verschiedenen Hosts, exportieren Sie dieselben Werte mit der prometheus_client-Bibliothek als Counter und Histogram und lassen Grafana zeichnen – record() schreibt dann zusätzlich in die Registry.
Viele DACH-Teams wählen einen pragmatischen Zwischenschritt: Der Tagesbericht läuft als geplanter GitLab-CI-Job und landet als Artefakt – kein neuer Dienst, aber eine nachvollziehbare Historie.
Aufbewahrung und Datenschutz
Die Metrik-CSV enthält keine gelösten Tokens, aber Zeitstempel, Methoden und – je nach Erweiterung – Ziel-URLs. Wer Felder wie pageurl oder die IP-Adressen der eingesetzten Proxys mitschreibt, verarbeitet unter Umständen personenbezogene Daten im Sinne der DSGVO. Prüfen Sie Zweck, Rechtsgrundlage und Löschfristen, bevor Sie das Log um solche Felder erweitern.
Bewährte Praxis: Rohdaten täglich rotieren, Tagesaggregate länger vorhalten, alles Weitere löschen statt archivieren.
Fehlerbehebung
| Symptom | Ursache | Lösung |
|---|---|---|
| CSV wächst unkontrolliert | Dauerbetrieb ohne Rotation | Datei täglich rotieren |
| Lösungen fehlen im Log | Solver direkt aufgerufen | jeden Aufruf über MonitoredSolver führen |
| Zahlen passen nicht zum Konto | Fehlversuche nicht protokolliert | prüfen, ob der finally-Block immer schreibt |
| Erfolgsquote plötzlich niedrig | Parameterfehler statt Ausfälle | Sitekey und Page-URL prüfen |
| Abgeschnittene Zeilen | fehlende Sperre bei Parallelität | Schreibzugriff über threading.Lock serialisieren |
FAQ
Wie erkenne ich, ob meine Threads ausgelastet sind?
An der Zahl gleichzeitig offener Lösungen. Erweitern Sie den Sammler um einen Zähler, der beim Start erhöht und im finally-Block verringert wird, und schreiben Sie das Maximum pro Minute mit. Liegt es dauerhaft an der Thread-Grenze Ihres Plans, warten Jobs bereits.
Welche Kennzahl zeigt, dass ein größerer Plan sinnvoll ist?
Die Wartezeit vor der Übermittlung, nicht die Lösungszeit. Bleibt die Lösungszeit stabil, während die Warteschlange wächst, sind die Threads das Nadelöhr. Der Schritt von STANDARD (30 $/Monat, 15 Threads) auf ADVANCE (90 $/Monat, 50 Threads) hebt die Gleichzeitigkeit von 15 auf 50.
Wie lange sollte ich die Rohdaten aufbewahren?
30 Tage detailliert, danach nur noch Tagesaggregate. Legen Sie die Frist einmal fest und automatisieren Sie das Löschen.
Warum weichen meine Zahlen vom Verlauf im CaptchaAI-Konto ab?
Fast immer, weil Fehlversuche fehlen: Wird nur bei Erfolg protokolliert, zählt Ihr Log zu wenige Versuche. Zweithäufigste Ursache: mehrere Prozesse schreiben ohne Sperre in dieselbe Datei.
Lässt sich die Überwachung in eine CI-Pipeline einbauen?
Ja. Ein geplanter Job in GitLab CI oder GitHub Actions ruft den Tagesbericht auf, legt ihn als Artefakt ab und schlägt fehl, sobald die Erfolgsquote unter Ihren Schwellenwert rutscht.
Verwandte Leitfäden
Zahlen statt Bauchgefühl – starten Sie die Überwachung Ihrer CaptchaAI-Nutzung.