Tutoriais

Simultaneidade com taxa limitada: depósito de tokens para chamadas de API CAPTCHA

A simultaneidade não controlada envia solicitações o com menor latência possível. Isso leva a ERROR_TOO_MUCH_REQUESTS, desperdício de equilíbrio de API e custos imprevisíveis. Um token bucket permite definir uma taxa exata – “não mais que 20 envios por segundo” – enquanto ainda permite rajadas curtas quando a capacidade está disponível.

Como funciona um balde de token

[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

Propriedades principais:

  • Capacidade — tamanho máximo do burst
  • Taxa de recarga — solicitações sustentadas por segundo
  • As solicitações aguardam quando o intervalo está vazio (sem rejeição, apenas limitação)

Implementação Python

Balde de token thread-safe

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 com taxa 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}")

Implementação de JavaScript

Balde de token assíncrono

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 lote com taxa 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}`);
}

Escolhendo Parâmetros

Carga de trabalho Capacidade (explosão) Taxa de recarga (sustentada)
Raspagem leve 5 2/sec
Automação padrão 20 10/sec
Pipeline de alto volume 50 30/sec
Taxa de transferência máxima 100 50/sec

Regras básicas:

  • Defina a capacidade para taxa de recarga de 2× (permite rajadas de 2 segundos)
  • Comece de forma conservadora, aumente enquanto monitora as taxas de erro
  • Apenas envios com limite de taxa – a pesquisa é leve e autolimitada

Balde de token versus outros algoritmos

Algoritmo Comportamento mais adequado para
Balde de tokens Taxa suave com permissão de burst Chamadas de API CAPTCHA
Balde com vazamento Taxa de saída fixa, sem rajadas Requisitos rigorosos de taxas
Janela fixa Contagem por janela de tempo, rajadas de borda Contadores simples
Janela deslizante Contagem durante o período contínuo Aplicação precisa de taxas

O balde de token é o mais adequado padrão – ele permite rajadas naturais (o raspador encontra 20 CAPTCHAs de uma vez) enquanto impõe uma taxa sustentada.

Solução de problemas

Problema Causa Correção
Solicitações ainda estão sendo limitadas Limitador de taxa definido acima do permitido pela API Taxa de recarga mais baixa para corresponder aos limites do CaptchaAI
Alta latência nas solicitações Tokens esgotados, aguardando recarga Aumente a capacidade para cenários de explosão
Memória crescendo Fila de espera se acumulando Defina um tamanho máximo de fila; rejeitar solicitações em excesso
Limitador de taxa não compartilhado entre processos Apenas na memória Use bucket de token baseado em Redis para limitação de taxa distribuída

Perguntas frequentes

Devo limitar a taxa de envios, pesquisas ou ambos?

Somente envios com limite de taxa. As solicitações de pesquisa são leves e autoaceleradas via time.sleep(5). Limitar excessivamente os aumentos de pesquisa resolvem a latência sem benefícios.

Como faço para lidar com ERROR_TOO_MUCH_REQUESTS apesar da limitação de taxa?

Seu limite de taxa está muito alto. Reduza a taxa de recarga. Verifique também se vários processos compartilham a mesma chave de API – taxa agregada em todos os processos.

Posso usar um limitador de taxa por tipo de CAPTCHA?

Sim – crie buckets de token separados para diferentes tipos de CAPTCHA. Isso evita que tarefas reCAPTCHA v2 de alto volume esfomeem os envios do Turnstile.

Artigos relacionados

Próximas etapas

Crie uma solução CAPTCHA com taxa controlada -obtenha sua chave API CaptchaAIe implementar taxas de solicitação sustentáveis.

Guias relacionados:

Os comentários estão desativados para este artigo.