Картинка-капча с иероглифами или хангылем решается тем же методом Image/OCR, что и обычная латинская, — меняется один параметр: language=2. Он переключает распознавание на набор символов CJK, после чего японская и корейская капча возвращают нормальный текст в UTF-8, а не строку из вопросительных знаков.
Проблема почти никогда не в самой капче. Она в двух местах: в локальном OCR, обученном на латинице, и в кодировке ответа, который клиент по привычке декодирует как Latin-1. Ниже — что именно ломается на японских и корейских сайтах, как это чинится и какой код достаточно скопировать, чтобы получить рабочий результат.
Почему латинский OCR не читает японский и корейский текст
Латиница — это 26 букв с устойчивой формой. Японская капча может смешивать в одной картинке хирагану, катакану и кандзи. Корейская использует хангыль — 24 базовые буквы, которые собираются в слоговые блоки, и таких блоков около 11 000.
Отсюда три следствия:
- Размер алфавита. Модель должна различать тысячи классов вместо десятков; локальный Tesseract без CJK-моделей здесь бесполезен.
- Похожие глифы. Хирагана и катакана содержат визуально близкие пары знаков — при искажении они путаются чаще, чем
0иO. - Байты, а не символы. Один символ CJK — это несколько байтов в UTF-8, и любая однобайтовая обработка ответа ломает текст.
Типы CAPTCHA по регионам
| Регион | Что встречается на практике | Наборы символов | Что решает CaptchaAI |
|---|---|---|---|
| Япония | Картинка с хираганой/катаканой, reCAPTCHA v2/v3 | Хирагана, катакана, кандзи, латиница | Image/OCR, reCAPTCHA |
| Корея | Картинка с хангылем, reCAPTCHA v2/v3, собственные слайдеры | Хангыль, латиница | Image/OCR, reCAPTCHA |
| Оба региона | reCAPTCHA v2/v3 с локализованным интерфейсом | Не применимо (токен) | reCAPTCHA |
Границы стоит оговорить сразу: hCaptcha и FunCaptcha на таких сайтах тоже попадаются, но CaptchaAI их не решает. GeeTest v4 значится как «скоро» и пока недоступен; GeeTest v3 поддерживается — см. решение GeeTest v3 через API. Рядом встречаются и бета-типы: CaptchaFox (beta), Friendly Captcha (beta), Lemin (beta).
Один параметр, который решает задачу: language=2
При отправке задачи в in.php методом base64 добавьте language=2. Это переключатель на CJK; отдельно указывать «японский» или «корейский» не нужно — система письма определяется автоматически. Дальше работает обычный поток: отправили задачу, получили ID задачи, опрашиваете res.php до готовности.
Второй обязательный шаг — кодировка. В requests явно задайте response.encoding = 'utf-8', а при записи в файл или БД проверьте, что вся цепочка (соединение, колонка, лог) тоже UTF-8. Большая часть жалоб «хангыль приходит битым» закрывается именно здесь.
Python: японская и корейская капча внутри живой сессии
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: картинка забирается тем же объектом Session, что и страница входа. Многие японские и корейские сайты привязывают изображение к cookie сессии, и картинка, скачанная отдельным «чистым» запросом, форме не соответствует.
JavaScript: тот же поток на Node.js
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}`);
Рекомендации по наборам символов
| Система письма | Объём | Что учитывать |
|---|---|---|
| Хирагана | 46 базовых знаков | Фонетическое письмо для исконно японских слов |
| Катакана | 46 базовых знаков | Фонетическое письмо для заимствований |
| Кандзи | Тысячи знаков | Общие с китайским; в капчах используется частотное подмножество |
| Хангыль | 24 буквы, ~11 000 слоговых блоков | Корейский фонетический алфавит |
| Смешанное письмо (Япония) | Хирагана + катакана + кандзи + латиница | Самый сложный случай: несколько систем в одной картинке |
Практический вывод: исходное разрешение важнее любой постобработки. Если в форме есть кнопка «обновить капчу», а картинка пришла сильно сжатой, дешевле запросить новую.
Сценарий: сбор цен с японского B2B-каталога
Типичная задача для команды из Москвы, Минска или Алматы — снимать цены и наличие с каталога японского или корейского поставщика для собственного прайс-листа. Форма входа отдаёт картинку с катаканой, сессия живёт около пятнадцати минут, а полный обход каталога занимает дольше.
Что здесь работает:
- Вход выполняется один раз, cookie сохраняются, капча решается только на логине — не на каждой странице.
- Заголовок
Accept-Languageвыставляется вja-JPилиko-KR: часть сайтов иначе отдаёт другую разметку и другой блок капчи. - При истечении сессии парсер повторяет вход, а не падает; тайм-аут на распознавание берётся с запасом, особенно на мобильных и нестабильных каналах.
- Собирается только то, что вы вправе обрабатывать: если в выгрузку попадают контактные данные сотрудников поставщика, применимы 152-ФЗ «О персональных данных» и GDPR-подобная дисциплина для трансграничных сценариев. Это вопрос вашей юридической проверки.
По стоимости: тарификация у CaptchaAI идёт по потокам, а не по числу решений. Для одного парсера с последовательным обходом хватает BASIC ($15/мес, 5 потоков). Если каталогов несколько и они обходятся параллельно, разумная ступень — STANDARD ($30/мес, 15 потоков) или ADVANCE ($90/мес, 50 потоков). Решения внутри тарифа отдельно не тарифицируются, поэтому расход предсказуем даже при неровной нагрузке — удобно, когда вы выставляете счёт в локальной валюте, а платите в долларах.
Поиск неисправностей
| Симптом | Причина | Что сделать |
|---|---|---|
| Хирагана распознаётся как катакана | Похожие по начертанию знаки при сильном искажении | Проверьте, что передан language=2, и запросите картинку в большем разрешении |
| Хангыль приходит «кракозябрами» | Ответ декодируется как Latin-1 | Задайте response.encoding = 'utf-8' и проверьте кодировку на всём пути до хранилища |
| Смешанное письмо распознаётся частично | В одной картинке несколько систем письма | Тот же language=2; при регулярных сбоях сохраняйте проблемные изображения для разбора |
| Низкая точность на «декоративном» шрифте | Сильная стилизация и шум | Берите изображение в исходном разрешении, без ресайза и повторного JPEG-сжатия |
| Сессия истекает после решения | Долгое распознавание сложных знаков | Открывайте сессию заранее и отправляйте форму сразу после получения ответа |
ERROR_WRONG_USER_KEY или пустой ответ |
Ключ не подставлен или обрезан | Убедитесь, что YOUR_API_KEY заменён, а ключ читается из переменной окружения |
Часто задаваемые вопросы
Нужны ли разные параметры для японской и корейской капчи?
Нет. В обоих случаях достаточно language=2 — система письма определяется автоматически, включая китайские иероглифы. Различается только контекст запроса к сайту: заголовок Accept-Language и cookie сессии.
Почему хангыль приходит в виде нечитаемых символов?
Это ошибка кодировки на вашей стороне, а не результат распознавания. Ответ приходит в UTF-8, и проблема возникает, если клиент, консоль, файл или колонка в БД работают в другой кодировке. Проверяйте всю цепочку целиком, а не только код парсера.
Отличается ли reCAPTCHA на японских и корейских сайтах?
Нет. reCAPTCHA основана на токене и не зависит от языка интерфейса: нужен sitekey со страницы, дальше поток стандартный — см. решение reCAPTCHA v2 через API. Локализованная надпись на виджете на процесс не влияет.
Сколько потоков нужно для парсера японского каталога?
Считайте по числу одновременных запросов на распознавание, а не по числу страниц. Последовательный парсер занимает один поток, поэтому BASIC ($15/мес, 5 потоков) закрывает и его, и запас на повторы. Параллельный обход нескольких доменов — повод перейти на STANDARD ($30/мес, 15 потоков).
Что делать, если вместо картинки стоит Cloudflare?
Тогда это другой тип задачи: Cloudflare Turnstile и Cloudflare Challenge решаются отдельными методами, без OCR. Порядок действий описан в руководстве по решению Cloudflare Turnstile через API.
С чего начать
Если вы ещё не подключали API, начните с быстрого старта CaptchaAI: получите ключ и первый решённый токен, затем вернитесь к коду выше и добавьте language=2.