Quando sua infraestrutura de scraping envia milhares de solicitações de resolução CAPTCHA, um único processo de trabalho fica gargalo. Um balanceador de carga distribui solicitações entre vários trabalhadores, aprimorando o rendimento, permitindo failover e permitindo escalar horizontalmente.
Visão geral da arquitetura
[Scraper 1] ──┐ ┌── [Worker 1] ──→ CaptchaAI API
[Scraper 2] ──┤── [Load Balancer] ──┤── [Worker 2] ──→ CaptchaAI API
[Scraper 3] ──┘ └── [Worker 3] ──→ CaptchaAI API
Configuração NGINX
Round-Robin (padrão)
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;
}
}
Menos conexões (mais adequado para resolução de 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;
}
Com trabalhadores 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
}
Servidor API de trabalho
Python (frasco)
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)
JavaScript (Expresso)
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}`));
Estratégias de roteamento comparadas
| Estratégia | Como funciona | mais adequado para |
|---|---|---|
| Round-Robin | Rotação sequencial | Trabalhadores com igual capacidade |
| Menos conexões | Rota para menos carregado | Resolução de CAPTCHA (duração variável da tarefa) |
| Ponderado | Proporcional ao peso | Trabalhadores de capacidade mista |
| Hash de IP | Mesmo cliente → mesmo trabalhador | É 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. As durações das tarefas variam (5s a 120s), portanto o round-robin cria uma carga desigual.
Balanceamento de carga do lado do cliente
Quando não for possível usar um balanceador de carga externo, implemente o roteamento no 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"})
Matriz de seleção de algoritmo
- Use round robin quando os trabalhadores forem homogêneos e os tempos de resolução permanecerem dentro de uma faixa estreita de latência.
- Use menos conexões quando a duração da resolução variar e, caso contrário, desafios de longa duração se acumulariam em um trabalhador.
- Mantenha o roteamento de backup e a afinidade de origem apenas para isolamento de falhas ou destinos sensíveis à sessão.
Solução de problemas
| Problema | Causa | Correção |
|---|---|---|
| 502 Gateway ruim | O trabalhador travou ou não foi iniciado | Verifique os registros dos trabalhadores; verificar a ligação da porta |
| Distribuição desigual de carga | Round-robin com durações de tarefas variáveis | Mudar para menos conexões |
| Verificação de integridade falso positivo | Verifique os passes, mas o trabalhador está lotado | Incluir porcentagem de carga na resposta de saúde |
| Tempo limite de conexão | proxy_read_timeout muito curto |
Defina como 300s+ para resolução de CAPTCHA |
Perguntas frequentes
Preciso de um balanceador de carga para 2 a 3 trabalhadores?
O balanceamento de carga do lado do cliente funciona bem para configurações pequenas. Use um balanceador de carga dedicado (NGINX, HAProxy) quando você tiver mais de 5 trabalhadores ou precisar de recursos como terminação SSL e verificações de integridade.
Devo usar sessões fixas?
Não. As solicitações de resolução CAPTCHA não têm estado: qualquer trabalhador pode realizar qualquer tarefa. Sessões fixas criariam uma distribuição desigual de carga.
Como lidar com trabalhadores em diferentes regiões?
Use um balanceador de carga global (AWS Global Accelerator, Cloudflare Load Balancing) que roteie para a região íntegra mais próxima. Cada região executa seu próprio balanceador de carga local para os trabalhadores dessa região.
Artigos relacionados
- Padrões de tratamento de erros de retorno de chamada Captchaai
- Padrões avançados Nodejs Puppeteer Captchaai
- Captcha Resolvendo Padrões de Arquitetura de Alto Volume
Próximas etapas
Dimensione seu rendimento de resolução de CAPTCHA -obtenha sua chave API CaptchaAIe implantar atrás de um balanceador de carga.
Guias relacionados:
- Failover de alta disponibilidade
- Arquitetura multirregional
- Dimensionamento do pool de conexões