Nhận ERROR_ZERO_BALANCE giữa lúc pipeline đang chạy? Tài khoản CaptchaAI của bạn đã hết số dư — mọi task mới sẽ bị từ chối cho tới khi bạn nạp thêm tiền. Đây không phải lỗi code: response trả về status: 0 kèm request: "ERROR_ZERO_BALANCE", tức là dừng cứng chứ không phải sự cố tạm thời nên thử lại. Bài viết này đi thẳng vào cách chẩn đoán, xử lý trong code mà không làm sập cả batch, và tự động cảnh báo trước khi số dư chạm đáy.
Vì sao ERROR_ZERO_BALANCE xảy ra
| Nguyên nhân | Tần suất | Cách khắc phục |
|---|---|---|
| Số dư tài khoản đã cạn | Phổ biến nhất | Nạp tiền tại captchaai.com |
| Khối lượng request tăng đột biến | Thường gặp | Thiết lập giám sát số dư |
| API key bị lộ và bị dùng trái phép | Hiếm | Xoay API key, kiểm tra log sử dụng |
| Phương thức thanh toán hết hạn | Thỉnh thoảng | Cập nhật thông tin thanh toán |
Một agency outsourcing QA chạy batch vài nghìn request reCAPTCHA mỗi đêm qua nhiều thread song song thường gặp lỗi này đúng lúc không ai trực dashboard — số dư prepaid cạn trước khi có người nạp lại vào sáng hôm sau. Bảng chẩn đoán và đoạn code cảnh báo bên dưới xử lý chính xác tình huống đó.
Chẩn đoán nhanh theo triệu chứng
Tra theo đúng triệu chứng bạn đang thấy trong log:
ERROR_ZERO_BALANCEở mọi request → tài khoản trống, nạp tiền tại captchaai.com.- Số dư giảm nhanh bất thường → API key bị lộ hoặc code đang gửi task dư thừa; xoay key và kiểm tra log sử dụng.
- Số dư hiển thị dương nhưng vẫn báo lỗi → thường là độ trễ đồng bộ/cache; đợi khoảng 1 phút rồi thử lại.
- Không nạp được tiền → vấn đề với phương thức thanh toán; cập nhật trong bảng điều khiển.
Mẹo: log cả
statusvàrequesttrả về từres.phpmỗi khi submit thất bại — phân biệtERROR_ZERO_BALANCEvới các mã lỗi khác giúp bạn định vị đúng nguyên nhân trong vài giây thay vì đoán mò.
Kiểm tra số dư qua API
Gọi action=getbalance trước khi chạy một batch lớn để biết chắc bạn còn đủ số dư:
import requests
def check_balance(api_key):
"""Check current CaptchaAI balance."""
resp = requests.get(
"https://ocr.captchaai.com/res.php",
params={"key": api_key, "action": "getbalance", "json": 1},
timeout=10,
)
data = resp.json()
if data.get("status") == 1:
return float(data["request"])
raise RuntimeError(f"Balance check failed: {data.get('request')}")
balance = check_balance("YOUR_API_KEY")
print(f"Balance: ${balance:.4f}")
Xử lý ERROR_ZERO_BALANCE mà không làm crash pipeline
Đừng để một ERROR_ZERO_BALANCE giữa batch làm chết cả tiến trình. Class dưới đây cache số dư, chặn request trước khi gửi nếu số dư quá thấp, và phân biệt rõ "hết tiền" với các lỗi tạm thời khác:
import requests
import time
import logging
logger = logging.getLogger(__name__)
class BalanceAwareSolver:
"""Solver that handles zero balance without crashing."""
def __init__(self, api_key, min_balance=0.50):
self.api_key = api_key
self.min_balance = min_balance
self._last_balance_check = 0
self._cached_balance = None
def solve(self, params):
"""Solve CAPTCHA with balance pre-check."""
# Check balance every 5 minutes
if time.time() - self._last_balance_check > 300:
self._check_balance()
if self._cached_balance is not None and self._cached_balance < 0.01:
raise InsufficientBalanceError(
f"Balance too low: ${self._cached_balance:.4f}. "
"Add funds at https://captchaai.com"
)
try:
return self._submit_and_poll(params)
except ZeroBalanceError:
self._cached_balance = 0.0
logger.error("ERROR_ZERO_BALANCE — add funds at captchaai.com")
raise
def _check_balance(self):
"""Check and cache balance."""
try:
resp = requests.get(
"https://ocr.captchaai.com/res.php",
params={
"key": self.api_key,
"action": "getbalance",
"json": 1,
},
timeout=10,
)
data = resp.json()
if data.get("status") == 1:
self._cached_balance = float(data["request"])
self._last_balance_check = time.time()
if self._cached_balance < self.min_balance:
logger.warning(
f"Low balance: ${self._cached_balance:.4f} "
f"(threshold: ${self.min_balance:.2f})"
)
except Exception as e:
logger.debug(f"Balance check failed: {e}")
def _submit_and_poll(self, params):
"""Submit task and poll for result."""
data = {"key": self.api_key, "json": 1, **params}
resp = requests.post(
"https://ocr.captchaai.com/in.php", data=data, timeout=30,
)
result = resp.json()
if result.get("status") != 1:
error = result.get("request", "")
if error == "ERROR_ZERO_BALANCE":
raise ZeroBalanceError("Account balance is zero")
raise RuntimeError(f"Submit failed: {error}")
task_id = result["request"]
time.sleep(10)
for _ in range(24):
resp = requests.get(
"https://ocr.captchaai.com/res.php",
params={
"key": self.api_key, "action": "get",
"id": task_id, "json": 1,
},
timeout=15,
)
data = resp.json()
if data.get("status") == 1:
return data["request"]
if data["request"] != "CAPCHA_NOT_READY":
raise RuntimeError(data["request"])
time.sleep(5)
raise TimeoutError("Solve timeout")
class ZeroBalanceError(Exception):
"""Raised when account has no balance."""
pass
class InsufficientBalanceError(Exception):
"""Raised when balance is below minimum threshold."""
pass
Thiết lập cảnh báo số dư tự động
Đặt một ngưỡng cảnh báo (ví dụ $2 cho batch vài nghìn request/ngày) để bạn có thời gian nạp tiền trước khi lỗi thật sự xảy ra, thay vì phát hiện qua log lỗi vào sáng hôm sau:
import smtplib
from email.message import EmailMessage
import threading
import time
import logging
logger = logging.getLogger(__name__)
class BalanceMonitor:
"""Monitor balance and send alerts when low."""
def __init__(self, api_key, alert_threshold=1.00, check_interval=600):
self.api_key = api_key
self.alert_threshold = alert_threshold
self.check_interval = check_interval
self._alert_sent = False
self._running = False
def start(self):
"""Start background monitoring."""
self._running = True
thread = threading.Thread(target=self._monitor_loop, daemon=True)
thread.start()
logger.info("Balance monitor started")
def stop(self):
"""Stop monitoring."""
self._running = False
def _monitor_loop(self):
"""Check balance periodically."""
while self._running:
try:
balance = self._get_balance()
logger.info(f"Balance: ${balance:.4f}")
if balance <= 0:
self._send_alert("CRITICAL: CaptchaAI Zero Balance",
f"Balance is ${balance:.4f}. Solving will fail.")
elif balance < self.alert_threshold and not self._alert_sent:
self._send_alert("WARNING: CaptchaAI Low Balance",
f"Balance: ${balance:.4f} (threshold: ${self.alert_threshold:.2f})")
self._alert_sent = True
elif balance >= self.alert_threshold:
self._alert_sent = False # Reset alert flag
except Exception as e:
logger.error(f"Monitor error: {e}")
time.sleep(self.check_interval)
def _get_balance(self):
"""Check account balance."""
resp = requests.get(
"https://ocr.captchaai.com/res.php",
params={"key": self.api_key, "action": "getbalance", "json": 1},
timeout=10,
)
data = resp.json()
if data.get("status") == 1:
return float(data["request"])
raise RuntimeError(data.get("request"))
def _send_alert(self, subject, body):
"""Send email alert. Replace with your notification method."""
logger.critical(f"{subject}: {body}")
# Implement email, Slack webhook, or other notification here
# Usage
monitor = BalanceMonitor("YOUR_API_KEY", alert_threshold=2.00)
monitor.start()
Ước tính chi phí trước khi chạy batch lớn
Trước khi bắn 5.000 request cùng lúc, hãy kiểm tra số dư có đủ theo ước tính hay không — rẻ hơn nhiều so với việc để pipeline dừng giữa chừng vì ERROR_ZERO_BALANCE:
# Approximate costs per CAPTCHA type
COST_PER_SOLVE = {
"recaptcha_v2": 0.003,
"recaptcha_v3": 0.004,
"turnstile": 0.002,
"geetest": 0.003,
"image": 0.001,
"bls": 0.002,
}
def estimate_cost(captcha_type, quantity):
"""Estimate cost for a batch of solves."""
rate = COST_PER_SOLVE.get(captcha_type, 0.003)
total = rate * quantity
return total
def check_budget(api_key, captcha_type, planned_solves):
"""Check if balance covers planned solves."""
balance = check_balance(api_key)
estimated = estimate_cost(captcha_type, planned_solves)
if balance >= estimated:
print(f"Budget OK: ${balance:.4f} covers ~{int(balance / COST_PER_SOLVE[captcha_type])} solves")
return True
else:
shortfall = estimated - balance
print(f"Need ${shortfall:.4f} more for {planned_solves} {captcha_type} solves")
return False
# Check before a large batch
check_budget("YOUR_API_KEY", "recaptcha_v2", 5000)
Chiến lược xử lý khi hết số dư giữa chừng
Khi số dư về 0 giữa lúc đang chạy, bạn có ba lựa chọn thực tế:
- Bỏ qua (skip) — item đó không được giải, pipeline chạy tiếp với các item còn lại.
- Đưa vào hàng đợi (queue) — lưu lại để giải bù ngay sau khi tài khoản được nạp tiền.
- Dừng hẳn (raise) — ngắt automation, phù hợp khi mọi item đều bắt buộc phải có kết quả.
Class dưới đây gói cả ba chiến lược vào một nơi:
class GracefulSolver:
"""Fall back to manual or skip when balance is zero."""
def __init__(self, api_key, on_zero_balance="skip"):
self.api_key = api_key
self.on_zero_balance = on_zero_balance # "skip", "queue", "raise"
self._pending_queue = []
self.solver = BalanceAwareSolver(api_key)
def solve_or_degrade(self, params, item_id=None):
"""Try to solve, degrade gracefully on zero balance."""
try:
return self.solver.solve(params)
except (ZeroBalanceError, InsufficientBalanceError):
return self._handle_zero(params, item_id)
def _handle_zero(self, params, item_id):
"""Handle zero balance based on configured strategy."""
if self.on_zero_balance == "skip":
logger.warning(f"Skipping CAPTCHA for item {item_id} — no balance")
return None
elif self.on_zero_balance == "queue":
self._pending_queue.append({"params": params, "item_id": item_id})
logger.info(f"Queued item {item_id} — {len(self._pending_queue)} pending")
return None
else: # "raise"
raise ZeroBalanceError("No balance — stopping automation")
def retry_pending(self):
"""Retry queued items after balance is refilled."""
if not self._pending_queue:
return []
results = []
remaining = []
for item in self._pending_queue:
try:
token = self.solver.solve(item["params"])
results.append({"item_id": item["item_id"], "token": token})
except (ZeroBalanceError, InsufficientBalanceError):
remaining.append(item)
break # Stop retrying — still no balance
self._pending_queue = remaining + self._pending_queue[len(results) + len(remaining):]
return results
Câu hỏi thường gặp
ERROR_ZERO_BALANCE khác gì với các lỗi tạm thời như timeout?
Khác hoàn toàn. CAPCHA_NOT_READY hay timeout là trạng thái tạm thời — cứ polling tiếp là được. ERROR_ZERO_BALANCE là dừng cứng: tài khoản không còn tiền nên server từ chối nhận task mới, và retry liên tục chỉ tốn thêm request chứ không giải quyết được gì. Việc đầu tiên cần làm là nạp tiền, không phải sửa code retry.
Nên đặt ngưỡng cảnh báo số dư ở mức nào?
Không có con số cố định — hãy tính theo tốc độ tiêu số dư trung bình trong giờ cao điểm của bạn. Hai mốc tham khảo phổ biến trong team vận hành automation:
- Ngưỡng "cảnh báo sớm": đủ để chạy tiếp vài giờ, đủ thời gian để có người nạp tiền trong giờ làm việc.
- Ngưỡng "khẩn cấp": đủ cho khoảng 15–30 phút cuối, dùng để tự động tạm dừng batch thay vì để lỗi dồn vào log.
Chạy nhiều thread song song có làm số dư cạn nhanh hơn không?
Với các gói tính theo thread như BASIC ($15/tháng, 5 thread), mỗi thread được giải không giới hạn trong tháng — số dư không cạn vì số lượng request. Dù vậy, vẫn nên gọi getbalance định kỳ trong mọi trường hợp, vì hết hạn gói hoặc phương thức thanh toán lỗi có thể khiến tài khoản mất quyền truy cập bất ngờ giữa batch.
Có thể tự động nạp tiền khi số dư thấp không?
Kiểm tra bảng điều khiển CaptchaAI để biết các tùy chọn tự động nạp tiền hiện có. Nếu chưa đủ linh hoạt, bạn hoàn toàn có thể tự xây hệ thống giám sát riêng bằng API số dư (action=getbalance) kết hợp cảnh báo qua email hoặc Slack như ở phần trên.
CaptchaAI có tính phí cho các lần giải không thành công không?
Không. Bạn chỉ bị tính phí cho những lần giải thành công có trả về token hoặc câu trả lời — các lần thất bại không trừ số dư.
Hướng dẫn liên quan
Đừng để pipeline đứng giữa chừng — nạp số dư cho tài khoản CaptchaAI của bạn và bật cảnh báo số dư thấp ngay từ hôm nay.