На китайском сайте почти всегда встречается один из трёх типов проверки: картинка с иероглифами, слайдер-пазл GeeTest v3 или привычная reCAPTCHA v2 на международной версии портала. Первые два CaptchaAI решает через Image/OCR с параметром language=2 и через метод geetest, третий — стандартным userrecaptcha. Всё остальное в этой статье про то, как не потерять сессию, кодировку и срок жизни challenge между получением задачи и отправкой формы.
Тема нужна не только тем, кто работает с китайским рынком напрямую. Команда в Алматы или Минске, которая собирает каталог поставщика или сверяет данные по открытому реестру, упирается ровно в те же проверки — и обычно на них же и останавливается, потому что западные материалы про reCAPTCHA здесь не помогают.
Какие проверки реально встречаются на китайских сайтах
| Тип проверки | Где встречается | Что использует CaptchaAI |
|---|---|---|
| Картинка с китайскими иероглифами | Госпорталы, академические базы | Image/OCR, language=2 |
| Арифметика на китайском | Формы регистрации | Image/OCR |
| Слайдер-пазл GeeTest v3 | Baidu, Bilibili и другие крупные платформы | GeeTest v3 |
| «Нажмите иероглифы по порядку» | Клик-капча на порядок символов | Image/OCR (координатный режим) |
| reCAPTCHA v2 | Международные версии китайских сайтов | reCAPTCHA v2 |
Отдельно стоит запомнить, чего в этом списке нет. Tencent CAPTCHA и GeeTest v4 CaptchaAI сейчас не решает: GeeTest v4 заявлен как «скоро», а не как доступный метод. Если целевая страница отдаёт именно их, задача решается не подбором сервиса, а поиском другой точки входа — мобильного API или официальной выгрузки.
Сценарий: сбор данных с китайского госпортала
Типичная задача выглядит так: нужно получить публичные записи из реестра, каждая страница поиска закрыта картинкой с четырьмя-пятью иероглифами, а картинка привязана к cookie сессии. Порядок действий, который работает:
- Откройте страницу поиска в одной
requests.Session()и сохраните cookie. - Скачайте изображение капчи этой же сессией — отдельный запрос без cookie вернёт другую картинку, а не ту, которую ждёт сервер.
- Отправьте картинку в
in.phpсlanguage=2и получите ID задачи. - Опрашивайте
res.phpраз в 5 секунд, пока вместоCAPCHA_NOT_READYне придёт результат. - Отправьте форму с распознанным текстом в той же сессии.
Ключевой момент — шаги 2 и 5 обязаны жить внутри одной сессии. Большая часть ситуаций «капча решена, но форма не принимается» объясняется именно тем, что изображение забирали одним клиентом, а форму отправляли другим.
Если объём небольшой — несколько сотен запросов в сутки — хватит тарифа BASIC ($15/мес, 5 потоков). Тарификация идёт по потокам, а не по количеству решений, поэтому месячные расходы считаются заранее: один поток — одна капча в работе, освободился — берёт следующую. Для непрерывного сбора каталога в несколько тысяч страниц в час имеет смысл смотреть на ADVANCE ($90/мес, 50 потоков).
Python: OCR китайских иероглифов и GeeTest v3
Картинка с китайским текстом требует явно указать набор символов: без language=2 распознавание уйдёт в латиницу и вернёт мусор. В примере ниже три функции — решение из файла, решение по URL с передачей cookie и отправка GeeTest v3.
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",
)
Обратите внимание на цикл опроса: CAPCHA_NOT_READY — не ошибка, а нормальное промежуточное состояние, его нужно пропускать и продолжать опрос. Любой другой ответ со status != 1 — уже реальная ошибка, и поднимать её наверх лучше сразу, а не молча повторять запрос.
Для GeeTest важен порядок: gt статичен для сайта и живёт долго, challenge генерируется под конкретную сессию и истекает за секунды. Извлекать challenge и отправлять задачу нужно подряд, без промежуточной очереди. Ответ содержит challenge, validate и seccode — все три значения уходят в форму.
JavaScript: тот же поток на Node.js
Если парсер уже написан на Node.js, логика повторяется один в один: та же пара in.php / res.php, тот же интервал опроса, та же обработка CAPCHA_NOT_READY.
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}`);
Что ломается чаще всего
| Симптом | Причина | Что делать |
|---|---|---|
| В результате вместо иероглифов «кракозябры» | Ответ декодируется не как UTF-8 | Явно задайте UTF-8 при чтении ответа и при записи в хранилище |
GeeTest возвращает ошибку по challenge |
Короткий срок жизни challenge |
Отправляйте задачу в течение нескольких секунд после получения параметра |
| OCR путает иероглифы с латиницей | Смешанный набор символов на картинке | Убедитесь, что передан language=2 |
| Сессия отклоняет форму после решения | Cookie не сохранены между запросами | Работайте одной сессией: и загрузка картинки, и отправка формы |
| Доля успешных решений ниже обычной | Мелкое или сильно сжатое изображение | Забирайте капчу в исходном разрешении, не масштабируйте |
| Запросы режет CDN | Гео-ограничения на стороне сайта | Прокси как нейтральный сетевой слой: без подходящего IP часть ресурсов просто недоступна |
Ещё одна практическая деталь: картинка обновляется при каждой перезагрузке страницы. Скачивайте её ровно один раз, решайте, отправляйте. Повторный GET за изображением обнуляет то значение, которое сервер держит в сессии, и корректно решённый текст перестаёт подходить.
Правовая рамка перед сбором данных
Технически решаемая проверка не делает данные разрешёнными к обработке. Если в выгрузку попадают персональные данные, для читателей из РФ действует 152-ФЗ «О персональных данных», для трансграничных проектов — требования уровня GDPR. Практическое правило простое: собирайте только то, что вы вправе обрабатывать и хранить, и не трогайте разделы, закрытые чужой авторизацией. Это ответственность на стороне читателя, а не обязательство со стороны CaptchaAI.
FAQ
Работает ли OCR с традиционными иероглифами, а не только с упрощёнными?
Да. Параметр language=2 включает распознавание китайских символов в обоих начертаниях — и упрощённом, и традиционном. Отдельный флаг для традиционного письма задавать не нужно.
Как достать gt и challenge со страницы Bilibili или Baidu?
Оба значения приходят в браузер до показа слайдера. Найдите их в исходнике страницы или в ответе инициализирующего AJAX-запроса: gt будет одинаковым от сессии к сессии, challenge — разным. Удобнее всего смотреть во вкладке Network, отфильтровав запросы по имени инициализирующего метода.
Нужны ли прокси для китайских сайтов?
Часто да, но по инфраструктурной причине: часть ресурсов ограничивает доступ по географии, и без подходящего IP запрос не дойдёт до формы вовсе. Прокси здесь — обычный сетевой слой, а не отдельная тактика.
Сколько потоков заказывать под регулярный сбор данных?
Считайте от параллельности, а не от общего числа страниц. Если парсер держит 5 одновременных запросов и на каждый приходится одна капча, хватает BASIC ($15/мес, 5 потоков). При 40–50 параллельных воркерах ориентируйтесь на ADVANCE ($90/мес, 50 потоков) — решения внутри тарифа отдельно не тарифицируются.
Что делать, если на сайте стоит Tencent CAPTCHA?
Этот тип CaptchaAI не поддерживает. Проверьте, нет ли у сервиса мобильного API или открытой выгрузки: у китайских госпорталов и крупных площадок альтернативная точка входа встречается чаще, чем кажется.