先给结论:日韩站点的图片验证码不用自己训练模型——提交时把 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:识别日文与韩文图片验证码
流程只有四步:
- 把图片读成 base64。
- 带上
language=2提交到in.php。 - 每几秒轮询
res.php。 - 拿到文本回填表单提交。
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 上时参数一致,换成 URLSearchParams 与 fetch 即可:
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}`);
结果不对时先查这五项
- 假名认错:シ、ツ 这类形近字最容易翻车。确认请求带了
language=2,再提高分辨率。 - 韩文输出乱码:响应被按 Latin-1 解码,显式写
response.encoding = 'utf-8'。 - 混排图整张失败:一张图混有多套字符集时照样只用
language=2,不要切图分多次提交。 - 艺术字准确率偏低:直接提交原图,压缩或二值化往往越处理越糊。
- 提交时会话已过期:先建好会话,拿到文本立刻回填。
跨境采集场景下怎么估成本
国内团队做日韩渠道的授权数据采集,典型节奏是白天低频、夜间集中补采,日站挂假名验证码,韩站挂谚文验证码。
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,把示例里的图片路径换成自己的,先跑通一张日文验证码。
延伸阅读: