Wenn ein Scraping- oder Test-Job doppelt so lange läuft wie geplant, liegt das selten am Solver. Die zusätzlichen Sekunden entstehen fast immer im eigenen Code: ein starres Polling-Intervall von 5 Sekunden, eine neue TCP-Verbindung pro Anfrage und ein Ablauf, der das CAPTCHA erst nach dem Seitenaufruf einreicht.
Die Solver-Laufzeit selbst können Sie nicht beeinflussen – alles davor und danach schon, und zwar ohne Architekturumbau.
Wo die Zeit bei einer CAPTCHA-Lösung vergeht
Zerlegen Sie eine Lösung in vier Abschnitte, bevor Sie optimieren:
- Übermittlung – die Anfrage an
in.php, inklusive TCP- und TLS-Handshake. - Warteschlange – die Zeit, bis ein freier Thread die Aufgabe übernimmt.
- Solver-Laufzeit – begrenzt durch die Obergrenzen pro CAPTCHA-Typ.
- Abholung – das Polling gegen
res.php, bis das Token bereitliegt.
Drei der vier Abschnitte liegen in Ihrer Hand.
1. Polling-Intervalle adaptiv statt starr setzen
Ein festes Intervall von 5 Sekunden kostet im Mittel die halbe Intervalllänge: Ist die Lösung eine Sekunde nach der letzten Abfrage fertig, wartet Ihr Code vier Sekunden weiter. Adaptives Polling fragt anfangs eng ab und geht danach zurück.
Python
import time
import requests
API_KEY = "YOUR_API_KEY"
RESULT_URL = "https://ocr.captchaai.com/res.php"
def adaptive_poll(task_id, timeout=120):
"""Start polling at 3s, increase to 5s after 4 polls."""
start = time.time()
interval = 3 # start aggressive
polls = 0
while time.time() - start < timeout:
time.sleep(interval)
polls += 1
resp = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get",
"id": task_id, "json": "1"
}).json()
if resp["status"] == 1:
elapsed = time.time() - start
print(f"Solved in {elapsed:.1f}s ({polls} polls)")
return resp["request"]
if resp["request"] != "CAPCHA_NOT_READY":
raise Exception(resp["request"])
# Back off after initial fast polls
if polls >= 4:
interval = 5
raise TimeoutError(f"Task {task_id} timed out")
JavaScript
async function adaptivePoll(taskId, apiKey, timeout = 120000) {
const start = Date.now();
let interval = 3000;
let polls = 0;
while (Date.now() - start < timeout) {
await new Promise(r => setTimeout(r, interval));
polls++;
const resp = await fetch(
`https://ocr.captchaai.com/res.php?key=${apiKey}&action=get&id=${taskId}&json=1`
);
const data = await resp.json();
if (data.status === 1) {
console.log(`Solved in ${((Date.now() - start) / 1000).toFixed(1)}s (${polls} polls)`);
return data.request;
}
if (data.request !== 'CAPCHA_NOT_READY') {
throw new Error(data.request);
}
if (polls >= 4) interval = 5000;
}
throw new Error(`Task ${taskId} timed out`);
}
Gegenüber einem festen 5-Sekunden-Intervall spart das im Schnitt 1–4 Sekunden pro Lösung. Drei Regeln dazu:
- Nicht unter 3 Sekunden abfragen – die Lösung wird dadurch nicht früher fertig, das Risiko von Rate-Limiting steigt aber.
- Nach vier engen Abfragen auf 5 Sekunden zurückgehen.
- Ein Timeout von 120 Sekunden setzen, damit ein hängender Task keinen Worker blockiert.
2. HTTP-Verbindungen wiederverwenden
Jede neue Verbindung kostet einen TCP- und einen TLS-Handshake. Bei sechs bis acht Abfragen pro Lösung summiert sich das – spürbar, sobald ein Job zehntausende Seiten verarbeitet:
Python
session = requests.Session()
# Use session.get() and session.post() instead of requests.get/post
# The session reuses TCP connections automatically
Node.js
const { Agent } = require('http');
const axios = require('axios');
const client = axios.create({
httpAgent: new Agent({ keepAlive: true, maxSockets: 10 }),
timeout: 10000,
});
// Use client.get() and client.post() for all API calls
Das spart rund 50–100 ms pro Anfrage – am deutlichsten bei Bild- und Grid-CAPTCHAs, wo der Handshake einen großen Anteil an der Gesamtdauer hat.
3. CAPTCHAs vorab einreichen, statt zu warten
Der größte Hebel ist nicht schnelleres, sondern paralleles Warten. Während Ihr Crawler Seite N verarbeitet, reichen Sie die Aufgabe für Seite N+1 bereits ein:
from concurrent.futures import ThreadPoolExecutor
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
def prefetch_submit(sitekey, page_url):
resp = session.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
})
data = resp.json()
if data["status"] == 1:
return data["request"]
raise Exception(data["request"])
# Submit next page's CAPTCHA while processing current page
with ThreadPoolExecutor(max_workers=2) as pool:
# Submit CAPTCHA for page 2 while processing page 1
future_task = pool.submit(prefetch_submit, "6Le-SITEKEY", "https://example.com/page/2")
# Process page 1...
process_page(current_data)
# Now get the pre-submitted task ID and poll
task_id = future_task.result()
token = adaptive_poll(task_id)
So überlappt die Lösungszeit mit der Verarbeitung, und die spürbare Wartezeit sinkt gegen null. Zwei Grenzen:
- Tokens sind nicht beliebig lange gültig – bei reCAPTCHA liegt das Fenster bei rund 120 Sekunden. Reichen Sie nur so weit im Voraus ein, wie Ihre Verarbeitung dauert.
- Jede offene Aufgabe belegt einen Thread. Zehn Seiten im Voraus brauchen zehn freie Threads.
Praxisbeispiel: Preismonitoring auf einem Hetzner-Server
Ein Preismonitoring-Job läuft nachts auf einem Cloud-Server in Falkenstein und ruft 400 Produktseiten ab, 120 davon mit reCAPTCHA-v2-Abfrage. Sequenziell abgearbeitet steht der Job bei jeder dieser Seiten still, bis das Token vorliegt – die Wartezeit addiert sich vollständig auf die Laufzeit.
Wird die nächste Seite vorab eingereicht, verschwindet der Großteil davon im Hintergrund. Läuft derselbe Job als GitLab-CI-Schedule, zählt das doppelt: Eine kürzere Laufzeit passt in ein enges Wartungsfenster und kostet weniger Runner-Minuten.
4. Pro Szenario die schnellere Methode wählen
Zwei Fälle, in denen ein anderer Weg messbar schneller ist:
| Szenario | Langsamer Weg | Schnellere Alternative |
|---|---|---|
| reCAPTCHA v2 mit bekannter Callback-Funktion | userrecaptcha + Polling |
userrecaptcha mit pingback (Callback-URL) |
| Bild-CAPTCHA mit reinem Zifferninhalt | base64 in voller Auflösung |
base64 mit numeric=1 |
5. Proxy nur senden, wenn die Zielseite ihn verlangt
Jeder Proxy fügt einen Netzwerk-Hop hinzu. Übergeben Sie die Parameter nur, wenn die Zielseite die Anfrage aus einem bestimmten Netz erwartet:
# Without proxy — faster for most use cases
data = {
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
}
# With proxy — only when required
data["proxy"] = "user:[email protected]:8080"
data["proxytype"] = "HTTP"
Zwei Hinweise:
- Ein Residential-Proxy ist langsamer als ein Rechenzentrums-Proxy und kostet ohne Not nur Zeit.
- IP-Adressen gelten nach DSGVO als personenbezogene Daten. Prüfen Sie Rechtsgrundlage und Auftragsverarbeitung, bevor Sie Traffic über einen fremden Anbieter leiten.
6. Polling vermeiden: Callback-URLs mit pingback
Hat Ihre Anwendung einen öffentlich erreichbaren Endpunkt, entfällt die Abholphase ganz:
resp = session.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
"pingback": "https://your-server.com/captcha-callback",
})
CaptchaAI sendet das Ergebnis an die angegebene URL, sobald die Lösung vorliegt. Voraussetzung:
- öffentliche HTTPS-URL, kein Dienst hinter NAT oder VPN,
- Firewall-Regeln, die eingehende Anfragen zulassen,
- eine Zuordnung der Task-ID zum wartenden Prozess, etwa über Redis.
Threads bestimmen den Durchsatz, Polling die Einzellatenz
Zwei Größen werden oft verwechselt: Polling, Verbindungs-Pooling und Prefetching senken die Latenz einer Lösung. Wie viele Lösungen gleichzeitig laufen, hängt an den Threads – CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro Lösung, mit unbegrenzt vielen Lösungen pro Thread:
- BASIC – 15 $ pro Monat, 5 Threads
- ADVANCE – 90 $ pro Monat, 50 Threads
- ENTERPRISE – 300 $ pro Monat, 200 Threads
Wer 40 Seiten parallel verarbeitet, braucht entsprechend viele Threads; ein engeres Intervall ersetzt sie nicht. Preise in US-Dollar.
7. Vor und nach jeder Änderung messen
Ohne Messreihe bleibt Tuning Bauchgefühl. Zwanzig Durchläufe pro Konfiguration reichen für eine Tendenz; aussagekräftig sind Median und P95:
import statistics
def benchmark(solve_func, iterations=20):
times = []
for i in range(iterations):
start = time.time()
try:
solve_func()
times.append(time.time() - start)
except Exception:
pass
if times:
print(f"Samples: {len(times)}/{iterations}")
print(f"Mean: {statistics.mean(times):.1f}s")
print(f"Median: {statistics.median(times):.1f}s")
print(f"P95: {sorted(times)[int(len(times)*0.95)]:.1f}s")
print(f"Min: {min(times):.1f}s")
print(f"Max: {max(times):.1f}s")
Notieren Sie zu jedem Lauf Datum, CAPTCHA-Typ und Thread-Zahl – ohne diesen Kontext sind zwei Messreihen nicht vergleichbar.
Welche Zielwerte pro CAPTCHA-Typ realistisch sind
Die Obergrenzen pro Typ stecken den Rahmen ab. Je kürzer die Lösung selbst dauert, desto stärker fallen Handshake und Polling-Overhead ins Gewicht:
| CAPTCHA-Typ | Obergrenze | Größter Hebel |
|---|---|---|
| Bild/OCR | < 0,5 s | Verbindungs-Pooling |
| reCAPTCHA v3 | < 4 s | Polling-Intervall |
| Cloudflare Turnstile | < 10 s | Polling-Intervall |
| GeeTest v3 | < 12 s | Polling-Intervall |
| reCAPTCHA v2 | < 60 s | Prefetching, Callback-URL |
Typische Stolpersteine
| Symptom | Ursache | Lösung |
|---|---|---|
| Latenz sinkt trotz adaptivem Polling nicht | Aufrufe laufen weiter über requests.get() |
Auf session.get() umstellen |
| Vorab eingereichte Tokens sind abgelaufen | Verarbeitung dauert länger als die Gültigkeit | Prefetch-Fenster verkleinern |
| Callback-URL erhält nie Daten | Endpunkt von außen nicht erreichbar | URL und Firewall-Regeln prüfen |
| Enges Polling erzeugt Fehlerantworten | Abfragen unter 2 s lösen Rate-Limiting aus | Mindestintervall 3 Sekunden |
| Aufgaben warten in der Warteschlange | Alle Threads belegt | Parallelität begrenzen oder Threads aufstocken |
Häufige Fragen
Wie kurz darf das Polling-Intervall sein?
Drei Sekunden sind die Untergrenze. Darunter steigt nur die Zahl der CAPCHA_NOT_READY-Antworten, und Sie riskieren Rate-Limiting.
Brauche ich mehr Threads oder ein besseres Polling?
Es kommt darauf an, wo Sie warten. Polling und Prefetching senken die Latenz einer Lösung, Threads erhöhen den Durchsatz. Stehen Aufgaben in der Warteschlange, hilft nur mehr Parallelität – etwa ADVANCE mit 50 Threads für 90 $ pro Monat.
Was passiert, wenn ein vorab eingereichtes Token abläuft?
Die Zielseite lehnt es ab, und Sie brauchen eine neue Lösung. reCAPTCHA-Tokens sind rund 120 Sekunden gültig – halten Sie das Prefetch-Fenster deutlich darunter.
Lohnt sich pingback in einer CI-Pipeline?
Nur, wenn der Runner einen öffentlich erreichbaren Endpunkt hat. In vielen GitLab-CI-Setups läuft er hinter NAT; dann bleibt adaptives Polling die praktikablere Variante.
Warum schwanken die Messwerte zwischen zwei Läufen?
Weil Warteschlange, Netzwerkweg und CAPTCHA-Konfiguration der Zielseite variieren. Vergleichen Sie Median und P95 aus mindestens 20 Läufen, nie Einzelmessungen.
Latenz senken – mit CaptchaAI
Holen Sie sich Ihren API-Schlüssel unter captchaai.com und stellen Sie Ihre Pipeline auf adaptives Polling, wiederverwendete Verbindungen und vorab eingereichte Aufgaben um.