Một request giải CAPTCHA lỗi không nên làm cả batch dừng — bài học dev automation thường chỉ rút ra sau khi pipeline crash lúc 2 giờ sáng.
Một agency QA ở TP.HCM chạy batch 5.000 URL checkout mỗi đêm qua CaptchaAI trên gói STANDARD ($30/tháng, 15 thread): không xử lý lỗi mềm, một ERROR_TOO_MUCH_REQUESTS ở URL 42 làm cả script dừng, mất 4.958 URL còn lại. Xuống cấp nhẹ nhàng: bỏ qua, xếp hàng thử lại, hoặc chuyển sang chế độ giới hạn.
Vì sao request giải CAPTCHA thất bại
Cần biết lỗi nào đáng thử lại:
| Thất bại | Mã lỗi | Chiến lược phục hồi |
|---|---|---|
| Hết thời gian chờ | CAPCHA_NOT_READY |
Thử lại với thử thách mới |
| Tham số sai | ERROR_BAD_PARAMETERS |
Ghi log và bỏ qua |
| Sai sitekey | ERROR_WRONG_GOOGLEKEY |
Trích xuất lại sitekey |
| Hết số dư | ERROR_ZERO_BALANCE |
Tạm dừng, cảnh báo, chờ nạp tiền |
| Bị giới hạn tần suất | ERROR_TOO_MUCH_REQUESTS |
Backoff theo cấp số nhân |
| API không phản hồi | Lỗi kết nối | Circuit breaker + thử lại |
Chỉ 3/6 lỗi trên đáng retry ngay; 3 lỗi còn lại cần sửa dữ liệu hoặc nạp tiền.
Nên retry
- Timeout, rate limit — lỗi tạm thời.
- Tham số dựng lại được xác định.
Nên bỏ qua
ERROR_BAD_PARAMETERS,ERROR_WRONG_GOOGLEKEY— lỗi ở dữ liệu gửi đi.- Lỗi mà thử thêm chỉ lãng phí số dư.
Pattern 1: Bỏ qua và chạy tiếp
Phù hợp cho xử lý theo lô, nơi vài item lỗi không ảnh hưởng kết quả.
- Dùng khi: crawler giá Shopee, Tiki — bỏ sản phẩm lỗi, giữ phiên chạy.
- Tránh khi: mỗi item bắt buộc xử lý.
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_or_skip(captcha_type, sitekey, page_url, max_retries=2):
"""Try to solve; return None on failure instead of crashing."""
for attempt in range(max_retries):
try:
token = solve_captcha(captcha_type, sitekey, page_url)
if token:
return token
except Exception as e:
print(f"Attempt {attempt + 1} failed: {e}")
return None # Skip this item
def process_urls(urls):
results = []
skipped = []
for url in urls:
sitekey = extract_sitekey(url)
if not sitekey:
skipped.append({"url": url, "reason": "no_sitekey"})
continue
token = solve_or_skip("recaptcha_v2", sitekey, url)
if token:
data = submit_form(url, token)
results.append({"url": url, "data": data})
else:
skipped.append({"url": url, "reason": "solve_failed"})
print(f"Processed: {len(results)}, Skipped: {len(skipped)}")
return results, skipped
Pattern 2: Retry queue (hàng đợi thử lại)
Khi bỏ qua là chưa đủ và mọi task cần được xử lý.
- Dùng khi: mỗi task có giá trị riêng (đơn hàng, form đăng ký).
- Cơ chế: task lỗi vào hàng đợi, backoff tăng theo số lần retry.
from collections import deque
import json
class RetryQueue:
def __init__(self, max_retries=3, backoff_base=60):
self.queue = deque()
self.max_retries = max_retries
self.backoff_base = backoff_base
def add(self, task):
task["retry_count"] = task.get("retry_count", 0) + 1
if task["retry_count"] <= self.max_retries:
task["retry_after"] = time.time() + (
self.backoff_base * task["retry_count"]
)
self.queue.append(task)
return True
return False # Exceeded max retries
def get_ready(self):
"""Get tasks ready for retry."""
ready = []
remaining = deque()
now = time.time()
while self.queue:
task = self.queue.popleft()
if task["retry_after"] <= now:
ready.append(task)
else:
remaining.append(task)
self.queue = remaining
return ready
def save(self, filepath="retry_queue.json"):
with open(filepath, "w") as f:
json.dump(list(self.queue), f)
def load(self, filepath="retry_queue.json"):
try:
with open(filepath) as f:
self.queue = deque(json.load(f))
except FileNotFoundError:
pass
# Usage
retry_q = RetryQueue()
def process_with_retry(task):
try:
token = solve_captcha(task["type"], task["sitekey"], task["url"])
if token:
return submit_form(task["url"], token)
else:
retry_q.add(task)
except Exception:
retry_q.add(task)
# Process retry queue periodically
def drain_retry_queue():
ready = retry_q.get_ready()
for task in ready:
process_with_retry(task)
Pattern 3: Chế độ xuống cấp (degraded mode)
Khi số lỗi liên tiếp vượt ngưỡng, đừng gửi request như bình thường.
- Dùng khi:
ERROR_ZERO_BALANCEhoặc API không phản hồi nhiều lần. - Cơ chế: chuyển solver sang chế độ giới hạn trong thời gian cố định.
Lưu ý:
failure_thresholdquá thấp khiến batch bình thường cũng bị coi là lỗi.
class CaptchaSolver:
def __init__(self, api_key):
self.api_key = api_key
self.degraded = False
self.failure_count = 0
self.failure_threshold = 5
self.recovery_time = None
def solve(self, captcha_type, sitekey, page_url):
if self.degraded:
if time.time() < self.recovery_time:
return self._degraded_action(page_url)
else:
self.degraded = False
self.failure_count = 0
try:
token = self._solve_api(captcha_type, sitekey, page_url)
self.failure_count = 0
return token
except Exception as e:
self.failure_count += 1
if self.failure_count >= self.failure_threshold:
self._enter_degraded_mode()
raise
def _enter_degraded_mode(self):
self.degraded = True
self.recovery_time = time.time() + 300 # 5 min
print("Entering degraded mode for 5 minutes")
# Send alert
def _degraded_action(self, url):
"""What to do when solving is unavailable."""
# Option A: Skip CAPTCHA pages entirely
return None
# Option B: Queue for later
# retry_queue.add({"url": url, ...})
# return None
# Option C: Try alternative solver
# return self._solve_with_backup_api(...)
def _solve_api(self, captcha_type, sitekey, page_url):
# Normal CaptchaAI API call
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
}).json()
if resp["status"] != 1:
raise Exception(resp["request"])
task_id = resp["request"]
for _ in range(24):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key, "action": "get",
"id": task_id, "json": "1"
}).json()
if result["status"] == 1:
return result["request"]
if result["request"] != "CAPCHA_NOT_READY":
raise Exception(result["request"])
raise Exception("TIMEOUT")
Pattern nào dùng khi nào
| Pattern | Dùng khi | Không dùng khi |
|---|---|---|
| Bỏ qua | Batch lớn, item lỗi chấp nhận được | Mỗi item bắt buộc xử lý |
| Retry queue | Task nào cũng phải hoàn tất | Cần phản hồi tức thì |
| Degraded mode | Lỗi hệ thống lặp lại | Lỗi ở một task đơn lẻ |
Node.js: kết hợp cả ba pattern
Ví dụ dưới đây gộp retry queue và degraded mode trong một class.
- Chế độ ngắn (5 phút) cho lỗi liên tiếp.
- Chế độ dài (10 phút) riêng cho
ERROR_ZERO_BALANCE.
class ResilientSolver {
constructor(apiKey) {
this.apiKey = apiKey;
this.retryQueue = [];
this.failureCount = 0;
this.degraded = false;
}
async solve(type, sitekey, pageUrl) {
if (this.degraded) {
this.retryQueue.push({ type, sitekey, pageUrl, addedAt: Date.now() });
return null;
}
try {
const token = await this._callApi(type, sitekey, pageUrl);
this.failureCount = 0;
return token;
} catch (err) {
this.failureCount++;
if (err.message === 'ERROR_ZERO_BALANCE') {
this._enterDegraded(600000); // 10 min
return null;
}
if (this.failureCount >= 5) {
this._enterDegraded(300000); // 5 min
}
this.retryQueue.push({ type, sitekey, pageUrl, addedAt: Date.now() });
return null;
}
}
_enterDegraded(durationMs) {
this.degraded = true;
console.warn(`Degraded mode for ${durationMs / 1000}s`);
setTimeout(() => {
this.degraded = false;
this.failureCount = 0;
this.drainRetryQueue();
}, durationMs);
}
async drainRetryQueue() {
const tasks = this.retryQueue.splice(0);
for (const task of tasks) {
await this.solve(task.type, task.sitekey, task.pageUrl);
}
}
async _callApi(type, sitekey, pageUrl) {
// Standard submit + poll
const axios = require('axios');
const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: { key: this.apiKey, method: 'userrecaptcha', googlekey: sitekey, pageurl: 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: this.apiKey, 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('TIMEOUT');
}
}
Xử lý sự cố thường gặp
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| Tất cả task bị bỏ qua | Ngưỡng xuống cấp quá thấp | Tăng failure_threshold |
| Retry queue phình to | Task không bao giờ thành công | Giới hạn retry; đẩy sang dead letter queue |
| Phục hồi quá chậm | recovery_time quá dài |
Rút ngắn; thêm health-check probe |
| Mất task khi restart | Queue chỉ nằm trong bộ nhớ | Ghi queue xuống file hoặc database |
Mẹo: log lý do skip mỗi item để review batch sau.
Câu hỏi thường gặp
Graceful degradation khác circuit breaker ở điểm nào?
Circuit breaker chặn hẳn cuộc gọi khi phát hiện lỗi liên tiếp. Graceful degradation rộng hơn, gồm cả fallback và logic bỏ qua.
Có nên retry mọi task giải CAPTCHA thất bại không?
Không. ERROR_BAD_PARAMETERS và ERROR_WRONG_GOOGLEKEY sẽ thất bại y hệt. Chỉ retry lỗi tạm thời như CAPCHA_NOT_READY.
Ngưỡng thất bại bao nhiêu thì nên vào chế độ xuống cấp?
Không có con số cố định. Bài này dùng failure_threshold = 5 cho batch vài nghìn URL/đêm; job real-time nên dùng ngưỡng thấp hơn (2–3).
Retry queue nên lưu ở đâu để không mất dữ liệu khi restart?
File JSON đủ cho script một máy; nhiều worker nên dùng Redis hoặc database.
Xây dựng automation CAPTCHA ổn định với CaptchaAI
Nhận API key tại captchaai.com và áp dụng pattern trên cho batch tiếp theo.