国内站点的验证码基本只有三类:歪扭的汉字图片(含点选汉字)、中文算术题,以及登录页的 GeeTest(极验)v3 滑块。链路只有两条——前两类走图片/OCR,language 传 2;滑块走 method=geetest,提交 gt 与 challenge。reCAPTCHA v2 只出现在出海站点上。类型判断对了,代码是最省事的一环。
适用范围: 本文只覆盖你自有或已授权的采集与 QA 环境;采集范围请对照网络安全法、数据安全法、PIPL 和 robots 协议判断。
CaptchaAI 覆盖哪些中文验证码
正式支持 12 种类型:图片/OCR、九宫格、GeeTest v3、reCAPTCHA v2/v3 全系、Cloudflare Turnstile 与 Challenge、BLS;CaptchaFox、Friendly Captcha、Lemin 为测试版。网易易盾、腾讯防水墙、阿里云验证码不在覆盖范围内,hCaptcha 与 FunCaptcha 也不支持。GeeTest v4 目前只是“即将支持”,别拿 v3 的参数硬试。
中文网站的验证码类型怎么分
| 验证码类型 | 常见位置 | 识别方式 |
|---|---|---|
| 汉字图片、中文算术题 | 政务门户、期刊库 | 图片/OCR,带 language=2 |
| 点选汉字 | 风控较严的登录流程 | 图片/OCR 坐标模式 |
| GeeTest(极验)v3 滑块 | 社区、视频站登录页 | method=geetest |
| reCAPTCHA v2 | 出海站点 | reCAPTCHA v2 |
场景:中文期刊库的批量导出
最典型的是中文期刊库:批量导出检索结果时,每翻几页就弹一张汉字验证码。把取图、识别、回填封装成一个函数挂进翻页循环,只在出现验证码元素时触发。并发上限看线程数:单机脚本用 BASIC($15/月,5 线程)够用,多进程跑批一般上 ADVANCE($90/月,50 线程)。计费按线程算,单个线程当月的识别次数不限。
Python:OCR 与极验滑块的调用流程
三个函数骨架相同:POST 到 in.php 拿任务 ID,再每 5 秒轮询 res.php,收到 CAPCHA_NOT_READY 就继续等。图片先做 base64 编码,language 必须传 2,否则引擎按拉丁字符去猜。装依赖走清华 TUNA 镜像更快。
import requests
import base64
import time
API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
def solve_chinese_image_captcha(image_path: str) -> str:
"""Solve a Chinese character image CAPTCHA."""
with open(image_path, "rb") as f:
image_b64 = base64.b64encode(f.read()).decode()
resp = requests.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "base64",
"body": image_b64,
"language": 2, # 2 = Chinese characters supported
"json": 1,
}, timeout=30).json()
if resp.get("status") != 1:
raise RuntimeError(f"Submit failed: {resp.get('request')}")
task_id = resp["request"]
start = time.monotonic()
while time.monotonic() - start < 120:
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get",
"id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
return poll["request"]
raise RuntimeError(f"Solve failed: {poll.get('request')}")
raise RuntimeError("Timeout")
def solve_chinese_captcha_from_url(captcha_url: str, cookies: dict = None) -> str:
"""Download and solve a Chinese CAPTCHA from a URL."""
session = requests.Session()
if cookies:
session.cookies.update(cookies)
resp = session.get(captcha_url, timeout=15)
image_b64 = base64.b64encode(resp.content).decode()
submit = requests.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "base64",
"body": image_b64,
"language": 2,
"json": 1,
}, timeout=30).json()
if submit.get("status") != 1:
raise RuntimeError(f"Submit: {submit.get('request')}")
task_id = submit["request"]
for _ in range(24):
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
return poll["request"]
raise RuntimeError(f"Solve: {poll.get('request')}")
raise RuntimeError("Timeout")
# --- GeeTest on Chinese platforms ---
def solve_geetest_chinese(gt: str, challenge: str, pageurl: str) -> dict:
"""Solve GeeTest v3 commonly found on Baidu, Bilibili, etc."""
resp = requests.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "geetest",
"gt": gt,
"challenge": challenge,
"pageurl": pageurl,
"json": 1,
}, timeout=30).json()
if resp.get("status") != 1:
raise RuntimeError(f"Submit: {resp.get('request')}")
task_id = resp["request"]
for _ in range(36):
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
# GeeTest returns challenge, validate, seccode
return poll["request"]
raise RuntimeError(f"Solve: {poll.get('request')}")
raise RuntimeError("Timeout")
# Usage — Chinese government portal
text = solve_chinese_image_captcha("chinese_captcha.png")
print(f"Chinese CAPTCHA text: {text}")
# GeeTest on a Chinese platform
geetest_result = solve_geetest_chinese(
gt="b46d1900d0a894591f1561f8c35670a7",
challenge="dynamic_challenge_string",
pageurl="https://www.example.cn/login",
)
GeeTest 返回的不是单个 token,而是 challenge、validate、seccode 三个值,原样回填到校验请求里;其中 challenge 的有效期只有几十秒,取到后立刻提交。
Node.js:同一套提交与轮询
逻辑一致:URLSearchParams 组装表单体,fetch 提交,再定时轮询。图片任务留 24 轮,GeeTest 留 36 轮——滑块比纯 OCR 慢,轮次要给足。
const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
const fs = require("fs");
async function solveChineseImageCaptcha(imagePath) {
const imageB64 = fs.readFileSync(imagePath, "base64");
const body = new URLSearchParams({
key: API_KEY,
method: "base64",
body: imageB64,
language: "2",
json: "1",
});
const resp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
if (resp.status !== 1) throw new Error(`Submit: ${resp.request}`);
const taskId = resp.request;
for (let i = 0; i < 24; i++) {
await new Promise((r) => setTimeout(r, 5000));
const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
const poll = await (await fetch(url)).json();
if (poll.request === "CAPCHA_NOT_READY") continue;
if (poll.status === 1) return poll.request;
throw new Error(`Solve: ${poll.request}`);
}
throw new Error("Timeout");
}
async function solveGeeTest(gt, challenge, pageurl) {
const body = new URLSearchParams({
key: API_KEY,
method: "geetest",
gt,
challenge,
pageurl,
json: "1",
});
const resp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
if (resp.status !== 1) throw new Error(`Submit: ${resp.request}`);
const taskId = resp.request;
for (let i = 0; i < 36; i++) {
await new Promise((r) => setTimeout(r, 5000));
const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
const poll = await (await fetch(url)).json();
if (poll.request === "CAPCHA_NOT_READY") continue;
if (poll.status === 1) return poll.request;
throw new Error(`Solve: ${poll.request}`);
}
throw new Error("Timeout");
}
// Usage
const text = await solveChineseImageCaptcha("chinese_captcha.png");
console.log(`Chinese text: ${text}`);
识别失败时的排查顺序
- 参数:提交时带上
language=2,否则汉字结果里会混进拉丁字母。 - 编码:响应与日志统一按 UTF-8 解码,结果才不会变成乱码或问号。
- 时效:
challenge有效期只有几十秒,取到参数后立刻提交。 - 会话:取图与提交表单共用一个 session,否则识别对了站点也不认。
- 频率:被 CDN 限流就拉开请求间隔,重试带指数退避。
常见问题
汉字验证码识别不准,先查什么?
先看提交参数带没带 language=2——没带的话引擎按拉丁字符猜,混排汉字几乎必错。再看原图,缩放过的截图会拉低成功率,尽量抓原始尺寸,再配一层有限次重试。
站点用的是 GeeTest v4(极验 4 代)怎么办?
暂不支持,官方口径是即将支持,目前可用的是 v3。区分很简单:初始化参数里有 gt 和 challenge 的是 v3,走本文流程;v4 参数结构不同,提交只会拿到错误码。
网易易盾、腾讯防水墙的验证码能识别吗?
不能,这几家国产方案不在覆盖范围内,接项目前先确认目标站点用的是哪一家。简体和繁体汉字则都能识别,language 一样传 2。
相关文章
下一步
把两条链路接进采集脚本,领取 CaptchaAI API Key 即可跑第一批任务。
相关指南: