Lorsqu'une résolution CAPTCHA échoue après toutes les tentatives, les données de la tâche sont perdues, sauf si vous les capturez. Une file d'attente de lettres mortes (DLQ) stocke les tâches ayant échoué pour une nouvelle tentative, une analyse ou une alerte ultérieure – de sorte qu'aucun travail n'est abandonné en silence.
Quand les tâches échouent
Raisons courantes pour lesquelles une tâche CAPTCHA se retrouve dans le DLQ :
ERROR_CAPTCHA_UNSOLVABLE– Le solveur n'a pas pu terminer le défiERROR_NO_SLOT_AVAILABLE– Tous les travailleurs sont occupés, les tentatives sont épuisées- Timeout : le solveur n'a pas renvoyé de résultat dans le délai imparti.
- Erreurs réseau : connexion interrompue lors de l'interrogation
Sans DLQ, ces échecs produisent une ligne de journal et sont oubliés.
Python : DLQ en mémoire avec nouvelle tentative
import time
import json
import requests
from collections import deque
from dataclasses import dataclass, asdict
from typing import Optional
API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
@dataclass
class FailedTask:
sitekey: str
page_url: str
error: str
attempts: int
timestamp: float
task_id: Optional[str] = None
class DeadLetterQueue:
def __init__(self, max_size=1000, max_retries=3):
self._queue = deque(maxlen=max_size)
self.max_retries = max_retries
def push(self, task: FailedTask):
self._queue.append(task)
print(f"[dlq] Added: {task.error} (attempts: {task.attempts})")
def pop(self) -> Optional[FailedTask]:
return self._queue.popleft() if self._queue else None
def size(self) -> int:
return len(self._queue)
def peek_all(self) -> list:
return [asdict(t) for t in self._queue]
def export_json(self, path: str):
with open(path, "w") as f:
json.dump(self.peek_all(), f, indent=2)
print(f"[dlq] Exported {self.size()} tasks to {path}")
dlq = DeadLetterQueue(max_retries=3)
def solve_captcha(sitekey, page_url, max_retries=3):
for attempt in range(max_retries + 1):
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:
raise Exception(data["request"])
task_id = data["request"]
for _ in range(24):
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get",
"id": task_id, "json": "1",
}, timeout=15).json()
if poll["status"] == 1:
return poll["request"]
if poll["request"] != "CAPCHA_NOT_READY":
raise Exception(poll["request"])
raise TimeoutError(f"Task {task_id} timed out")
except Exception as e:
if attempt == max_retries:
dlq.push(FailedTask(
sitekey=sitekey,
page_url=page_url,
error=str(e),
attempts=attempt + 1,
timestamp=time.time(),
))
return None
time.sleep(2 ** attempt)
return None
# Process a batch
urls = [f"https://example.com/page/{i}" for i in range(5)]
for url in urls:
token = solve_captcha("6Le-SITEKEY", url)
if token:
print(f"Solved: {token[:40]}...")
print(f"\nDLQ size: {dlq.size()}")
Résultat attendu :
Solved: 03AGdBq26ZfPxL...
Solved: 03AGdBq27AbCdE...
[dlq] Added: ERROR_CAPTCHA_UNSOLVABLE (attempts: 4)
Solved: 03AGdBq28FgHiJ...
[dlq] Added: Task 71823460 timed out (attempts: 4)
DLQ size: 2
Réessayer depuis le DLQ
def retry_dlq(dlq: DeadLetterQueue, max_retries=2):
retried = 0
recovered = 0
while dlq.size() > 0:
task = dlq.pop()
if task.attempts >= dlq.max_retries + max_retries:
print(f"[dlq] Permanently failed: {task.sitekey} — {task.error}")
continue
retried += 1
token = solve_captcha(
task.sitekey, task.page_url, max_retries=max_retries
)
if token:
recovered += 1
print(f"[dlq-retry] Recovered: {token[:40]}...")
print(f"[dlq] Retried: {retried}, Recovered: {recovered}")
# Run DLQ retry after main batch
retry_dlq(dlq)
JavaScript : DLQ avec persistance des fichiers
const fs = require('fs');
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
const DLQ_FILE = './captcha-dlq.json';
class DeadLetterQueue {
constructor(maxRetries = 3) {
this.maxRetries = maxRetries;
this.queue = this._load();
}
push(task) {
this.queue.push({
...task,
timestamp: Date.now(),
});
this._save();
console.log(`[dlq] Added: ${task.error} (attempts: ${task.attempts})`);
}
pop() {
const task = this.queue.shift();
if (task) this._save();
return task || null;
}
size() {
return this.queue.length;
}
_load() {
try {
return JSON.parse(fs.readFileSync(DLQ_FILE, 'utf8'));
} catch {
return [];
}
}
_save() {
fs.writeFileSync(DLQ_FILE, JSON.stringify(this.queue, null, 2));
}
}
const dlq = new DeadLetterQueue(3);
async function solveCaptcha(sitekey, pageurl, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: { key: API_KEY, method: 'userrecaptcha', googlekey: sitekey, pageurl, json: 1 }
});
if (submit.data.status !== 1) throw new Error(submit.data.request);
const taskId = submit.data.request;
for (let i = 0; i < 24; i++) {
await new Promise(r => setTimeout(r, 5000));
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 }
});
if (poll.data.status === 1) return poll.data.request;
if (poll.data.request !== 'CAPCHA_NOT_READY') throw new Error(poll.data.request);
}
throw new Error(`Task ${taskId} timed out`);
} catch (err) {
if (attempt === maxRetries) {
dlq.push({ sitekey, pageurl, error: err.message, attempts: attempt + 1 });
return null;
}
await new Promise(r => setTimeout(r, 2 ** attempt * 1000));
}
}
}
// Process tasks
(async () => {
for (let i = 0; i < 5; i++) {
const token = await solveCaptcha('6Le-SITEKEY', `https://example.com/page/${i}`);
if (token) console.log(`Solved: ${token.substring(0, 40)}...`);
}
console.log(`DLQ size: ${dlq.size()}`);
})();
Analyse DLQ
Exportez et analysez les tâches ayant échoué pour trouver des modèles :
# Export DLQ for analysis
dlq.export_json("failed-tasks.json")
# Analyze error distribution
from collections import Counter
errors = Counter(t["error"] for t in dlq.peek_all())
for error, count in errors.most_common():
print(f" {error}: {count}")
Utilisez ces données pour :
- Identifiez les clés de site qui échouent systématiquement – vérifiez si les paramètres sont corrects
- Délais d'attente ponctuels pendant des heures spécifiques – en corrélation avec la charge de l'API
- Rechercher les erreurs réseau – vérifier l'état du proxy
Dépannage
| Problème | Parce que | Corriger |
|---|---|---|
| DLQ grandit indéfiniment | Ne traite pas les tentatives | Programmer une vidange DLQ périodique avec retry_dlq() |
| Même tâche réessayée pour toujours | Pas de plafond de tentatives maximales | Vérifiez task.attempts avant de remettre en file d'attente |
| Fichier DLQ corrompu | Écritures simultanées | Utilisez le verrouillage de fichiers ou passez à Redis/database |
| Tâches perdues en cas de crash | DLQ en mémoire uniquement | Utiliser un DLQ basé sur des fichiers ou basé sur Redis |
FAQ
Dois-je utiliser un DLQ en mémoire ou persistant ?
Utilisez en mémoire pour les scripts de courte durée. Utilisez des services basés sur des fichiers ou basés sur Redis pour les services de longue durée où un redémarrage de processus entraînerait la perte des tâches en file d'attente.
Quand dois-je abandonner définitivement une tâche ?
Après 2-3 tentatives DLQ (en plus des tentatives originales). Si une tâche échoue plus de 6 fois au total, les paramètres sont probablement erronés : enregistrez-la et continuez.
Puis-je combiner cela avec le modèle du disjoncteur ?
Oui. Le disjoncteur empêche l'envoi de requêtes lors d'une panne et le DLQ capture toutes les tâches qui échouent avant le déclenchement du circuit. VoirModèle de disjoncteur.
Ne perdez plus jamais une tâche CAPTCHA ayant échoué avec CaptchaAI
Obtenez votre clé API surcaptchaai.com.
Guides associés
- Modèle de disjoncteur pour les appels d'API CAPTCHA
- Implémentation d'une logique de nouvelle tentative pour l'API CaptchaAI
- File d'attente Redis + CaptchaAI : traitement distribué