Use Cases

解决日语和韩语网站上的验证码

先给结论:日韩站点的图片验证码不用自己训练模型——提交时把 language 设成 2,走 CaptchaAI 的图片/OCR 接口,日文、韩文、中文由同一条链路处理。

坑在字符集。按拉丁字母训练的本地 OCR 遇到假名和谚文只会输出问号。日语一张图可能混排 ひらがな、カタカナ 与日语汉字;韩语谚文(한글)由 24 个字母拼出约 11,000 个音节块,字符空间不是一个量级。

日本与韩国站点常见的验证码类型

地区 常见类型 字符集 处理方式
日本 假名/汉字图片验证码、reCAPTCHA 假名、日语汉字、拉丁 图片/OCR
韩国 谚文图片验证码、reCAPTCHA、自研滑块 谚文、拉丁 图片/OCR
两地通用 reCAPTCHA v2/v3(界面本地化) 基于 token reCAPTCHA

覆盖边界:CaptchaAI 支持 reCAPTCHA v2/v3、Turnstile、GeeTest(极验)v3、图片/OCR 与九宫格;hCaptcha 与 FunCaptcha 暂不支持,GeeTest v4 即将支持。自研滑块接入前先确认底层方案。

Python:识别日文与韩文图片验证码

流程只有四步:

  1. 把图片读成 base64。
  2. 带上 language=2 提交到 in.php
  3. 每几秒轮询 res.php
  4. 拿到文本回填表单提交。
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_japanese_captcha(image_path: str) -> str:
    """Solve a Japanese 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,          # CJK character support
        "json": 1,
    }, timeout=30).json()

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

    task_id = resp["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")


def solve_korean_captcha(image_path: str) -> str:
    """Solve a Korean hangul 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,
        "json": 1,
    }, timeout=30).json()

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

    task_id = resp["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")


def solve_captcha_from_session(session: requests.Session,
                                captcha_url: str,
                                language: int = 2) -> str:
    """Download and solve a CAPTCHA within a session context."""
    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": str(language),
        "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")


# --- Usage ---

# Japanese CAPTCHA
jp_text = solve_japanese_captcha("japanese_captcha.png")
print(f"Japanese CAPTCHA: {jp_text}")

# Korean CAPTCHA from a live session
session = requests.Session()
session.headers["Accept-Language"] = "ko-KR,ko;q=0.9"
session.get("https://example.kr/login")  # establish session
kr_text = solve_captcha_from_session(session, "https://example.kr/captcha/image")
print(f"Korean CAPTCHA: {kr_text}")
  • 复用会话:solve_captcha_from_session 直接用已建立的 requests.Session,验证码图片常带一次性参数,必须在同一会话里下载。
  • 对齐语言:Accept-Language 要与目标站点一致,否则拿到的是另一套页面,字符集也跟着变。

Node.js:同样的参数,换个写法

采集端在 Node.js 上时参数一致,换成 URLSearchParamsfetch 即可:

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 solveAsianCaptcha(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 solveFromUrl(captchaUrl, cookies = "") {
  const resp = await fetch(captchaUrl, {
    headers: { Cookie: cookies, "Accept-Language": "ja-JP,ja;q=0.9" },
  });
  const buffer = await resp.arrayBuffer();
  const imageB64 = Buffer.from(buffer).toString("base64");

  const body = new URLSearchParams({
    key: API_KEY, method: "base64", body: imageB64,
    language: "2", json: "1",
  });

  const submitResp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
  if (submitResp.status !== 1) throw new Error(`Submit: ${submitResp.request}`);

  const taskId = submitResp.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");
}

// Usage
const jpText = await solveAsianCaptcha("japanese_captcha.png");
console.log(`Japanese: ${jpText}`);

结果不对时先查这五项

  1. 假名认错:シ、ツ 这类形近字最容易翻车。确认请求带了 language=2,再提高分辨率。
  2. 韩文输出乱码:响应被按 Latin-1 解码,显式写 response.encoding = 'utf-8'
  3. 混排图整张失败:一张图混有多套字符集时照样只用 language=2,不要切图分多次提交。
  4. 艺术字准确率偏低:直接提交原图,压缩或二值化往往越处理越糊。
  5. 提交时会话已过期:先建好会话,拿到文本立刻回填。

跨境采集场景下怎么估成本

国内团队做日韩渠道的授权数据采集,典型节奏是白天低频、夜间集中补采,日站挂假名验证码,韩站挂谚文验证码。

CaptchaAI 按线程计费而不按次计费,这种波峰波谷正好合适。要估的是夜里的并发线程数,不是月调用量:

  • BASIC:$15/月,5 线程
  • STANDARD:$30/月,15 线程
  • ADVANCE:$90/月,50 线程

三档都不限识别次数,价格按美元计价。

合规提醒:先看目标站的 robots 协议与授权范围,再对照《数据安全法》和 PIPL 确认数据的存储与出境方式。

常见问题

日文图片验证码识别一般要多久?

图片/OCR 的服务上限是 <0.5 秒,端到端还要算上下载与轮询间隔。轮询间隔压到 2 秒会快不少。

日语汉字验证码和中文验证码要分开处理吗?

不用。language=2 覆盖整个 CJK 字符集,假名、日语汉字、中文、谚文共用一个参数,不必自己分语言。

韩国站点的自研滑块能识别吗?

看底层方案。GeeTest(极验)v3 走对应接口;自研滑块与 hCaptcha 暂不支持,先在自有测试环境确认类型。

相关文章

下一步

领取 CaptchaAI API Key,把示例里的图片路径换成自己的,先跑通一张日文验证码。

延伸阅读:

该文章已禁用评论。