CaptchaAI zählt keine Anfragen pro Sekunde mit: Es gibt weder ein hartes Submit-Limit noch ein Tageskontingent. Ihren Durchsatz begrenzen genau zwei Größen – die Threads Ihres Plans und die momentane Auslastung der Worker. Wer beide kennt, drosselt an der richtigen Stelle und sieht ERROR_NO_SLOT_AVAILABLE deutlich seltener.
Dieser Leitfaden zeigt, welche Grenzen tatsächlich gelten und mit welchen Bausteinen – Backoff, Token-Bucket, Semaphore, adaptives Polling – Sie Ihre Pipeline auch bei Lastspitzen ruhig halten.
Rate-Limits bei CaptchaAI: welche Grenzen wirklich gelten
CaptchaAI arbeitet nicht mit klassischem Rate-Limiting pro Sekunde. Die Kapazität ergibt sich aus der Verfügbarkeit der Worker und aus Ihrer Thread-Zuteilung:
| Faktor | Verhalten |
|---|---|
| Übermittlungsrate | kein festes Limit pro Sekunde |
| Gleichzeitige Tasks | so viele, wie Ihr Plan an Threads vorsieht |
| Polling-Intervall | empfohlen: alle 5 Sekunden pro Task |
| Guthabenabfrage | kein Limit |
Ein Thread ist genau ein CAPTCHA, das gerade in Bearbeitung ist. Sobald die Lösung zurückkommt, nimmt derselbe Thread die nächste Aufgabe an. Abgerechnet wird pro Thread und Monat – nicht pro Lösung, ohne Aufschlag nach CAPTCHA-Typ und ohne tägliche Obergrenze. BASIC beginnt bei 15 $ mit 5 Threads, ENTERPRISE liegt bei 300 $ mit 200 Threads; die vollständige Staffel bis VIP-3 steht auf der Preisseite. (Preise in US-Dollar.)
Sind alle Worker belegt, beantwortet in.php neue Übermittlungen mit ERROR_NO_SLOT_AVAILABLE. Das ist weder ein Fehler in Ihrem Code noch eine Sperre Ihres Kontos, sondern ein Rückstausignal: Die Warteschlange ist voll, versuchen Sie es kurz darauf erneut.
Kapazität planen: ab welchem Volumen Drosselung nötig wird
Bevor Sie Code schreiben, sollten Sie Ihre Größenordnung kennen:
| Volumen | Parallelität | Strategie |
|---|---|---|
| unter 100/Stunde | 1–5 | sequenziell, keine Drosselung nötig |
| 100–1.000/Stunde | 5–20 | Parallelität über eine Semaphore begrenzen |
| 1.000–10.000/Stunde | 20–50 | asynchron mit Warteschlange und Callbacks |
| über 10.000/Stunde | 50–100 | Worker-Pool, dedizierte Kapazität |
Ab etwa 10.000 Lösungen pro Stunde lohnt sich eine Rücksprache mit dem CaptchaAI-Support über dedizierte Kapazität.
Ein Beispiel aus der Praxis: Ein Preisvergleichsdienst betreibt seine Crawler auf zwei Hetzner-VPS und startet die Läufe nachts per GitLab-CI-Schedule. Tagsüber fallen kaum 200 CAPTCHAs pro Stunde an, zwischen 2 und 4 Uhr starten alle Jobs gleichzeitig und erzeugen das Zehnfache. Ohne clientseitige Begrenzung feuert die Pipeline alles auf einmal ab; eine Semaphore auf 40 gleichzeitige Tasks verteilt dieselbe Last gleichmäßig und senkt die Fehlerquote spürbar. Ähnlich verhalten sich Terminportale wie die BLS-Buchungsstrecken: wenige Minuten hohe Last, danach lange Ruhe.
Hinweis zur DSGVO: Laufen Ihre Crawler über Residential-Proxys, gehört die Datenschutzfrage in dieselbe Planung. IP-Adressen sind personenbezogene Daten; Rechtsgrundlage und Datenflüsse sollten Sie unabhängig von der technischen Drosselung dokumentieren.
ERROR_NO_SLOT_AVAILABLE mit exponentiellem Backoff abfangen
Faustregel: Bei einem belegten Slot warten und erneut übermitteln – mit wachsendem Abstand und einer Obergrenze, damit ein einzelner Task nicht endlos hängt.
Python
import time
import requests
API_KEY = "YOUR_API_KEY"
def submit_with_backoff(params, max_retries=5):
params["key"] = API_KEY
for attempt in range(max_retries):
resp = requests.get(
"https://ocr.captchaai.com/in.php", params=params
)
if resp.text.startswith("OK|"):
return resp.text.split("|")[1]
if resp.text == "ERROR_NO_SLOT_AVAILABLE":
wait = min(2 ** attempt * 2, 60) # 2, 4, 8, 16, 32s max 60
print(f"No slots, waiting {wait}s (attempt {attempt + 1})")
time.sleep(wait)
continue
raise Exception(f"Submit error: {resp.text}")
raise Exception("Max retries exceeded — no slots available")
Node.js
async function submitWithBackoff(params, maxRetries = 5) {
params.key = process.env.CAPTCHAAI_API_KEY;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const resp = await axios.get("https://ocr.captchaai.com/in.php", {
params,
});
const text = String(resp.data);
if (text.startsWith("OK|")) {
return text.split("|")[1];
}
if (text === "ERROR_NO_SLOT_AVAILABLE") {
const wait = Math.min(2 ** attempt * 2000, 60000);
console.log(`No slots, waiting ${wait}ms (attempt ${attempt + 1})`);
await new Promise((r) => setTimeout(r, wait));
continue;
}
throw new Error(`Submit error: ${text}`);
}
throw new Error("Max retries exceeded");
}
Beide Varianten verdoppeln die Wartezeit pro Versuch und deckeln sie bei 60 Sekunden. Entscheidend ist die Unterscheidung im Code: Nur ERROR_NO_SLOT_AVAILABLE wird wiederholt. Alle anderen Rückmeldungen – etwa ein ungültiger Schlüssel oder ein fehlender sitekey – sind dauerhafte Fehler, die ein erneuter Versuch nicht heilt; sie gehören sofort ins Logging.
Clientseitig drosseln: Token-Bucket und Semaphore
Backoff reagiert erst, wenn es zu spät ist. Ruhiger läuft eine Pipeline, die von vornherein nicht mehr Last erzeugt, als sie verarbeiten kann. Zwei Bausteine genügen dafür: Ein Token-Bucket begrenzt die Übermittlungen pro Sekunde, eine Semaphore die Zahl der offenen Tasks.
Token-Bucket in Python
import time
import threading
class RateLimiter:
def __init__(self, rate, per=1.0):
"""Allow `rate` requests per `per` seconds."""
self.rate = rate
self.per = per
self.tokens = rate
self.last_refill = time.monotonic()
self.lock = threading.Lock()
def acquire(self):
with self.lock:
now = time.monotonic()
elapsed = now - self.last_refill
self.tokens = min(self.rate, self.tokens + elapsed * (self.rate / self.per))
self.last_refill = now
if self.tokens >= 1:
self.tokens -= 1
return
else:
sleep_time = (1 - self.tokens) * (self.per / self.rate)
time.sleep(sleep_time)
self.acquire()
# Allow 10 submissions per second
limiter = RateLimiter(rate=10, per=1.0)
def submit_limited(params):
limiter.acquire()
return submit_with_backoff(params)
Parallelität mit einer Asyncio-Semaphore begrenzen
import asyncio
# Limit to 20 concurrent tasks
semaphore = asyncio.Semaphore(20)
async def solve_limited(solver, session, params):
async with semaphore:
return await solver.solve(session, params)
Setzen Sie die Semaphore auf die Thread-Zahl Ihres Plans oder knapp darunter. Höher zu gehen bringt nichts: Die zusätzlichen Tasks warten dann auf der Serverseite statt in Ihrem Prozess.
Polling-Intervalle richtig wählen
res.php sollte pro Task nicht häufiger als alle 5 Sekunden abgefragt werden. Kürzere Abstände beschleunigen nichts, erzeugen aber unnötige Anfragen. Bewährt hat sich ein adaptives Muster: kurze Intervalle am Anfang, längere, je länger die Lösung dauert.
async def smart_poll(session, task_id, solver):
"""Polls with adaptive intervals."""
intervals = [5, 5, 5, 10, 10, 15, 15, 30, 30, 60]
for wait in intervals:
await asyncio.sleep(wait)
result = await solver.check(session, task_id)
if result is not None:
return result
raise TimeoutError(f"Task {task_id} timed out")
Die Staffel deckt rund 3 Minuten ab. Bei schnellen Typen wie Cloudflare Turnstile liefert meist schon die erste oder zweite Abfrage ein Ergebnis; ein Rasterbild-CAPTCHA erreicht häufiger die späteren Intervalle.
Nutzung messen: Anfragerate und Fehlerquote
Ohne Messwerte ist jede Drosselung geraten. Ein schlankes Zeitfenster über die letzten 60 Sekunden genügt, um Anfragerate und Fehlerquote laufend zu sehen:
import time
from collections import deque
class APIMetrics:
def __init__(self, window=60):
self.window = window
self.requests = deque()
self.errors = deque()
def record_request(self):
now = time.time()
self.requests.append(now)
self._cleanup(self.requests, now)
def record_error(self, error_code):
now = time.time()
self.errors.append((now, error_code))
self._cleanup_tuples(self.errors, now)
def get_rate(self):
now = time.time()
self._cleanup(self.requests, now)
return len(self.requests) / self.window
def get_error_rate(self):
now = time.time()
self._cleanup(self.requests, now)
self._cleanup_tuples(self.errors, now)
if not self.requests:
return 0
return len(self.errors) / len(self.requests)
def _cleanup(self, dq, now):
while dq and dq[0] < now - self.window:
dq.popleft()
def _cleanup_tuples(self, dq, now):
while dq and dq[0][0] < now - self.window:
dq.popleft()
metrics = APIMetrics()
# Use in your submit function
def submit_tracked(params):
metrics.record_request()
try:
return submit_with_backoff(params)
except Exception as e:
metrics.record_error(str(e))
raise
# Check metrics periodically
print(f"Rate: {metrics.get_rate():.1f} req/s")
print(f"Error rate: {metrics.get_error_rate():.1%}")
Zwei Werte verdienen eine Alarmschwelle. Steigt die Fehlerquote über wenige Prozent, ist Ihre Parallelität zu hoch angesetzt. Bleibt die Anfragerate dauerhaft deutlich unter dem, was Ihr Plan an Threads hergibt, drosseln Sie zu stark und verschenken Durchsatz.
Drei Drosselungsfehler, die in Produktion teuer werden
- Sofortiger erneuter Versuch ohne Wartezeit. Eine Schleife, die nach
ERROR_NO_SLOT_AVAILABLEunmittelbar wieder übermittelt, verschärft den Rückstau, statt ihn abzubauen. - Backoff ohne Obergrenze. Ohne Deckel wächst die Wartezeit ins Absurde; 60 Sekunden sind ein praxistauglicher Maximalwert.
- Drosselung nur pro Prozess. Laufen fünf Container parallel, gilt Ihr Limit fünfmal. Verwalten Sie das Kontingent zentral, etwa über einen gemeinsamen Zähler in Redis.
Häufige Fragen
Wie viele CAPTCHAs kann ich gleichzeitig lösen lassen?
So viele, wie Ihr Plan an Threads enthält: 5 bei BASIC, 200 bei ENTERPRISE, 5.000 bei VIP-3. Innerhalb dieser Grenze ist die Zahl der Lösungen pro Monat nicht gedeckelt.
Wie lange sollte ein Backoff maximal warten?
Etwa 60 Sekunden. Danach ist es sinnvoller, den Task in eine Warteschlange zurückzustellen, als den Prozess weiter zu blockieren.
Kostet ein zurückgewiesener Submit zusätzliches Guthaben?
Nein. Abgerechnet wird pro Thread und Monat, nicht pro Lösung – ein zurückgewiesener Submit erzeugt keine separate Gebühr und belegt nur kurz eine Verbindung.
Warum bleibt ERROR_NO_SLOT_AVAILABLE dauerhaft stehen?
Dann liegt Ihre Dauerlast über der Thread-Zuteilung. Prüfen Sie zuerst, ob die Semaphore wirklich greift; reicht das nicht, erhöht ein größerer Plan die Thread-Zahl.
Woran erkenne ich, dass meine Drosselung zu streng eingestellt ist?
An einer niedrigen Anfragerate bei gleichzeitig sehr niedriger Fehlerquote. Erhöhen Sie die Parallelität dann schrittweise, bis die Fehlerquote messbar anzieht, und nehmen Sie einen Schritt zurück.