Tutoriels API

File d'attente de lettres mortes pour les tâches CAPTCHA ayant échoué

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éfi
  • ERROR_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

Les commentaires sont désactivés pour cet article.