API Tutorials

不同验证码类型的自定义超时设置

超时到底该设多少秒?因验证码类型而异:图片验证码 3 秒出结果,Cloudflare Challenge 却可能要等上 2 分钟。共用一个阈值,要么在快验证码上白等,要么在慢验证码上过早判超时。

下面按类型给出实测的初始等待、轮询间隔与最大超时参考值,并附可直接接入的 Python 实现。


各类型验证码超时参考表

验证码类型 识别耗时(规格) 初始等待 轮询间隔 最大超时
图片 / OCR < 0.5 秒 1 秒 2 秒 30 秒
reCAPTCHA v2 < 60 秒 10 秒 5 秒 90 秒
reCAPTCHA v3 < 4 秒 3 秒 2 秒 60 秒
reCAPTCHA Enterprise < 60 秒 10 秒 5 秒 120 秒
隐形 reCAPTCHA < 30 秒 8 秒 5 秒 90 秒
Turnstile < 10 秒 3 秒 3 秒 45 秒
Cloudflare Challenge < 15 秒 8 秒 5 秒 120 秒
GeeTest v3 < 12 秒 5 秒 5 秒 60 秒
BLS < 1 秒 1 秒 2 秒 45 秒
数值可作为起点,再按实际耗时微调。国内开发者需注意:reCAPTCHA 依赖 Google 托管脚本,国内加载常慢于 GeeTest(极验),超时可适当放宽。

常见超时问题排查

问题 原因 处理方式
图片验证码 120 秒才超时 阈值设得太长,白白浪费时间 图片类型缩短到 30 秒
reCAPTCHA v2 频繁超时 最大超时设得太短 reCAPTCHA v2 至少给到 90 秒
第一次轮询总返回“未就绪” 初始等待太短 按类型表调高初始等待
轮询请求量偏高 轮询间隔太短 token 类验证码用 5 秒,图片用 3 秒

常见问题

不同验证码类型为什么不能共用一个超时?

图片验证码 3 秒出结果,给 120 秒超时等于白等 117 秒;反过来 reCAPTCHA Enterprise 只给 30 秒,可能在识别完成前就被判超时。分开配置才能兼顾两种情况。

超时设得太短会怎样?

会收到误报超时——任务可能已解出,只是没轮询到结果。经验值:最大超时设为平均识别耗时的 2 倍。

国内网络下,reCAPTCHA 和 GeeTest 的超时要分开对待吗?

建议分开对待。reCAPTCHA 脚本托管在 Google,国内访问延迟通常高于境内的 GeeTest(极验),最好先实测再定超时。

轮询间隔越短越好吗?

不是。间隔越短,res.php 请求越多,容易触发限流。token 类用 5 秒、图片类用 3 秒即可。


Python 实现:按类型自动匹配超时

每种类型维护独立超时配置,提交后自动套用对应参数:

import requests
import time

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://ocr.captchaai.com"

# Per-type timeout configuration
TIMEOUT_CONFIG = {
    "base64": {
        "initial_wait": 1,
        "poll_interval": 2,
        "max_timeout": 30,
    },
    "userrecaptcha": {
        "initial_wait": 10,
        "poll_interval": 5,
        "max_timeout": 90,
    },
    "userrecaptcha_v3": {
        "initial_wait": 3,
        "poll_interval": 2,
        "max_timeout": 60,
    },
    "turnstile": {
        "initial_wait": 3,
        "poll_interval": 3,
        "max_timeout": 45,
    },
    "cloudflare_challenge": {
        "initial_wait": 8,
        "poll_interval": 5,
        "max_timeout": 120,
    },
    "geetest": {
        "initial_wait": 5,
        "poll_interval": 5,
        "max_timeout": 60,
    },
    "bls": {
        "initial_wait": 1,
        "poll_interval": 2,
        "max_timeout": 45,
    },
    "default": {
        "initial_wait": 10,
        "poll_interval": 5,
        "max_timeout": 120,
    },
}


def get_config_key(method, **params):
    """Determine config key from method and parameters."""
    if method == "userrecaptcha" and params.get("version") == "v3":
        return "userrecaptcha_v3"
    return method


def solve(method, **params):
    """Solve CAPTCHA with type-appropriate timeouts."""
    config_key = get_config_key(method, **params)
    config = TIMEOUT_CONFIG.get(config_key, TIMEOUT_CONFIG["default"])

    # Submit task
    data = {"key": API_KEY, "method": method, "json": 1}
    data.update(params)
    resp = requests.post(f"{BASE_URL}/in.php", data=data, timeout=30)
    result = resp.json()

    if result.get("status") != 1:
        raise RuntimeError(f"Submit error: {result.get('request')}")

    task_id = result["request"]

    # Wait before first poll
    time.sleep(config["initial_wait"])

    # Poll with type-specific interval and timeout
    start = time.time()
    while time.time() - start < config["max_timeout"]:
        resp = requests.get(f"{BASE_URL}/res.php", params={
            "key": API_KEY, "action": "get",
            "id": task_id, "json": 1,
        }, timeout=15)
        data = resp.json()

        if data["request"] != "CAPCHA_NOT_READY":
            elapsed = time.time() - start + config["initial_wait"]
            print(f"Solved {method} in {elapsed:.1f}s")
            return data["request"]

        time.sleep(config["poll_interval"])

    raise TimeoutError(
        f"{method} timeout after {config['max_timeout']}s"
    )


# Usage — each type uses optimal timeouts automatically
# Image (fast: 3s wait, 3s poll, 30s max)
token = solve("base64", body=base64_image)

# reCAPTCHA v2 (medium: 10s wait, 5s poll, 90s max)
token = solve("userrecaptcha", googlekey="KEY", pageurl="https://example.com")

# Turnstile (fast: 3s wait, 3s poll, 45s max)
token = solve("turnstile", sitekey="KEY", pageurl="https://example.com")

根据历史识别时间动态调整超时

若耗时波动大,可让程序记录历史耗时,按 P95 自动上调超时上限:

import statistics


class AdaptiveTimeoutSolver:
    """Adjusts timeouts based on historical solve times."""

    def __init__(self, api_key):
        self.api_key = api_key
        self.base = "https://ocr.captchaai.com"
        self.history = {}  # method -> [solve_times]

    def solve(self, method, **params):
        config = self._get_config(method)

        # Submit
        data = {"key": self.api_key, "method": method, "json": 1}
        data.update(params)
        resp = requests.post(f"{self.base}/in.php", data=data, timeout=30)
        task_id = resp.json()["request"]

        time.sleep(config["initial_wait"])
        start = time.time()

        # Poll with adaptive timeout
        while time.time() - start < config["max_timeout"]:
            resp = requests.get(f"{self.base}/res.php", params={
                "key": self.api_key, "action": "get",
                "id": task_id, "json": 1,
            })
            data = resp.json()

            if data["request"] != "CAPCHA_NOT_READY":
                elapsed = time.time() - start + config["initial_wait"]
                self._record(method, elapsed)
                return data["request"]

            time.sleep(config["poll_interval"])

        raise TimeoutError(f"Timeout after {config['max_timeout']}s")

    def _get_config(self, method):
        """Get timeout config, adjusted by history."""
        base = TIMEOUT_CONFIG.get(method, TIMEOUT_CONFIG["default"])

        # If we have history, adjust max_timeout
        times = self.history.get(method, [])
        if len(times) >= 5:
            p95 = sorted(times)[int(len(times) * 0.95)]
            adjusted_timeout = max(p95 * 2, base["max_timeout"])
            return {**base, "max_timeout": adjusted_timeout}

        return base

    def _record(self, method, elapsed):
        if method not in self.history:
            self.history[method] = []
        self.history[method].append(elapsed)
        # Keep last 100 entries
        if len(self.history[method]) > 100:
            self.history[method] = self.history[method][-100:]

    def get_stats(self, method):
        times = self.history.get(method, [])
        if not times:
            return None
        return {
            "count": len(times),
            "mean": statistics.mean(times),
            "median": statistics.median(times),
            "p95": sorted(times)[int(len(times) * 0.95)],
            "max": max(times),
        }


# Usage
solver = AdaptiveTimeoutSolver("YOUR_API_KEY")
token = solver.solve("turnstile", sitekey="KEY", pageurl="https://example.com")
print(solver.get_stats("turnstile"))

提交超时与轮询超时是两回事

这两者常被混为一谈,需分开配置:

Submit timeout: How long to wait for the API to accept your task
  → Set to 30s (network issues only)

Poll timeout: How long to wait for the solve result
  → Varies by CAPTCHA type (30s to 120s)

对应到代码里:

# Submit timeout (fixed, short)
resp = requests.post(
    f"{BASE_URL}/in.php", data=data,
    timeout=30,  # 30s is plenty for submission
)

# Poll timeout (varies by type)
resp = requests.get(
    f"{BASE_URL}/res.php", params=params,
    timeout=15,  # 15s per individual poll request
)
# Overall polling loop timeout: 30-120s depending on type

相关指南


把每一毫秒都用在刀刃上——试试 CaptchaAI 的类型自适应超时。

该文章已禁用评论。