Use Cases

Решение CAPTCHA на китайских сайтах с помощью CaptchaAI

На китайском сайте почти всегда встречается один из трёх типов проверки: картинка с иероглифами, слайдер-пазл 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 сессии. Порядок действий, который работает:

  1. Откройте страницу поиска в одной requests.Session() и сохраните cookie.
  2. Скачайте изображение капчи этой же сессией — отдельный запрос без cookie вернёт другую картинку, а не ту, которую ждёт сервер.
  3. Отправьте картинку в in.php с language=2 и получите ID задачи.
  4. Опрашивайте res.php раз в 5 секунд, пока вместо CAPCHA_NOT_READY не придёт результат.
  5. Отправьте форму с распознанным текстом в той же сессии.

Ключевой момент — шаги 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 или открытой выгрузки: у китайских госпорталов и крупных площадок альтернативная точка входа встречается чаще, чем кажется.

Куда двигаться дальше

Комментарии для этой статьи отключены.