Casos de Uso

Resolver CAPTCHA en sitios web chinos con CaptchaAI

Un scraper que entra en un portal chino se topa, en la práctica, con tres muros: una imagen de caracteres chinos que hay que transcribir, el deslizante de GeeTest v3, o un reCAPTCHA v2 en la versión internacional del sitio. Los tres se resuelven con el mismo par de endpoints de CaptchaAI (in.php para enviar, res.php para consultar el resultado); lo único que cambia es el method y, en el OCR, el idioma que declaras.

Qué CAPTCHA vas a encontrar y con qué solver se resuelve

Tipo de CAPTCHA Dónde aparece Solver de CaptchaAI
Imagen con caracteres chinos Portales gubernamentales, bases académicas Image/OCR con language=2
Operación aritmética en chino Formularios de registro Image/OCR
GeeTest v3 (deslizante) Baidu, Bilibili y plataformas grandes GeeTest v3
Clic sobre caracteres en orden "Haz clic en los caracteres en orden" Image/OCR (modo coordenadas)
reCAPTCHA v2 Versiones internacionales de sitios chinos reCAPTCHA v2

Dos precisiones de alcance: GeeTest v4 aún no está disponible (figura como próximamente) y hCaptcha y FunCaptcha (Arkose Labs) no son compatibles. En velocidad, el OCR de imagen se resuelve en menos de 0,5 s, GeeTest v3 por debajo de 12 s y reCAPTCHA v2 por debajo de 60 s, con una alta tasa de éxito en los tipos compatibles; esa diferencia manda al dimensionar threads.

Python: OCR de caracteres chinos y GeeTest v3

El punto que más gente confunde es el idioma. Para que el motor reconozca caracteres chinos hay que enviar language=2; sin él, el OCR lee los glifos como texto latino y devuelve cadenas sin sentido.

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",
)

El bucle de sondeo espera 5 segundos entre consultas y trata CAPCHA_NOT_READY como "sigue trabajando", no como error. Consultar cada 200 ms no acelera nada.

JavaScript: el mismo flujo desde Node.js

Si tu recolector vive en Node.js la lógica es idéntica y no necesitas librerías extra más allá de fetch y URLSearchParams.

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}`);

Codificación y sesiones: donde falla de verdad

Los fallos en sitios chinos casi nunca vienen del solver, vienen del transporte.

Síntoma Causa habitual Corrección
Caracteres ilegibles en la respuesta Charset mal negociado Decodifica como UTF-8 de forma explícita
El OCR mezcla latín y chino Falta el parámetro de idioma Envía language=2
challenge de GeeTest caducado TTL muy corto Extrae gt y challenge y envíalos en segundos
Sesión rechazada tras resolver Cookies no persistidas Usa la misma sesión para la imagen y el envío
Tasa de resolución baja en portales oficiales Glifos complejos en imágenes pequeñas Captura la imagen a mayor resolución
Bloqueos del CDN Concurrencia excesiva Espacia las peticiones desde una salida de red autorizada

Nota para equipos en España y Latinoamérica: si el pipeline recoge datos personales, aplican el RGPD y la LOPDGDD (o la LFPDPPP en México). Respeta los términos de servicio del sitio.

Cuántos threads necesitas

CaptchaAI factura por thread concurrente, no por CAPTCHA resuelto, con resoluciones ilimitadas dentro de esos threads. Para unos pocos portales chinos basta el plan BASIC ($15/mes, 5 threads); con decenas de sesiones en paralelo, STANDARD ($30/mes, 15 threads) o ADVANCE ($90/mes, 50 threads). Al ser un importe fijo en USD, es fácil de presupuestar.

Si es tu primera integración, monta primero el flujo con una imagen guardada en disco y solo después conéctalo al sitio real: así separas los errores de tu código de los del portal.

Preguntas frecuentes

¿Qué valor de language uso para chino simplificado y tradicional?

language=2 cubre ambos: activa el reconocimiento de caracteres chinos en Image/OCR.

¿Por qué el OCR me devuelve letras latinas en lugar de caracteres?

Casi siempre por enviar la tarea sin language=2, o por decodificar la respuesta con un charset distinto de UTF-8.

¿Puedo resolver el CAPTCHA en un servidor y enviar el formulario desde otro?

Sí, siempre que las cookies de sesión viajen contigo. El sitio asocia la imagen a la sesión que la pidió.

¿CaptchaAI resuelve el CAPTCHA propietario de Tencent?

No como tipo con soporte propio. Si el widget se presenta como imagen de caracteres, la vía es Image/OCR; si por debajo usa GeeTest v3, se resuelve como GeeTest v3.

Artículos relacionados

Empieza ahora

Crea tu cuenta en captchaai.com y lanza tu primera tarea con language=2 sobre un CAPTCHA de caracteres chinos.

Guías relacionadas:

Los comentarios están deshabilitados para este artículo.