Répartir votre trafic sur plusieurs clés API, avec basculement automatique, est le moyen le plus simple de garder votre résolution CAPTCHA en ligne en continu. Avec une seule clé, il suffit d'un solde épuisé, d'une limite de débit ou d'une désactivation pour arrêter tout votre pipeline. La rotation multi-clés supprime ce point de défaillance : elle étale la charge et bascule vers une autre clé sans intervention. Ce guide détaille trois stratégies en Python et Node.js, de la plus simple à la plus robuste :
- Round-robin — chaque clé reçoit une part égale du trafic.
- Pondération par le solde — les clés les mieux approvisionnées encaissent davantage de requêtes.
- Basculement — une requête qui échoue repart aussitôt sur la clé suivante.
Quand passer à plusieurs clés
Une seule clé suffit tant que le volume reste faible et régulier. Trois signaux indiquent qu'il faut répartir le trafic :
- Vous atteignez la limite de débit de votre compte aux heures de pointe.
- Un solde tombé à zéro interrompt vos résolutions sans préavis.
- Une clé désactivée — IP non autorisée ou clé révoquée — fige un worker entier.
Avec deux ou trois clés, le rotateur écarte la clé fautive et poursuit sur les autres : l'incident devient transparent pour votre pipeline, sans réveil nocturne ni file de tâches bloquée.
Quelle stratégie choisir
| Stratégie | Idéale quand | Effort de mise en place |
|---|---|---|
| Round-robin | Vos comptes ont des soldes comparables | Faible |
| Pondérée par le solde | Les soldes divergent fortement | Moyen |
| Basculement | La disponibilité prime sur le reste | Moyen |
Rien n'empêche de combiner les trois : le code de basculement plus bas s'appuie sur le rotateur pondéré pour choisir chaque clé.
Rotation round-robin
La stratégie la plus simple fait tourner les clés à tour de rôle : chaque clé reçoit une part égale du trafic. Cela suffit tant que vos comptes ont des soldes comparables.
Python
import itertools
import requests
API_KEYS = [
"KEY_ACCOUNT_1",
"KEY_ACCOUNT_2",
"KEY_ACCOUNT_3",
]
key_cycle = itertools.cycle(API_KEYS)
def get_next_key():
return next(key_cycle)
def solve_captcha(sitekey, page_url):
api_key = get_next_key()
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
})
data = resp.json()
if data["status"] != 1:
raise Exception(f"[{api_key[:8]}...] {data['request']}")
print(f"Submitted with key {api_key[:8]}...")
return data["request"], api_key
task_id, used_key = solve_captcha("6Le-SITEKEY", "https://example.com")
itertools.cycle avance d'une clé à chaque appel, sans état à gérer. La limite : une clé vide reste dans la rotation et fait échouer une requête sur trois. Les stratégies suivantes corrigent ce point.
Rotation pondérée par le solde
Quand vos comptes n'ont pas le même solde, dirigez plus de requêtes vers les clés les mieux garnies. Le rotateur interroge le solde via getbalance, écarte les clés vides ou en erreur, puis tire une clé au hasard pondéré par son solde.
Exemple : une agence à Lyon répartit ses tests de connexion sur trois comptes ; la pondération envoie l'essentiel du trafic vers le compte le mieux approvisionné, et le basculement prend le relais si l'un atteint sa limite.
Préférez cette approche quand :
- vos comptes ont des soldes très différents ;
- vous voulez vider en priorité un compte proche de son expiration ;
- un pic de trafic ne doit pas épuiser vos plus petits soldes.
Python
import random
import requests
import threading
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
class KeyRotator:
def __init__(self, keys):
self.keys = {k: {"balance": 0, "failures": 0, "disabled": False} for k in keys}
self._lock = threading.Lock()
self.refresh_balances()
def refresh_balances(self):
for key in self.keys:
try:
resp = requests.get(RESULT_URL, params={
"key": key, "action": "getbalance", "json": "1"
}, timeout=10).json()
if resp["status"] == 1:
self.keys[key]["balance"] = float(resp["request"])
self.keys[key]["disabled"] = False
else:
self.keys[key]["disabled"] = True
except Exception:
self.keys[key]["disabled"] = True
def get_key(self):
with self._lock:
available = {
k: v for k, v in self.keys.items()
if not v["disabled"] and v["balance"] > 0.01
}
if not available:
raise Exception("No API keys with balance available")
# Weighted random by balance
keys = list(available.keys())
weights = [available[k]["balance"] for k in keys]
return random.choices(keys, weights=weights, k=1)[0]
def report_failure(self, key, error_code):
with self._lock:
self.keys[key]["failures"] += 1
if error_code in ("ERROR_WRONG_USER_KEY", "ERROR_KEY_DOES_NOT_EXIST",
"ERROR_ZERO_BALANCE", "ERROR_IP_NOT_ALLOWED"):
self.keys[key]["disabled"] = True
print(f"[rotator] Disabled key {key[:8]}...: {error_code}")
def report_success(self, key, cost=0.003):
with self._lock:
self.keys[key]["balance"] -= cost
self.keys[key]["failures"] = 0
rotator = KeyRotator(["KEY_1", "KEY_2", "KEY_3"])
# Usage
api_key = rotator.get_key()
# ... solve captcha ...
rotator.report_success(api_key)
Le Lock protège l'état partagé : sans lui, deux threads pourraient lire puis écrire le même solde et le corrompre. Appelez refresh_balances() régulièrement pour recaler les soldes sur la réalité de l'API.
Rotation avec basculement
En production, une clé peut échouer en plein appel. Ce schéma réessaie avec la clé suivante et ne désactive une clé que sur les erreurs permanentes, jamais sur un incident réseau.
Python
def solve_with_failover(sitekey, page_url, max_attempts=3):
for attempt in range(max_attempts):
api_key = rotator.get_key()
try:
resp = requests.post(SUBMIT_URL, data={
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
}, timeout=15)
data = resp.json()
if data["status"] != 1:
rotator.report_failure(api_key, data["request"])
continue
rotator.report_success(api_key)
return data["request"], api_key
except requests.RequestException:
rotator.report_failure(api_key, "NETWORK_ERROR")
continue
raise Exception(f"All {max_attempts} keys failed")
JavaScript
const axios = require('axios');
class KeyRotator {
constructor(keys) {
this.keys = keys.map(k => ({ key: k, disabled: false, failures: 0 }));
this.index = 0;
}
getKey() {
const available = this.keys.filter(k => !k.disabled);
if (available.length === 0) throw new Error('No API keys available');
const entry = available[this.index % available.length];
this.index++;
return entry.key;
}
disable(key, reason) {
const entry = this.keys.find(k => k.key === key);
if (entry) {
entry.disabled = true;
console.log(`[rotator] Disabled ${key.substring(0, 8)}...: ${reason}`);
}
}
}
const rotator = new KeyRotator(['KEY_1', 'KEY_2', 'KEY_3']);
async function solveWithFailover(sitekey, pageurl, maxAttempts = 3) {
for (let i = 0; i < maxAttempts; i++) {
const apiKey = rotator.getKey();
try {
const resp = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: { key: apiKey, method: 'userrecaptcha', googlekey: sitekey, pageurl, json: 1 }
});
if (resp.data.status !== 1) {
rotator.disable(apiKey, resp.data.request);
continue;
}
return { taskId: resp.data.request, apiKey };
} catch (err) {
rotator.disable(apiKey, 'NETWORK_ERROR');
}
}
throw new Error('All keys failed');
}
Le point clé : report_failure ne désactive la clé que sur une erreur permanente. Un NETWORK_ERROR compte comme une tentative ratée, mais la clé reste dans la rotation pour l'appel suivant.
Charger les clés depuis les variables d'environnement
Ne codez jamais les clés en dur : chargez-les depuis l'environnement ou un gestionnaire de secrets.
Python
import os
API_KEYS = os.environ["CAPTCHAAI_KEYS"].split(",")
# Set: CAPTCHAAI_KEYS=key1,key2,key3
rotator = KeyRotator(API_KEYS)
JavaScript
const API_KEYS = process.env.CAPTCHAAI_KEYS.split(',');
const rotator = new KeyRotator(API_KEYS);
Actualisation périodique du solde
Pour un worker de longue durée, rafraîchissez les soldes à intervalle régulier : une clé vidée est ainsi écartée avant de renvoyer une erreur.
import threading
def periodic_refresh(rotator, interval=300):
def refresh():
while True:
rotator.refresh_balances()
for key, info in rotator.keys.items():
print(f" {key[:8]}...: ${info['balance']:.2f} "
f"{'(disabled)' if info['disabled'] else '(active)'}")
threading.Event().wait(interval)
t = threading.Thread(target=refresh, daemon=True)
t.start()
periodic_refresh(rotator, interval=300) # every 5 minutes
Bonnes pratiques pour un parc de clés
Quelques règles évitent la plupart des incidents en production :
- Stockez les clés dans un gestionnaire de secrets (HashiCorp Vault, AWS Secrets Manager) ou des variables d'environnement — jamais en clair dans le dépôt Git.
- Tronquez les clés dans vos logs (
key[:8]) et faites tourner sans tarder toute clé exposée par mégarde. - Suivez le solde de chaque compte séparément et rechargez avant l'épuisement, sans attendre
ERROR_ZERO_BALANCE. - Ne désactivez une clé que sur une erreur permanente, jamais sur un incident réseau ponctuel.
Dépannage
| Problème | Cause | Correctif |
|---|---|---|
| Toutes les clés désactivées | Solde nul sur l'ensemble des comptes | Rechargez les comptes ; surveillez ERROR_ZERO_BALANCE |
| Toujours la même clé utilisée | L'indice round-robin n'avance pas | Vérifiez la sécurité vis-à-vis des threads (le verrou Lock) |
| Clé désactivée à tort | Une erreur temporaire traitée comme permanente | Ne désactivez que sur ERROR_WRONG_USER_KEY, ERROR_ZERO_BALANCE, ERROR_IP_NOT_ALLOWED |
FAQ
Combien de clés faut-il prévoir ?
Deux clés suffisent pour un basculement de base. À partir de trois, vous répartissez la charge ; pour un fort volume (plus de 1 000 résolutions/jour), tablez sur 3 à 5 clés.
Le rotateur est-il sûr en environnement multi-thread ?
Oui, à condition de protéger l'état partagé avec un verrou, comme le Lock de l'exemple pondéré. Sans lui, deux threads peuvent corrompre le même solde.
Comment stocker plusieurs clés sans les exposer ?
Passez par des variables d'environnement ou un gestionnaire de secrets, jamais par du code en dur. Côté RGPD, limitez aussi les journaux susceptibles de contenir une clé.
Que se passe-t-il si toutes les clés sont épuisées en même temps ?
Le rotateur lève une exception explicite plutôt que de boucler à l'infini. Prévoyez une alerte sur ce cas et un rechargement automatique du solde avant d'atteindre zéro, pour ne jamais servir une file de résolutions à vide.
Passez votre résolution CAPTCHA à l'échelle avec la rotation multi-clés
Créez votre compte et répartissez votre trafic dès la première intégration — ouvrez un compte sur captchaai.com.
Guides associés
- Liste blanche IP et sécurité des clés API CaptchaAI
- Sécuriser vos identifiants CaptchaAI dans les variables d'environnement
- Vérifier le solde CaptchaAI et le recharger automatiquement