超时到底该设多少秒?因验证码类型而异:图片验证码 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 的类型自适应超时。