Tutoriales

Simultaneidad con tasa limitada: depósito de tokens para llamadas API CAPTCHA

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:

Los comentarios están deshabilitados para este artículo.