Como lidar com CAPTCHA quando sua agência atende vários clientes ao mesmo tempo? A resposta não é escrever uma rotina de resolução para cada projeto novo — é montar um único pipeline compartilhado, com fila, workers e isolamento de dados por client_id, que qualquer conta nova simplesmente entra. Este guia mostra a arquitetura completa com a API da CaptchaAI, do enfileiramento de tarefas ao tratamento de erros, com exemplos funcionais em Python e Node.js.
Arquitetura de um pipeline de CAPTCHA multicliente
Em vez de multiplicar scripts de resolução por projeto, centralize a lógica em quatro peças que qualquer cliente novo reaproveita:
┌──────────────┐ ┌───────────────┐ ┌──────────────┐
│ Client A │──▶ │ │ │ │
│ Client B │──▶ │ Task Queue │──▶ │ CaptchaAI │
│ Client C │──▶ │ │ │ API │
└──────────────┘ └───────────────┘ └──────────────┘
│ │
▼ ▼
┌───────────────┐ ┌──────────────┐
│ Result Store │◀── │ Polling │
│ (Redis/DB) │ │ Workers │
└───────────────┘ └──────────────┘
As quatro peças do pipeline
| Peça | Função |
|---|---|
| Ingestão de tarefas | Recebe os pedidos de resolução vindos dos scrapers de cada cliente |
| Fila | Armazena as tarefas em buffer e aplica limites de simultaneidade por cliente |
| Workers de resolução | Enviam a tarefa à CaptchaAI e consultam o resultado |
| Armazenamento de resultados (Result Store) | Guarda os tokens resolvidos, particionados por cliente, para consumo posterior |
Pipeline em Python: da fila ao token resolvido
A classe abaixo faz o trabalho pesado: enfileira tarefas por cliente, envia cada uma para a API da CaptchaAI e faz o polling do resultado sem travar as demais tarefas que esperam na fila.
Classe principal do solver
import requests
import time
from dataclasses import dataclass
from typing import Optional
from collections import deque
from threading import Lock
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
@dataclass
class SolveRequest:
client_id: str
method: str
params: dict
callback: Optional[callable] = None
@dataclass
class SolveResult:
client_id: str
task_id: str
token: Optional[str] = None
error: Optional[str] = None
class CaptchaPipeline:
def __init__(self, api_key: str, max_concurrent: int = 10):
self.api_key = api_key
self.max_concurrent = max_concurrent
self.queue = deque()
self.active = {}
self.lock = Lock()
def enqueue(self, request: SolveRequest):
with self.lock:
self.queue.append(request)
def submit_task(self, request: SolveRequest) -> Optional[str]:
data = {
"key": self.api_key,
"method": request.method,
"json": 1,
**request.params
}
try:
resp = requests.post(SUBMIT_URL, data=data, timeout=15)
result = resp.json()
if result.get("status") == 1:
return result["request"]
else:
print(f"[{request.client_id}] Submit error: {result.get('error_text', result.get('request'))}")
return None
except requests.RequestException as e:
print(f"[{request.client_id}] Network error: {e}")
return None
def poll_result(self, task_id: str, max_wait: int = 120) -> Optional[str]:
elapsed = 0
interval = 5
while elapsed < max_wait:
time.sleep(interval)
elapsed += interval
try:
resp = requests.get(RESULT_URL, params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1
}, timeout=10)
result = resp.json()
if result.get("status") == 1:
return result["request"]
elif result.get("request") == "CAPCHA_NOT_READY":
continue
else:
print(f"Poll error for {task_id}: {result.get('error_text', result.get('request'))}")
return None
except requests.RequestException:
continue
return None
def process_queue(self):
while self.queue or self.active:
# Fill active slots
with self.lock:
while self.queue and len(self.active) < self.max_concurrent:
request = self.queue.popleft()
task_id = self.submit_task(request)
if task_id:
self.active[task_id] = request
# Poll active tasks
completed = []
for task_id, request in list(self.active.items()):
token = self.poll_result(task_id, max_wait=10)
if token:
result = SolveResult(
client_id=request.client_id,
task_id=task_id,
token=token
)
if request.callback:
request.callback(result)
completed.append(task_id)
with self.lock:
for task_id in completed:
del self.active[task_id]
A fila roda em memória neste exemplo; em produção, prefira uma fila persistente (Redis, banco de dados) para sobreviver a reinícios — voltamos a esse ponto na seção sobre observabilidade mais abaixo.
Uso multicliente
Cada cliente entra no mesmo pipeline apenas trocando method e params — não é preciso duplicar a classe para cada projeto novo:
pipeline = CaptchaPipeline(api_key="YOUR_API_KEY", max_concurrent=15)
# Client A — reCAPTCHA v2
pipeline.enqueue(SolveRequest(
client_id="client_a",
method="userrecaptcha",
params={
"googlekey": "6Le-SITEKEY-A",
"pageurl": "https://client-a-staging.example.com/qa-form"
},
callback=lambda r: print(f"[{r.client_id}] Solved: {r.token[:40]}...")
))
# Client B — Turnstile
pipeline.enqueue(SolveRequest(
client_id="client_b",
method="turnstile",
params={
"sitekey": "0x4AAAA-SITEKEY-B",
"pageurl": "https://client-b-target.com/login"
},
callback=lambda r: print(f"[{r.client_id}] Solved: {r.token[:40]}...")
))
pipeline.process_queue()
Pipeline em Node.js: fila assíncrona com Promises
A versão em Node.js troca o polling bloqueante por async/await e resolve cada tarefa como uma Promise independente — o que facilita plugar o pipeline em um servidor Express ou em uma fila de mensagens já existente.
const axios = require("axios");
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
class CaptchaPipeline {
constructor(apiKey, maxConcurrent = 10) {
this.apiKey = apiKey;
this.maxConcurrent = maxConcurrent;
this.queue = [];
this.activeCount = 0;
}
enqueue(clientId, method, params) {
return new Promise((resolve, reject) => {
this.queue.push({ clientId, method, params, resolve, reject });
this._processNext();
});
}
async _processNext() {
if (this.activeCount >= this.maxConcurrent || this.queue.length === 0) return;
this.activeCount++;
const task = this.queue.shift();
try {
const token = await this._solve(task);
task.resolve({ clientId: task.clientId, token });
} catch (err) {
task.reject(err);
} finally {
this.activeCount--;
this._processNext();
}
}
async _solve(task) {
const submitResp = await axios.post(SUBMIT_URL, null, {
params: {
key: this.apiKey,
method: task.method,
json: 1,
...task.params,
},
timeout: 15000,
});
if (submitResp.data.status !== 1) {
throw new Error(submitResp.data.error_text || submitResp.data.request);
}
const taskId = submitResp.data.request;
return this._poll(taskId);
}
async _poll(taskId, maxWait = 120000) {
const interval = 5000;
let elapsed = 0;
while (elapsed < maxWait) {
await new Promise((r) => setTimeout(r, interval));
elapsed += interval;
try {
const resp = await axios.get(RESULT_URL, {
params: {
key: this.apiKey,
action: "get",
id: taskId,
json: 1,
},
timeout: 10000,
});
if (resp.data.status === 1) return resp.data.request;
if (resp.data.request !== "CAPCHA_NOT_READY") {
throw new Error(resp.data.error_text || resp.data.request);
}
} catch (err) {
if (err.response) throw err;
}
}
throw new Error(`Timeout waiting for task ${taskId}`);
}
}
// Usage
(async () => {
const pipeline = new CaptchaPipeline("YOUR_API_KEY", 15);
const results = await Promise.allSettled([
pipeline.enqueue("client_a", "userrecaptcha", {
googlekey: "6Le-SITEKEY-A",
pageurl: "https://client-a-staging.example.com/qa-form",
}),
pipeline.enqueue("client_b", "turnstile", {
sitekey: "0x4AAAA-SITEKEY-B",
pageurl: "https://client-b-target.com/login",
}),
]);
results.forEach((r) => {
if (r.status === "fulfilled") {
console.log(`[${r.value.clientId}] Token: ${r.value.token.slice(0, 40)}...`);
} else {
console.error(`Failed: ${r.reason.message}`);
}
});
})();
Configuração por cliente: proxy, limites e método padrão
Guarde as preferências de cada cliente — proxy, tipo de proxy, simultaneidade máxima e método padrão de resolução — em um dicionário central. Isso evita hardcodar valores dentro da lógica de envio e permite adicionar um cliente novo em uma linha:
CLIENT_CONFIG = {
"client_a": {
"proxy": "host:port:user:pass",
"proxytype": "HTTP",
"max_concurrent": 5,
"default_method": "userrecaptcha"
},
"client_b": {
"proxy": None,
"proxytype": None,
"max_concurrent": 10,
"default_method": "turnstile"
}
}
def build_params(client_id, params):
config = CLIENT_CONFIG.get(client_id, {})
if config.get("proxy"):
params["proxy"] = config["proxy"]
params["proxytype"] = config["proxytype"]
return params
Se a maioria dos seus clientes está no Brasil, hospedar os workers em uma região próxima — por exemplo, sa-east-1 da AWS, em São Paulo — reduz o tempo de rede antes mesmo da chamada chegar à API da CaptchaAI, o que ajuda quando o max_concurrent de um cliente já está no limite.
Estratégia de tratamento de erros da API CaptchaAI
Trate cada código de erro de forma diferente — parar a fila inteira por um erro que afeta um cliente só desperdiça o throughput dos demais:
ERROR_ZERO_BALANCE— pare a fila e alerte todos os clientesERROR_NO_SLOT_AVAILABLE— reenfileire a tarefa com atrasoERROR_WRONG_CAPTCHA_ID— descarte a tarefa e registre o erroERROR_CAPTCHA_UNSOLVABLE— tente novamente uma vez e depois marque como falha- Timeout de rede — repita com backoff exponencial (máximo de 3 tentativas)
Solução de problemas comuns no pipeline
| Problema | Causa | Correção |
|---|---|---|
| A fila cresce sem parar | Todos os slots ativos estão ocupados | Aumente max_concurrent ou adicione mais workers |
| O callback não dispara | A tarefa falhou silenciosamente | Verifique o retorno de erro dentro do loop de polling |
| Tokens trocados entre clientes | Armazenamento de resultados compartilhado sem isolamento | Indexe os resultados por client_id + task_id |
| Erros de limite de requisições (429) | Envios simultâneos demais | Reduza a simultaneidade e adicione um atraso entre envios |
Observabilidade, isolamento de dados e LGPD
Um pipeline multicliente concentra tokens e metadados de vários clientes no mesmo Result Store — isso é conveniente para operar, mas exige cuidado se algum cliente estiver sujeito à LGPD (Lei Geral de Proteção de Dados), no Brasil, ou à RGPD, no caso de clientes em Portugal. Três práticas resolvem a maior parte do risco:
- Particione o armazenamento de resultados por
client_iddesde o primeiro dia — não misture logs de clientes diferentes na mesma tabela sem uma coluna de isolamento. - Configure retenção curta para tokens já consumidos: um token resolvido perde o valor depois de usado, então não há motivo para guardá-lo indefinidamente.
- Registre erros com o
task_id, nunca com dados do formulário do cliente final — isso mantém os logs úteis para debug sem expor dados pessoais desnecessários.
Nenhuma dessas práticas depende de um recurso exclusivo da CaptchaAI — são decisões de arquitetura do seu próprio pipeline, e valem tanto para um cliente só quanto para dez.
Perguntas frequentes
Um único plano CaptchaAI atende vários clientes ao mesmo tempo?
Sim. A CaptchaAI cobra por thread simultânea, não por cliente nem por solve — então um plano como ADVANCE (US$ 90/mês, 50 threads) pode atender vários clientes pequenos ao mesmo tempo, desde que a soma dos max_concurrent de cada um não ultrapasse o total de threads contratado.
hCaptcha funciona nos pipelines dos meus clientes?
Não — o CaptchaAI não resolve hCaptcha atualmente. Se algum cliente depender especificamente dele, deixe essa limitação clara antes de fechar o projeto e trate esse fluxo à parte.
Como evito misturar tokens entre clientes no mesmo pipeline?
Indexe cada resultado por client_id e task_id no Result Store, nunca só pelo task_id. É o erro mais comum em pipelines compartilhados — e está listado na tabela de solução de problemas acima.
Preciso de uma chave de API separada por cliente?
Não é obrigatório, mas simplifica o faturamento e o rastreamento de consumo por cliente. Se preferir manter uma única chave para todos, use o parâmetro soft_id da CaptchaAI para separar o consumo internamente.
Monte seu pipeline de CAPTCHA multicliente com a CaptchaAI
Crie sua conta na CaptchaAI, gere sua chave de API e coloque o exemplo em Python ou Node.js deste guia para rodar com o primeiro cliente ainda hoje. Acesse captchaai.com para começar.