Tutoriais de API

Fila de mensagens mortas para tarefas CAPTCHA com falha

Quando uma solução CAPTCHA falha após todas as novas tentativas, os dados da tarefa são perdidos, a menos que você os capture. Uma fila de mensagens mortas (DLQ) armazena tarefas com falha para nova tentativa, análise ou alerta posterior, para que nenhum trabalho seja descartado silenciosamente.


Quando as tarefas falham

Razões comuns pelas quais uma tarefa CAPTCHA termina no DLQ:

  • ERROR_CAPTCHA_UNSOLVABLE — O solucionador não conseguiu completar o desafio
  • ERROR_NO_SLOT_AVAILABLE — Todos os trabalhadores ocupados, novas tentativas esgotadas
  • Tempo limite — o Solver não retornou um resultado dentro do prazo
  • Erros de rede — A conexão caiu durante a pesquisa

Sem um DLQ, essas falhas produzem uma linha de log e são esquecidas.


Python: DLQ na memória com nova tentativa

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()}")

Resultado esperado:

Solved: 03AGdBq26ZfPxL...
Solved: 03AGdBq27AbCdE...
[dlq] Added: ERROR_CAPTCHA_UNSOLVABLE (attempts: 4)
Solved: 03AGdBq28FgHiJ...
[dlq] Added: Task 71823460 timed out (attempts: 4)

DLQ size: 2

Tentando novamente do 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 com persistência de arquivo

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()}`);
})();

Análise DLQ

Exporte e analise tarefas com falha para encontrar padrões:

# 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}")

Use esses dados para:

  • Identifique as chaves do site que falham consistentemente → verifique se os parâmetros estão corretos
  • Tempos limite pontuais durante horários específicos → correlacionados com a carga da API
  • Encontre erros de rede → verifique a integridade do proxy

Solução de problemas

Problema Causa Correção
DLQ cresce indefinidamente Não processando novas tentativas Agende drenagem DLQ periódica com retry_dlq()
A mesma tarefa repetida para sempre Sem limite máximo de tentativas Verifique task.attempts antes de colocar novamente na fila
Arquivo DLQ corrompido Gravações simultâneas Use o bloqueio de arquivo ou mude para Redis/database
Tarefas perdidas em caso de falha Somente DLQ na memória Use DLQ baseado em arquivo ou com suporte de Redis

Perguntas frequentes

Devo usar um DLQ na memória ou persistente?

Use na memória para scripts de curta duração. Use baseado em arquivo ou com suporte de Redis para serviços de longa execução em que a reinicialização de um processo perderia tarefas na fila.

Quando devo descartar permanentemente uma tarefa?

Após 2-3 tentativas de DLQ (além das tentativas originais). Se uma tarefa falhar mais de 6 vezes no total, os parâmetros provavelmente estão errados – registre-a e siga em frente.

Posso combinar isso com o padrão do disjuntor?

Sim. O disjuntor impede o envio de solicitações durante uma interrupção e o DLQ captura quaisquer tarefas que falhem antes do circuito desarmar. VerPadrão de disjuntor.


Nunca mais perca uma tarefa CAPTCHA com falha com CaptchaAI

Obtenha sua chave API emcaptchaai.com.


Guias relacionados

Os comentários estão desativados para este artigo.