Lorsque votre infrastructure de scraping envoie des milliers de requêtes de résolution CAPTCHA, un seul processus de travail crée des goulots d'étranglement. Un équilibreur de charge répartit les requêtes entre plusieurs nœuds de calcul, améliorant ainsi le débit, permettant le basculement et vous permettant d'évoluer horizontalement.
Présentation de l'architecture
[Scraper 1] ──┐ ┌── [Worker 1] ──→ CaptchaAI API
[Scraper 2] ──┤── [Load Balancer] ──┤── [Worker 2] ──→ CaptchaAI API
[Scraper 3] ──┘ └── [Worker 3] ──→ CaptchaAI API
Configuration NGINX
Round-Robin (par défaut)
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;
}
}
Moins de connexions (meilleur pour la résolution 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;
}
Avec des travailleurs de secours
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
}
Serveur API de travail
Python (flacon)
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 (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}`));
Comparaison des stratégies de routage
| Stratégie | Comment ça marche | Idéal pour |
|---|---|---|
| Tournoi à la ronde | Rotation séquentielle | Travailleurs à capacité égale |
| Moins de connexions | Itinéraire vers le moins chargé | Résolution de CAPTCHA (durée de tâche variable) |
| Pondéré | Proportionnel au poids | Travailleurs à capacité mixte |
| Hachage IP | Même client – même travailleur | Affinité de session requise |
| Aléatoire | Sélection aléatoire | Charge simple et uniformément répartie |
Recommandation : Utilisez le moins de connexions pour la résolution de CAPTCHA. Les durées des tâches varient (de 5 à 120 s), donc le round-robin crée une charge inégale.
Équilibrage de charge côté client
Lorsque vous ne pouvez pas utiliser d'équilibreur de charge externe, implémentez le routage dans le client :
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"})
Dépannage
| Problème | Parce que | Corriger |
|---|---|---|
| 502 Mauvaise passerelle | Le travailleur s'est écrasé ou n'a pas démarré | Vérifiez les journaux des travailleurs ; vérifier la liaison du port |
| Répartition inégale de la charge | Round-robin avec des durées de tâches variables | Passer au moins de connexions |
| Bilan de santé faux positif | Vérifiez les passes mais le travailleur est à pleine capacité | Inclure le pourcentage de charge dans la réponse de santé |
| Délai de connexion | proxy_read_timeout trop court |
Réglé sur 300 s+ pour la résolution de CAPTCHA |
FAQ
Ai-je besoin d’un équilibreur de charge pour 2-3 travailleurs ?
L'équilibrage de charge côté client fonctionne bien pour les petites configurations. Utilisez un équilibreur de charge dédié (NGINX, HAProxy) lorsque vous avez plus de 5 travailleurs ou que vous avez besoin de fonctionnalités telles que la terminaison SSL et les contrôles de santé.
Dois-je utiliser des sessions collantes ?
Non. Les requêtes de résolution CAPTCHA sont sans état : n'importe quel travailleur peut gérer n'importe quelle tâche. Des sessions persistantes créeraient une répartition inégale de la charge.
Comment gérer les travailleurs dans différentes régions ?
Utilisez un équilibreur de charge global (AWS Global Accelerator, Cloudflare Load Balancing) qui achemine vers la région saine la plus proche. Chaque région exécute son propre équilibreur de charge local pour les travailleurs de cette région.
Articles connexes
- Modèles de gestion des erreurs de rappel Captchaai
- Nodejs Puppeteer Captchaai Modèles avancés
- Captcha résolvant des modèles d'architecture à volume élevé
Prochaines étapes
Augmentez votre débit de résolution de CAPTCHA –récupérez votre clé API CaptchaAIet déployer derrière un équilibreur de charge.
Guides associés :
- Basculement haute disponibilité
- Architecture multi-régions
- Dimensionnement du pool de connexions