每小时稳定跑到 10,000 个验证码,靠的不是让单次识别变快,而是把足够多请求同时放在飞行中——这是并发数学题,不是速度题。
用 CaptchaAI 配合异步提交/轮询架构,普通云主机即可撑住。本文给出算法、可直接跑的 Python/Node.js 代码和调优参数。
先算清楚:需要多少并发
单个 reCAPTCHA v2 识别耗时约 15 秒(中位数):
| 处理方式 | 吞吐量 |
|---|---|
| 顺序处理 | 3,600 秒 ÷ 15 秒 = 每小时 240 次,离目标差了 40 多倍 |
| 并发处理(约 42 路任务同时在途) | 满足 10,000/hour 目标 |
核心逻辑:不是等 CaptchaAI 更快,而是让请求相互重叠,在同一个 15 秒窗口里跑完 42 个任务。并发数并非越高越好,上限见下方调优参数。
架构:四个组件
┌──────────┐ ┌────────────┐ ┌─────────────┐ ┌──────────┐
│ Task │────▶│ Submit │────▶│ CaptchaAI │────▶│ Result │
│ Queue │ │ Workers │ │ API │ │ Store │
│ (Redis) │ │ (async) │ │ │ │ (DB) │
└──────────┘ └────────────┘ └─────────────┘ └──────────┘
│ ▲
│ ┌──────────┐ │
└───▶│ Poll │────┘
│ Workers │
└──────────┘
四个组件:
| 组件 | 作用 |
|---|---|
| 任务队列 | 保存待处理任务的 sitekey 和 pageurl |
| 提交 worker | 并发把任务发给 CaptchaAI API |
| 轮询 worker | 按间隔查询结果 |
| 结果存储 | token 到达即落库 |
Python 异步管道实现
# high_throughput_solver.py
import os
import asyncio
import time
import aiohttp
API_KEY = os.environ.get("CAPTCHAAI_KEY", "YOUR_API_KEY")
BASE_URL = "https://ocr.captchaai.com"
MAX_CONCURRENT = 50 # Max simultaneous solves
POLL_INTERVAL = 5 # Seconds between polls
INITIAL_WAIT = 12 # Seconds before first poll
semaphore = asyncio.Semaphore(MAX_CONCURRENT)
stats = {"submitted": 0, "solved": 0, "failed": 0, "start": 0}
async def solve_one(session, sitekey, pageurl, task_num):
"""Submit and poll a single CAPTCHA."""
async with semaphore:
try:
# Submit
async with session.get(f"{BASE_URL}/in.php", params={
"key": API_KEY, "method": "userrecaptcha",
"googlekey": sitekey, "pageurl": pageurl, "json": "1",
}) as resp:
result = await resp.json(content_type=None)
if result.get("status") != 1:
stats["failed"] += 1
return None
stats["submitted"] += 1
task_id = result["request"]
# Wait before first poll
await asyncio.sleep(INITIAL_WAIT)
# Poll
for _ in range(25):
async with session.get(f"{BASE_URL}/res.php", params={
"key": API_KEY, "action": "get",
"id": task_id, "json": "1",
}) as resp:
poll_result = await resp.json(content_type=None)
if poll_result.get("status") == 1:
stats["solved"] += 1
return poll_result["request"]
if poll_result.get("request") != "CAPCHA_NOT_READY":
stats["failed"] += 1
return None
await asyncio.sleep(POLL_INTERVAL)
stats["failed"] += 1
return None
except Exception as e:
stats["failed"] += 1
return None
async def run_batch(tasks):
"""Process a batch of CAPTCHA tasks concurrently."""
connector = aiohttp.TCPConnector(
limit=MAX_CONCURRENT,
keepalive_timeout=60,
)
async with aiohttp.ClientSession(connector=connector) as session:
coros = [
solve_one(session, task["sitekey"], task["pageurl"], i)
for i, task in enumerate(tasks)
]
results = await asyncio.gather(*coros)
return results
async def main():
# Generate test tasks (replace with your task source)
tasks = [
{
"sitekey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"pageurl": "https://www.google.com/recaptcha/api2/demo",
}
for _ in range(100) # Start with 100 tasks
]
stats["start"] = time.time()
print(f"Processing {len(tasks)} tasks with {MAX_CONCURRENT} concurrent workers")
results = await run_batch(tasks)
elapsed = time.time() - stats["start"]
print(f"\nCompleted in {elapsed:.0f}s")
print(f"Submitted: {stats['submitted']}")
print(f"Solved: {stats['solved']}")
print(f"Failed: {stats['failed']}")
print(f"Throughput: {stats['solved'] / (elapsed / 3600):.0f} solves/hour")
asyncio.run(main())
JavaScript 并发方案
// high_throughput_solver.js
const axios = require('axios');
const https = require('https');
const API_KEY = process.env.CAPTCHAAI_KEY || 'YOUR_API_KEY';
const BASE = 'https://ocr.captchaai.com';
const MAX_CONCURRENT = 50;
const agent = new https.Agent({ keepAlive: true, maxSockets: MAX_CONCURRENT });
const api = axios.create({ baseURL: BASE, httpsAgent: agent, timeout: 30000 });
const stats = { submitted: 0, solved: 0, failed: 0 };
async function solveOne(sitekey, pageurl) {
try {
const submit = await api.get('/in.php', {
params: { key: API_KEY, method: 'userrecaptcha', googlekey: sitekey, pageurl, json: '1' },
});
if (submit.data.status !== 1) { stats.failed++; return null; }
stats.submitted++;
await new Promise(r => setTimeout(r, 12000));
for (let i = 0; i < 25; i++) {
const poll = await api.get('/res.php', {
params: { key: API_KEY, action: 'get', id: submit.data.request, json: '1' },
});
if (poll.data.status === 1) { stats.solved++; return poll.data.request; }
if (poll.data.request !== 'CAPCHA_NOT_READY') { stats.failed++; return null; }
await new Promise(r => setTimeout(r, 5000));
}
stats.failed++;
return null;
} catch { stats.failed++; return null; }
}
async function runWithConcurrency(tasks, limit) {
const results = [];
const executing = new Set();
for (const task of tasks) {
const p = solveOne(task.sitekey, task.pageurl).then(r => {
executing.delete(p);
return r;
});
executing.add(p);
results.push(p);
if (executing.size >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
(async () => {
const tasks = Array.from({ length: 100 }, () => ({
sitekey: '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
pageurl: 'https://www.google.com/recaptcha/api2/demo',
}));
const start = Date.now();
console.log(`Processing ${tasks.length} tasks, ${MAX_CONCURRENT} concurrent`);
await runWithConcurrency(tasks, MAX_CONCURRENT);
const elapsed = (Date.now() - start) / 1000;
console.log(`\nDone in ${elapsed.toFixed(0)}s`);
console.log(`Solved: ${stats.solved}, Failed: ${stats.failed}`);
console.log(`Throughput: ${(stats.solved / (elapsed / 3600)).toFixed(0)} solves/hour`);
agent.destroy();
})();
调优参数怎么选
| 档位 | 保守 | 均衡 | 激进 |
|---|---|---|---|
| 最大并发数 | 20 | 50 | 100 |
| 首次轮询等待 | 15 秒 | 12 秒 | 10 秒 |
| 轮询间隔 | 7 秒 | 5 秒 | 3 秒 |
| 最大轮询次数 | 30 | 25 | 20 |
| 预期吞吐量 | ~4,800 次/小时 | ~10,000 次/小时 | ~18,000 次/小时 |
从保守档起步,逐步调大
MAX_CONCURRENT,一旦吞吐量增长放缓或错误率上升,就停止加码。
实时监控这四个指标
| 指标 | 判断标准 |
|---|---|
| 每分钟识别数 | 目标 10,000/hour 时应稳定在 ~167 附近 |
| 错误率 | 控制在 5% 以内,一旦飙升立即调低并发 |
| 队列深度 | 持续增长说明 worker 不够,长期为空说明并发配置过高 |
| P90 识别耗时 | 如果明显变长,通常是触发了限流,而不是网络问题 |
常见故障排查
| 现象 | 原因 | 处理方式 |
|---|---|---|
| 吞吐量卡在 ~5,000/hr 上不去 | 并发数不够 | 把 MAX_CONCURRENT 提到 80–100 |
| 错误率 > 10% | 请求打满了 API,或代理不稳定 | 降低并发,检查代理健康状态 |
| 内存持续上涨 | 任务结果无限堆积 | 结果一到就处理,不要攒批缓冲 |
收到 ERROR_NO_SLOT_AVAILABLE |
CaptchaAI 侧队列已满 | 退避 5 秒后重试,不要立即重发 |
部署提醒
示例里的 pageurl 指向 Google 官方 demo 页,国内网络访问未必稳定,但只影响本地测试——识别请求始终直连 ocr.captchaai.com,与 Google 无关。pip install aiohttp 超时可换清华源:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple aiohttp。
常见问题
ADVANCE、PREMIUM 这类套餐的线程数够跑 10,000/hour 吗?
够。CaptchaAI 按线程数计费,单线程识别次数不设上限:ADVANCE($90/月,50 线程)峰值约 12,000/hour,不够时可直接升级 PREMIUM($170/月,100 线程)。
可以跨多台服务器一起跑吗?
可以。用共享队列(Redis、RabbitMQ)分发任务,多台机器各起一份 worker 脚本,独立拉取、提交、轮询,横向扩容即可提升吞吐量。
轮询很久拿不到结果,要不要继续等?
一直是 CAPCHA_NOT_READY 就正常等待;收到 ERROR_NO_SLOT_AVAILABLE 说明触发限流,应退避几秒再重试,而不是加快轮询频率。
下一步
搭建你自己的高吞吐验证码识别管道——注册并获取 CaptchaAI API Key。
相关指南: