Um worker sozinho resolvendo CAPTCHA satura rápido: a fila cresce, o tempo de resposta sobe e o scraper passa a estourar timeout bem antes de chegar a mil requisições por hora. A correção não é aumentar o timeout — é colocar vários workers atrás de um balanceador de carga, com health check e failover automático, escolhendo a estratégia de roteamento certa para uma tarefa de duração variável como resolver CAPTCHA. Este guia mostra como montar essa arquitetura com NGINX, um servidor de workers em Python (Flask) e Node.js (Express), e uma alternativa de balanceamento no lado do cliente para pools pequenos.
Visão geral da arquitetura de balanceamento de carga
Cada scraper fala sempre com o mesmo endpoint do balanceador, que decide qual worker está livre. Adicionar capacidade é só subir mais um worker:
[Scraper 1] ──┐ ┌── [Worker 1] ──→ CaptchaAI API
[Scraper 2] ──┤── [Load Balancer] ──┤── [Worker 2] ──→ CaptchaAI API
[Scraper 3] ──┘ └── [Worker 3] ──→ CaptchaAI API
O worker recebe a tarefa, chama a API da CaptchaAI e devolve o resultado; o balanceador nunca fala com a API diretamente, só decide o roteamento.
Como configurar o NGINX como balanceador
O NGINX cobre a maioria dos casos sem serviço gerenciado. Três variações resolvem cenários diferentes.
Round-robin: a estratégia padrão do NGINX
upstream captcha_workers {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080;
}
server {
listen 80;
server_name captcha.internal;
location /solve {
proxy_pass http://captcha_workers;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 10s;
proxy_read_timeout 300s; # CAPTCHA solving can take minutes
}
location /health {
proxy_pass http://captcha_workers;
proxy_connect_timeout 5s;
proxy_read_timeout 5s;
}
}
O round-robin distribui em sequência, sem olhar a carga de cada worker — só funciona bem quando as tarefas duram o mesmo tempo, raro na resolução de CAPTCHA.
Menos conexões: a estratégia recomendada para CAPTCHA
upstream captcha_workers {
least_conn; # Route to worker with fewest active connections
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080 weight=2; # Higher capacity worker
# Health checks
server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.12:8080 max_fails=3 fail_timeout=30s;
}
Como o tempo de resolução varia de 5 a 120 segundos, esta configuração também define weight para o worker de maior capacidade e usa max_fails/fail_timeout para tirar da rotação um worker instável.
Com workers de backup
upstream captcha_workers {
least_conn;
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080 backup; # Only used when others are down
}
Reserve workers backup para picos ou manutenção — só entram quando os principais falham.
Implementando o servidor de API dos workers
Cada worker expõe dois endpoints: /solve, que recebe a tarefa e chama a CaptchaAI, e /health, que o balanceador consulta para decidir se o worker pode receber mais tráfego. Os dois exemplos abaixo — Python e Node.js — seguem o mesmo contrato, então é possível misturar linguagens no mesmo pool sem mudar a configuração do NGINX.
Python com Flask
import os
import time
import threading
import requests
from flask import Flask, request, jsonify
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
app = Flask(__name__)
# Track active tasks for load reporting
active_tasks = 0
tasks_lock = threading.Lock()
max_concurrent = int(os.environ.get("MAX_CONCURRENT", "20"))
@app.route("/solve", methods=["POST"])
def solve():
global active_tasks
with tasks_lock:
if active_tasks >= max_concurrent:
return jsonify({"error": "WORKER_AT_CAPACITY"}), 503
active_tasks += 1
try:
data = request.json
result = solve_captcha(data)
return jsonify(result)
finally:
with tasks_lock:
active_tasks -= 1
@app.route("/health")
def health():
with tasks_lock:
load = active_tasks / max_concurrent
return jsonify({
"status": "healthy" if load < 0.9 else "overloaded",
"active_tasks": active_tasks,
"max_concurrent": max_concurrent,
"load_pct": round(load * 100, 1)
}), 200 if load < 0.9 else 503
def solve_captcha(data):
session = requests.Session()
payload = {
"key": API_KEY,
"method": data.get("method", "userrecaptcha"),
"googlekey": data.get("sitekey"),
"pageurl": data.get("pageurl"),
"json": 1
}
if data.get("proxy"):
payload["proxy"] = data["proxy"]
payload["proxytype"] = data.get("proxytype", "HTTP")
resp = session.post("https://ocr.captchaai.com/in.php", data=payload)
result = resp.json()
if result.get("status") != 1:
return {"error": result.get("request")}
captcha_id = result["request"]
for _ in range(60):
time.sleep(5)
poll = session.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": captcha_id, "json": 1
}).json()
if poll.get("status") == 1:
return {"solution": poll["request"], "captcha_id": captcha_id}
if poll.get("request") != "CAPCHA_NOT_READY":
return {"error": poll.get("request")}
return {"error": "TIMEOUT"}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, threaded=True)
O MAX_CONCURRENT limita quantas tarefas o worker aceita ao mesmo tempo; acima disso, ele responde 503 e o least_conn do NGINX move a tarefa para outro worker. O /health reporta o percentual de carga — o dado que evita o falso positivo descrito no diagnóstico, mais abaixo.
JavaScript com Express
const express = require("express");
const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const MAX_CONCURRENT = parseInt(process.env.MAX_CONCURRENT || "20", 10);
const PORT = parseInt(process.env.PORT || "8080", 10);
let activeTasks = 0;
const app = express();
app.use(express.json());
app.post("/solve", async (req, res) => {
if (activeTasks >= MAX_CONCURRENT) {
return res.status(503).json({ error: "WORKER_AT_CAPACITY" });
}
activeTasks++;
try {
const result = await solveCaptcha(req.body);
res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
} finally {
activeTasks--;
}
});
app.get("/health", (req, res) => {
const load = activeTasks / MAX_CONCURRENT;
const status = load < 0.9 ? "healthy" : "overloaded";
res
.status(load < 0.9 ? 200 : 503)
.json({ status, activeTasks, maxConcurrent: MAX_CONCURRENT, loadPct: Math.round(load * 100) });
});
async function solveCaptcha(data) {
const submitResp = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: API_KEY,
method: data.method || "userrecaptcha",
googlekey: data.sitekey,
pageurl: data.pageurl,
json: 1,
},
});
if (submitResp.data.status !== 1) {
return { error: submitResp.data.request };
}
const captchaId = submitResp.data.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const pollResp = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
});
if (pollResp.data.status === 1) {
return { solution: pollResp.data.request, captchaId };
}
if (pollResp.data.request !== "CAPCHA_NOT_READY") {
return { error: pollResp.data.request };
}
}
return { error: "TIMEOUT" };
}
app.listen(PORT, () => console.log(`Worker listening on port ${PORT}`));
Comparando as estratégias de roteamento
A escolha depende de como a carga varia entre os workers:
| Estratégia | Como funciona | Mais adequado para |
|---|---|---|
| Round-robin | Rotação sequencial | Workers com capacidade equivalente |
| Menos conexões | Rota para o worker menos carregado | Resolução de CAPTCHA (duração variável da tarefa) |
| Ponderado | Proporcional ao peso definido | Workers de capacidade mista |
| Hash de IP | Mesmo cliente sempre no mesmo worker | Quando é necessária afinidade de sessão |
| Aleatório | Seleção aleatória | Carga simples e distribuída uniformemente |
Recomendação: use menos conexões para resolver CAPTCHA. Como a duração de cada tarefa varia entre 5 s e 120 s, o round-robin acaba empilhando tarefas longas no mesmo worker enquanto outros ficam ociosos.
Balanceamento de carga no lado do cliente (sem infraestrutura extra)
Quando não vale a pena subir um balanceador dedicado — pools pequenos, ou testes locais — o roteamento pode viver no próprio cliente:
import random
import requests
class ClientLoadBalancer:
def __init__(self, workers):
self.workers = [
{"url": url, "healthy": True, "active": 0}
for url in workers
]
def get_worker(self):
healthy = [w for w in self.workers if w["healthy"]]
if not healthy:
raise Exception("No healthy workers")
return min(healthy, key=lambda w: w["active"])
def solve(self, task):
worker = self.get_worker()
worker["active"] += 1
try:
resp = requests.post(
f"{worker['url']}/solve",
json=task,
timeout=300
)
if resp.status_code == 503:
worker["healthy"] = False
return self.solve(task) # Retry on another worker
return resp.json()
except requests.RequestException:
worker["healthy"] = False
return self.solve(task)
finally:
worker["active"] -= 1
lb = ClientLoadBalancer([
"http://10.0.1.10:8080",
"http://10.0.1.11:8080",
"http://10.0.1.12:8080"
])
result = lb.solve({"sitekey": "6Le-wvkS...", "pageurl": "https://example.com"})
O ClientLoadBalancer segue a mesma lógica do least_conn: escolhe o worker com menos tarefas ativas e remove da rotação quem devolver erro ou 503, tentando de novo em outro worker. Para 2 ou 3 workers, isso já substitui um balanceador dedicado.
Qual algoritmo escolher em cada cenário
- Use round-robin com workers homogêneos e tempos de resolução em uma faixa estreita de latência.
- Use menos conexões quando a duração da resolução variar — caso contrário, os desafios mais longos se acumulam em um único worker.
- Reserve backup e afinidade de origem para isolamento de falhas ou sessões sensíveis; fora disso, só adicionam complexidade.
Diagnosticando problemas comuns
Os quatro problemas abaixo cobrem a maior parte dos tickets de balanceamento para CAPTCHA:
| Problema | Causa | Correção |
|---|---|---|
| 502 Bad Gateway | O worker travou ou nunca chegou a iniciar | Verifique os logs do worker e confirme a vinculação da porta |
| Distribuição desigual de carga | Round-robin com tarefas de duração variável | Troque para menos conexões |
| Falso positivo no health check | A checagem passa, mas o worker está no limite de capacidade | Inclua o percentual de carga na resposta do /health |
| Timeout de conexão | proxy_read_timeout configurado curto demais |
Defina 300 s ou mais para resolução de CAPTCHA |
Perguntas frequentes
Quantas threads do plano CaptchaAI cobrem um pool de workers?
CaptchaAI cobra por thread simultânea, não por worker individual. Um pool de 5 workers já passa fácil de 15 a 20 threads em uso ao mesmo tempo — o BASIC (US$ 15/mês, 5 threads) fica pequeno rápido. Para pools maiores, o ADVANCE (US$ 90/mês, 50 threads) ou o PREMIUM (US$ 170/mês, 100 threads) costuma refletir melhor a concorrência real do cluster.
O health check pode passar mesmo com o worker no limite?
Sim — é a causa mais comum de distribuição desigual quando a checagem só confirma que o processo responde, sem olhar a carga real. Inclua active_tasks / max_concurrent na resposta de /health e trate cargas acima de 90% como degradadas, não apenas como erro HTTP.
Preciso de sessões fixas (sticky sessions) para resolver CAPTCHA?
Não. A resolução de CAPTCHA não tem estado — qualquer worker processa qualquer tarefa, já que o resultado depende só do sitekey e da pageurl enviados. Sessões fixas aqui só criam carga desigual: alguns workers acumulam tarefas demoradas enquanto outros ficam ociosos.
A partir de quantos workers vale migrar para um balanceador dedicado?
Com 2 a 3 workers, o balanceamento no lado do cliente já resolve bem. A partir de 5 workers — ou quando você precisa de terminação SSL e health checks centralizados — compensa migrar para um balanceador dedicado como NGINX ou HAProxy.
Faz diferença rodar os workers perto da região da API?
Sim. A latência de rede pesa no tempo total, principalmente em CAPTCHAs com várias consultas de polling. Para times na América Latina, hospedar os workers em sa-east-1 (São Paulo) da AWS reduz o RTT de cada chamada a in.php/res.php frente a rodar tudo na Europa ou na Ásia. Em arquitetura multirregional, mantenha um balanceador local por região, roteando o tráfego global para a mais próxima.
Artigos relacionados
- Como tratar erros de callback na CaptchaAI
- Automação avançada com Node.js, Puppeteer e CaptchaAI
- Arquitetura para resolução de CAPTCHA em alto volume
Próximas etapas
Escale sua taxa de resolução de CAPTCHA: obtenha sua chave de API da CaptchaAI e coloque o pool de workers atrás do balanceador ainda hoje.
Guias relacionados: