Use Cases

使用 CaptchaAI 解决中文网站上的验证码问题

国内站点的验证码基本只有三类:歪扭的汉字图片(含点选汉字)、中文算术题,以及登录页的 GeeTest(极验)v3 滑块。链路只有两条——前两类走图片/OCR,language 传 2;滑块走 method=geetest,提交 gtchallenge。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 与极验滑块的调用流程

三个函数骨架相同:POSTin.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,而是 challengevalidateseccode 三个值,原样回填到校验请求里;其中 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}`);

识别失败时的排查顺序

  1. 参数:提交时带上 language=2,否则汉字结果里会混进拉丁字母。
  2. 编码:响应与日志统一按 UTF-8 解码,结果才不会变成乱码或问号。
  3. 时效challenge 有效期只有几十秒,取到参数后立刻提交。
  4. 会话:取图与提交表单共用一个 session,否则识别对了站点也不认。
  5. 频率:被 CDN 限流就拉开请求间隔,重试带指数退避。

常见问题

汉字验证码识别不准,先查什么?

先看提交参数带没带 language=2——没带的话引擎按拉丁字符猜,混排汉字几乎必错。再看原图,缩放过的截图会拉低成功率,尽量抓原始尺寸,再配一层有限次重试。

站点用的是 GeeTest v4(极验 4 代)怎么办?

暂不支持,官方口径是即将支持,目前可用的是 v3。区分很简单:初始化参数里有 gtchallenge 的是 v3,走本文流程;v4 参数结构不同,提交只会拿到错误码。

网易易盾、腾讯防水墙的验证码能识别吗?

不能,这几家国产方案不在覆盖范围内,接项目前先确认目标站点用的是哪一家。简体和繁体汉字则都能识别,language 一样传 2。

相关文章

下一步

把两条链路接进采集脚本,领取 CaptchaAI API Key 即可跑第一批任务。

相关指南:

该文章已禁用评论。