API Tutorials

CaptchaAI余额检查和自动充值集成

批量任务跑到一半突然全部失败,排查半天,原因往往只是余额见底了。更靠谱的做法是把余额检查写进代码:跑批前查一次,跑的过程中常驻监控,再加一层消费记录。本文给出可以直接复制的 Python 实现。


查询余额:一次 GET 请求就够了

CaptchaAI 的 getbalance 接口不需要额外鉴权步骤,带上 API Key 发一个 GET 请求即可:

import requests

API_KEY = "YOUR_API_KEY"

resp = requests.get("https://ocr.captchaai.com/res.php", params={
    "key": API_KEY,
    "action": "getbalance",
    "json": 1,
})

data = resp.json()
balance = float(data["request"])
print(f"Balance: ${balance:.2f}")

响应格式:

{"status": 1, "request": "12.345"}

status 为 1 表示查询成功,余额金额在 request 字段里,单位始终是美元。接口本身很轻量,不占用求解额度,也不计入速率限制。


跑批前先做一次余额预检查

批量任务开跑前先确认余额够不够,比跑到一半失败再回滚划算:

import requests
import sys


def check_balance(api_key, min_required=1.0):
    """Check balance and abort if too low."""
    resp = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": api_key,
        "action": "getbalance",
        "json": 1,
    })
    data = resp.json()

    if data.get("status") != 1:
        print(f"Balance check failed: {data.get('request')}")
        return None

    balance = float(data["request"])
    print(f"Current balance: ${balance:.2f}")

    if balance < min_required:
        print(f"WARNING: Balance ${balance:.2f} below minimum ${min_required:.2f}")
        return None

    return balance


# Usage
API_KEY = "YOUR_API_KEY"
balance = check_balance(API_KEY, min_required=5.0)

if balance is None:
    print("Insufficient balance. Add funds before running pipeline.")
    sys.exit(1)

print(f"Balance OK (${balance:.2f}). Starting pipeline...")

min_required 建议按单次任务的实际消耗来设,不要随手写个数字——一批任务大概花 $2,阈值设成 $1 基本等于没设。


写一个常驻的余额监控器

预检查只能挡住“开局就没钱”,长时间跑的任务还需要持续盯着余额变化,跌破阈值就报警:

import requests
import time
import smtplib
from email.message import EmailMessage


class BalanceMonitor:
    """Monitor CaptchaAI balance and send alerts."""

    def __init__(self, api_key, alert_threshold=5.0, check_interval=300):
        self.api_key = api_key
        self.alert_threshold = alert_threshold
        self.check_interval = check_interval  # seconds
        self.base_url = "https://ocr.captchaai.com"
        self.history = []
        self.alerted = False

    def get_balance(self):
        resp = requests.get(f"{self.base_url}/res.php", params={
            "key": self.api_key,
            "action": "getbalance",
            "json": 1,
        }, timeout=10)
        data = resp.json()
        return float(data["request"])

    def check_and_alert(self):
        balance = self.get_balance()
        self.history.append({
            "time": time.time(),
            "balance": balance,
        })

        print(f"Balance: ${balance:.2f}")

        if balance < self.alert_threshold and not self.alerted:
            self.send_alert(balance)
            self.alerted = True
        elif balance >= self.alert_threshold:
            self.alerted = False

        return balance

    def send_alert(self, balance):
        """Send low-balance alert. Override for your notification system."""
        print(f"ALERT: Balance low! ${balance:.2f} < ${self.alert_threshold:.2f}")
        # Add your notification logic:
        # - Email, Slack webhook, SMS, etc.

    def get_spending_rate(self, hours=1):
        """Calculate spending rate over the last N hours."""
        cutoff = time.time() - (hours * 3600)
        recent = [h for h in self.history if h["time"] > cutoff]

        if len(recent) < 2:
            return 0.0

        spent = recent[0]["balance"] - recent[-1]["balance"]
        return max(0.0, spent)

    def estimate_remaining_hours(self):
        """Estimate how many hours until balance runs out."""
        rate = self.get_spending_rate(hours=1)
        if rate <= 0:
            return float("inf")

        balance = self.history[-1]["balance"] if self.history else 0
        return balance / rate

    def run(self):
        """Run continuous monitoring."""
        print(f"Monitoring balance (alert at ${self.alert_threshold:.2f})")
        while True:
            try:
                self.check_and_alert()
                rate = self.get_spending_rate()
                remaining = self.estimate_remaining_hours()
                print(f"  Spending: ${rate:.2f}/hr, ~{remaining:.1f}hrs remaining")
            except Exception as e:
                print(f"Monitor error: {e}")
            time.sleep(self.check_interval)


# Usage
monitor = BalanceMonitor(
    api_key="YOUR_API_KEY",
    alert_threshold=5.0,
    check_interval=300,  # Check every 5 minutes
)
monitor.run()

alerted 标志位是关键:没有它,余额持续低于阈值时 run() 每轮都会重复报警。加上这个开关后,只有“跌破阈值”的瞬间触发一次,回升后重置。get_spending_rateestimate_remaining_hours 把历史记录换算成“还能撑多久”。


接入 Slack 通知

send_alert() 默认只打印日志,接上真实通知渠道才有用,Slack 版本如下:

import requests


def send_slack_alert(webhook_url, balance, threshold):
    """Send balance alert to Slack channel."""
    payload = {
        "text": f":warning: CaptchaAI balance low!",
        "blocks": [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": (
                        f"*CaptchaAI Balance Alert*\n"
                        f"Current balance: *${balance:.2f}*\n"
                        f"Alert threshold: ${threshold:.2f}\n"
                        f"Action: Add funds at captchaai.com"
                    ),
                },
            },
        ],
    }
    requests.post(webhook_url, json=payload)


# Add to BalanceMonitor.send_alert():
# send_slack_alert(SLACK_WEBHOOK, balance, self.alert_threshold)

国内团队更常把告警接在钉钉或企业微信群机器人上,而不是 Slack。改法很简单:把 payload 换成对方要求的 JSON 格式(通常是 msgtype + text/markdown 字段),POST 到机器人 Webhook 地址,调用位置仍挂在 BalanceMonitor.send_alert() 里,其余逻辑不用改。


记录每日、每周、每月的消费

只看当前余额,说明不了“钱是怎么花掉的”。把每次查询的结果落盘成日志,就能按天核算消费:

import csv
import datetime


class SpendingTracker:
    """Track CaptchaAI spending over time."""

    def __init__(self, api_key, log_file="captchaai_spending.csv"):
        self.api_key = api_key
        self.log_file = log_file
        self._init_log()

    def _init_log(self):
        try:
            with open(self.log_file, "r") as f:
                pass
        except FileNotFoundError:
            with open(self.log_file, "w", newline="") as f:
                writer = csv.writer(f)
                writer.writerow(["timestamp", "balance"])

    def record_balance(self):
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": self.api_key,
            "action": "getbalance",
            "json": 1,
        })
        balance = float(resp.json()["request"])

        with open(self.log_file, "a", newline="") as f:
            writer = csv.writer(f)
            writer.writerow([
                datetime.datetime.utcnow().isoformat(),
                f"{balance:.4f}",
            ])
        return balance

    def get_daily_spending(self):
        """Calculate today's spending from log."""
        today = datetime.date.today().isoformat()
        balances = []

        with open(self.log_file, "r") as f:
            reader = csv.DictReader(f)
            for row in reader:
                if row["timestamp"].startswith(today):
                    balances.append(float(row["balance"]))

        if len(balances) < 2:
            return 0.0
        return balances[0] - balances[-1]

    def summary(self):
        """Print spending summary."""
        balance = self.record_balance()
        daily = self.get_daily_spending()
        print(f"Current balance: ${balance:.2f}")
        print(f"Spent today: ${daily:.2f}")
        if daily > 0:
            print(f"Daily rate: ${daily:.2f}/day")
            print(f"Days remaining: {balance / daily:.1f}")


# Usage
tracker = SpendingTracker("YOUR_API_KEY")
tracker.summary()

把这段代码接进定时任务(例如每小时跑一次 record_balance()),几天后 captchaai_spending.csv 就能看出真实的消费曲线,比事后翻账单直观,也方便按周/按月汇总。


把余额检查嵌进求解流程

前面几段都是“外挂式”的监控,下面这个例子把余额检查直接写进求解逻辑本身,任务发起前自动确认额度够用:

import requests
import time


class BalanceAwareSolver:
    """Solver that checks balance before solving."""

    def __init__(self, api_key, min_balance=1.0):
        self.api_key = api_key
        self.base_url = "https://ocr.captchaai.com"
        self.min_balance = min_balance
        self.last_balance_check = 0
        self.cached_balance = None
        self.solves_since_check = 0

    def solve(self, method, **params):
        """Solve with balance pre-check."""
        # Check balance every 50 solves or every 5 minutes
        if self._should_check_balance():
            balance = self._get_balance()
            if balance < self.min_balance:
                raise RuntimeError(
                    f"Balance too low: ${balance:.2f} "
                    f"(minimum: ${self.min_balance:.2f})"
                )

        return self._do_solve(method, **params)

    def _should_check_balance(self):
        elapsed = time.time() - self.last_balance_check
        return elapsed > 300 or self.solves_since_check >= 50

    def _get_balance(self):
        resp = requests.get(f"{self.base_url}/res.php", params={
            "key": self.api_key,
            "action": "getbalance",
            "json": 1,
        })
        self.cached_balance = float(resp.json()["request"])
        self.last_balance_check = time.time()
        self.solves_since_check = 0
        return self.cached_balance

    def _do_solve(self, method, **params):
        data = {"key": self.api_key, "method": method, "json": 1}
        data.update(params)
        resp = requests.post(f"{self.base_url}/in.php", data=data)
        task_id = resp.json()["request"]

        for _ in range(60):
            time.sleep(5)
            result = requests.get(f"{self.base_url}/res.php", params={
                "key": self.api_key, "action": "get",
                "id": task_id, "json": 1,
            })
            data = result.json()
            if data["request"] != "CAPCHA_NOT_READY":
                self.solves_since_check += 1
                return data["request"]

        raise TimeoutError("Solve timeout")


# Usage
solver = BalanceAwareSolver("YOUR_API_KEY", min_balance=2.0)

try:
    token = solver.solve("userrecaptcha", googlekey="KEY", pageurl="https://example.com")
except RuntimeError as e:
    print(f"Balance issue: {e}")

_should_check_balance 用“5 分钟或 50 次求解”两个条件做缓存,避免每次 solve() 都发一次网络请求。余额不够时直接抛 RuntimeError,上层代码可按需捕获,决定暂停、重试还是终止。


故障排查

问题 可能原因 处理方式
余额返回 0 新账户尚未充值,或额度已用完 登录 captchaai.com 添加资金
ERROR_WRONG_USER_KEY API Key 填错或已失效 到控制台重新核对 Key
余额查询超时 网络波动 请求里加上 timeout=10
余额数字没变化 读到了缓存值 强制发起一次新请求,跳过本地缓存

仍未解决时,确认调用的确实是 getbalance,再看 status 字段——不为 1 时 request 里通常已给出原因。


常见问题

生产环境里,余额应该多久查一次?

每 5-10 分钟或每 50-100 次求解查一次就够,参考 _should_check_balance 的判断逻辑。不用每次求解前都查——查询不消耗解决额度,但过于频繁没有意义。

CaptchaAI 支持余额低于阈值时自动充值吗?

目前 API 不提供自动扣款功能。BalanceMonitor 只负责在余额跌破阈值时发通知(日志、Slack/钉钉消息等),实际充值仍需登录 captchaai.com 手动操作,或对接自己的计费系统。

余额突然变成 0 或读数异常,应该先查什么?

先登录后台核对额度是否确实用完,再检查 API Key 有没有填错——ERROR_WRONG_USER_KEY 是最常见的误报原因。数字忽高忽低多半是缓存值,强制刷新一次即可。

多个 worker 并发跑任务,要不要每个进程都单独查一次余额?

不需要。把检查放在共享的 BalanceMonitor 进程里,所有 worker 读同一份缓存结果;各自高频查询只会抢占限流配额,还会让告警重复触发。


相关指南


别等余额跑空才发现——从 CaptchaAI 开始,把每一次消耗都记下来。

该文章已禁用评论。