Если решение CAPTCHA завершается неудачей после всех повторных попыток, данные задачи теряются, если вы их не зафиксируете. Очередь недоставленных сообщений (DLQ) сохраняет невыполненные задачи для последующего повторения, анализа или оповещения, поэтому никакая работа не прерывается автоматически.
Когда задачи не выполняются
Распространенные причины, по которым задача CAPTCHA попадает в DLQ:
ERROR_CAPTCHA_UNSOLVABLE— Решатель не смог выполнить задачу.ERROR_NO_SLOT_AVAILABLE— Все рабочие заняты, повторные попытки исчерпаны.- Тайм-аут — решатель не вернул результат в установленный срок.
- Сетевые ошибки — соединение разорвано во время опроса.
Без DLQ эти сбои создают строку журнала и забываются.
Python: DLQ в памяти с повтором
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()}")
Ожидаемый результат:
Solved: 03AGdBq26ZfPxL...
Solved: 03AGdBq27AbCdE...
[dlq] Added: ERROR_CAPTCHA_UNSOLVABLE (attempts: 4)
Solved: 03AGdBq28FgHiJ...
[dlq] Added: Task 71823460 timed out (attempts: 4)
DLQ size: 2
Повторная попытка из 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 с сохранением файлов
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()}`);
})();
DLQ-анализ
Экспортируйте и анализируйте неудачные задачи, чтобы найти закономерности:
# 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}")
Используйте эти данные, чтобы:
- Определите ключи сайта, которые постоянно выходят из строя — проверьте правильность параметров.
- Тайм-ауты в определенные часы коррелируют с загрузкой API.
- Найдите сетевые ошибки — проверьте работоспособность прокси-сервера.
Поиск неисправностей
| Проблема | Причина | Исправить |
|---|---|---|
| DLQ растет бесконечно | Не обрабатывать повторные попытки | Запланируйте периодическую очистку DLQ с помощью retry_dlq() |
| Та же задача повторяется бесконечно | Нет ограничения на максимальное количество попыток | Проверьте task.attempts перед повторной постановкой в очередь |
| Файл DLQ поврежден. | Одновременная запись | Используйте блокировку файлов или переключитесь на Redis/database. |
| Потерянные задачи при сбое | Только DLQ в памяти | Используйте DLQ на основе файлов или Redis. |
Часто задаваемые вопросы
Должен ли я использовать DLQ в памяти или постоянный?
Используйте память для кратковременных сценариев. Используйте файловые службы или службы Redis для долго работающих служб, в которых при перезапуске процесса задачи в очереди будут потеряны.
Когда мне следует навсегда отказаться от задачи?
После 2–3 повторных попыток DLQ (помимо исходных попыток). Если задача не удалась более 6 раз, скорее всего, параметры неверны — зарегистрируйте это и двигайтесь дальше.
Могу ли я объединить это с шаблоном автоматического выключателя?
Да. Автоматический выключатель предотвращает отправку запросов во время сбоя, а DLQ фиксирует любые задачи, которые не выполняются, до отключения цепи. ВидетьСхема автоматического выключателя.
Никогда больше не теряйте неудачную задачу CAPTCHA с CaptchaAI
Получите ключ API по адресуcaptchaai.com.
Связанные руководства
- Шаблон автоматического выключателя для вызовов API CAPTCHA
- Реализация логики повтора для API CaptchaAI
- Redis Queue + CaptchaAI: распределенная обработка