La simultaneidad no controlada envía solicitudes lo más rápido posible. Eso conduce a ERROR_TOO_MUCH_REQUESTS, saldo de API desperdiciado y costos impredecibles. Un depósito de tokens le permite establecer una velocidad exacta (“no más de 20 envíos por segundo”) y, al mismo tiempo, permite ráfagas cortas cuando hay capacidad disponible.
Cómo funciona un depósito de tokens
[Bucket] capacity=20, refill=10/sec
Time 0: ████████████████████ 20 tokens available
→ 15 requests consume 15 tokens
Time 0: █████ 5 tokens remain
Time 1s: ███████████████ 15 tokens (5 + 10 refilled)
→ 15 requests consume 15 tokens
Time 1s: (empty) 0 tokens
Time 2s: ██████████ 10 tokens (0 + 10 refilled)
→ Request waits if bucket is empty
Propiedades clave:
- Capacidad: tamaño máximo de ráfaga
- Tasa de recarga: solicitudes sostenidas por segundo
- Las solicitudes esperan cuando el depósito está vacío (sin rechazo, solo limitación)
Implementación de Python
Cubo de tokens seguro para subprocesos
import time
import threading
class TokenBucket:
def __init__(self, capacity, refill_rate):
"""
Args:
capacity: Maximum tokens (burst size)
refill_rate: Tokens added per second
"""
self.capacity = capacity
self.refill_rate = refill_rate
self.tokens = capacity
self.last_refill = time.monotonic()
self.lock = threading.Lock()
def acquire(self, timeout=None):
"""Block until a token is available."""
deadline = time.monotonic() + timeout if timeout else float("inf")
while True:
with self.lock:
self._refill()
if self.tokens >= 1:
self.tokens -= 1
return True
# Check timeout
if time.monotonic() >= deadline:
return False
# Wait before retrying (avoid busy loop)
time.sleep(min(1.0 / self.refill_rate, 0.1))
def _refill(self):
now = time.monotonic()
elapsed = now - self.last_refill
new_tokens = elapsed * self.refill_rate
self.tokens = min(self.capacity, self.tokens + new_tokens)
self.last_refill = now
Solucionador CAPTCHA de tasa limitada
import os
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
# Allow 10 submissions/sec with burst of 20
rate_limiter = TokenBucket(capacity=20, refill_rate=10)
def solve_captcha_rate_limited(sitekey, pageurl):
"""Solve with rate limiting on submission."""
# Wait for token before submitting
rate_limiter.acquire()
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1
})
data = resp.json()
if data.get("status") != 1:
raise RuntimeError(data.get("request"))
captcha_id = data["request"]
# Polling doesn't need rate limiting (separate concern)
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": captcha_id, "json": 1
}).json()
if result.get("status") == 1:
return result["request"]
if result.get("request") != "CAPCHA_NOT_READY":
raise RuntimeError(result.get("request"))
raise TimeoutError("Solve timeout")
# Run 100 tasks through rate limiter
tasks = [
{"sitekey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"pageurl": f"https://example.com/p/{i}"}
for i in range(100)
]
with ThreadPoolExecutor(max_workers=30) as executor:
futures = {
executor.submit(
solve_captcha_rate_limited, t["sitekey"], t["pageurl"]
): t for t in tasks
}
for future in as_completed(futures):
task = futures[future]
try:
solution = future.result()
print(f"[OK] {task['pageurl']}")
except Exception as e:
print(f"[ERR] {task['pageurl']}: {e}")
Implementación de JavaScript
Depósito de tokens asíncronos
class TokenBucket {
constructor(capacity, refillRate) {
this.capacity = capacity;
this.refillRate = refillRate; // tokens per second
this.tokens = capacity;
this.lastRefill = Date.now();
this.waitQueue = [];
}
_refill() {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRate);
this.lastRefill = now;
}
async acquire() {
this._refill();
if (this.tokens >= 1) {
this.tokens -= 1;
return;
}
// Wait until a token is available
const waitTime = ((1 - this.tokens) / this.refillRate) * 1000;
await new Promise((resolve) => setTimeout(resolve, waitTime));
this._refill();
this.tokens -= 1;
}
}
Solucionador de lotes de velocidad limitada
const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const rateLimiter = new TokenBucket(20, 10); // 20 burst, 10/sec sustained
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function solveCaptchaLimited(sitekey, pageurl) {
// Wait for rate limit token
await rateLimiter.acquire();
const submitResp = await axios.post(
"https://ocr.captchaai.com/in.php",
null,
{
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: pageurl,
json: 1,
},
}
);
if (submitResp.data.status !== 1) {
throw new Error(submitResp.data.request);
}
const captchaId = submitResp.data.request;
for (let i = 0; i < 60; i++) {
await sleep(5000);
const result = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
});
if (result.data.status === 1) return result.data.request;
if (result.data.request !== "CAPCHA_NOT_READY") {
throw new Error(result.data.request);
}
}
throw new Error("TIMEOUT");
}
// Solve 100 tasks — rate limiter ensures max 10 submissions/sec
async function batchSolve(tasks) {
const results = await Promise.allSettled(
tasks.map((t) => solveCaptchaLimited(t.sitekey, t.pageurl))
);
const solved = results.filter((r) => r.status === "fulfilled").length;
const failed = results.filter((r) => r.status === "rejected").length;
console.log(`Solved: ${solved}, Failed: ${failed}`);
}
Elegir parámetros
| Carga de trabajo | Capacidad (explosión) | Tasa de recarga (sostenida) |
|---|---|---|
| raspado ligero | 5 | 2/sec |
| Automatización estándar | 20 | 10/sec |
| Tubería de alto volumen | 50 | 30/sec |
| Rendimiento máximo | 100 | 50/sec |
Reglas generales:
- Establezca la capacidad en una tasa de recarga de 2 × (permite ráfagas de 2 segundos)
- Comience de manera conservadora, aumente mientras monitorea las tasas de error
- Envíos con límite de velocidad únicamente: el sondeo es liviano y autolimitado
Token Bucket frente a otros algoritmos
| Algoritmo | Comportamiento | Lo mejor para |
|---|---|---|
| Cubo de fichas | Velocidad suave con margen de ráfaga | Llamadas API CAPTCHA |
| balde con fugas | Tasa de checkout fija, sin ráfagas | Requisitos de tarifas estrictos |
| ventana fija | Conteo por ventana de tiempo, ráfagas de borde | Contadores simples |
| ventana corredera | Contar durante el período continuo | Aplicación precisa de las tarifas |
El depósito de tokens es una opción sólida valor predeterminado: permite ráfagas naturales (el raspador encuentra 20 CAPTCHA a la vez) al tiempo que aplica una tasa sostenida.
Solución de problemas
| Problema | causa | Solución |
|---|---|---|
| Las solicitudes siguen siendo limitadas | Limitador de velocidad establecido más alto de lo que permite la API | Tasa de recarga más baja para igualar los límites de CaptchaAI |
| Alta latencia en solicitudes | Tokens agotados, esperando recarga | Aumentar la capacidad para escenarios de ráfaga |
| Memoria creciendo | Cola de espera acumulándose | Establecer un tamaño máximo de cola; rechazar solicitudes excesivas |
| Limitador de velocidad no compartido entre procesos | Sólo en memoria | Utilice un depósito de tokens basado en Redis para limitar la tasa distribuida |
Preguntas frecuentes
¿Debo limitar la velocidad de los envíos, las consultas o ambos?
Envíos con límite de tarifa únicamente. Las solicitudes de sondeo son livianas y se aceleran automáticamente a través de time.sleep(5). La limitación excesiva de los aumentos de sondeo resuelve la latencia sin beneficio.
¿Cómo manejo ERROR_TOO_MUCH_REQUESTS a pesar de la limitación de tasa?
Su límite de tasa está establecido demasiado alto. Reduzca la tasa de recarga. También verifique si varios procesos comparten la misma clave API: tasa agregada en todos los procesos.
¿Puedo usar un limitador de velocidad por tipo CAPTCHA?
Sí: cree depósitos de tokens separados para diferentes tipos de CAPTCHA. Esto evita que las tareas reCAPTCHA v2 de gran volumen agoten los envíos de Turnstile.
Artículos relacionados
- Llamadas a API Captcha de patrón de disyuntor
Próximos pasos
Cree una resolución de CAPTCHA con velocidad controlada:obtenga su clave API CaptchaAIe implementar tasas de solicitud sostenibles.
Guías relacionadas:
- Tarifa que limita sus propias solicitudes
- Límites de velocidad y limitación
- Resolviendo 10,000 tareas por hora