Ketika solve CAPTCHA gagal setelah semua retry, data task akan hilang kecuali Anda menangkapnya. Dead-letter queue (DLQ) menyimpan task yang gagal untuk dicoba ulang nanti, dianalisis, atau diberi alert — sehingga tidak ada pekerjaan yang berhenti secara diam-diam.
Ketika Task Gagal
Alasan umum task CAPTCHA masuk ke DLQ:
ERROR_CAPTCHA_UNSOLVABLE— Solver tidak dapat menyelesaikan challengeERROR_NO_SLOT_AVAILABLE— Semua worker sibuk, retry sudah habis- Timeout — Solver tidak memberikan hasil dalam batas waktu
- Error jaringan — Koneksi terputus selama polling
Tanpa DLQ, kegagalan ini hanya menghasilkan baris log dan terlupakan.
Python: DLQ In-Memory dengan Retry
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()}")
Hasil yang diharapkan:
Solved: 03AGdBq26ZfPxL...
Solved: 03AGdBq27AbCdE...
[dlq] Added: ERROR_CAPTCHA_UNSOLVABLE (attempts: 4)
Solved: 03AGdBq28FgHiJ...
[dlq] Added: Task 71823460 timed out (attempts: 4)
DLQ size: 2
Retry dari 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 dengan Persistensi File
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()}`);
})();
Analisis DLQ
Ekspor dan analisis task yang gagal untuk menemukan pola:
# Export DLQ untuk analisis
dlq.export_json("failed-tasks.json")
# Analisis distribusi error
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}")
Gunakan data ini untuk:
- Identifikasi sitekey yang selalu gagal → periksa apakah parameter sudah benar
- Timeout tertentu pada jam tertentu → korelasikan dengan beban API
- Temukan error jaringan → periksa kesehatan proxy
Kebijakan Replay Dead-Letter
- Pertahankan konteks request asli, alasan kegagalan, dan jumlah percobaan ulang agar replay tetap aman dan mudah di-debug.
- Tentukan kegagalan mana yang bisa di-retry otomatis dan mana yang harus menunggu tinjauan operator.
- Perlakukan throughput replay, usia queue, dan tingkat kegagalan berulang sebagai sinyal operasional kelas satu.
Pemecahan Masalah
| Masalah | Penyebab | Perbaikan |
|---|---|---|
| DLQ terus bertambah | Tidak memproses retry | Jadwalkan pengurasan DLQ berkala dengan retry_dlq() |
| Task yang sama di-retry selamanya | Tidak ada batas percobaan maksimal | Periksa task.attempts sebelum memasukkan ulang ke queue |
| File DLQ rusak | Penulisan concurrent | Gunakan file locking atau beralih ke Redis/database |
| Task hilang saat crash | Hanya DLQ in-memory | Gunakan DLQ berbasis file atau Redis |
Pertanyaan Umum
Haruskah saya menggunakan DLQ in-memory atau persistent?
Gunakan in-memory untuk skrip yang berjalan singkat. Gunakan berbasis file atau Redis untuk layanan yang berjalan lama di mana proses yang di-restart akan kehilangan task yang antri.
Kapan saya harus membuang task secara permanen?
Setelah 2–3 retry DLQ (di atas retry asli). Jika sebuah task gagal total 6+ kali, kemungkinan besar parameternya salah — catat dan lanjutkan.
Bisakah saya menggabungkan ini dengan pola circuit breaker?
Ya. Circuit breaker mencegah pengiriman request selama outage, dan DLQ menangkap task apa pun yang gagal sebelum circuit trip. Lihat Circuit Breaker Pattern untuk API Call CAPTCHA.
Jangan Pernah Kehilangan Task CAPTCHA yang Gagal Lagi
Dapatkan API key Anda di captchaai.com.
Panduan Terkait
- Circuit Breaker Pattern untuk API Call CAPTCHA
- Implementasi Retry Logic untuk API CaptchaAI
- Redis Queue + CaptchaAI: Pemrosesan Terdistribusi