Khắc Phục Sự Cố

ERROR_ZERO_BALANCE: Khắc phục sự cố thanh toán và lập hoá đơn

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ả statusrequest trả về từ res.php mỗi khi submit thất bại — phân biệt ERROR_ZERO_BALANCE vớ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.

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