Trường Hợp Sử Dụng

Giải CAPTCHA trên các trang web Trung Quốc bằng CaptchaAI

So với một CAPTCHA ảnh tiếng Anh, trang Trung Quốc chỉ đòi bạn đổi hai thứ: đặt language=2 cho ảnh chữ Hán, và dùng method=geetest cho câu đố trượt GeeTest v3. Phần còn lại giữ nguyên — gửi task tới in.php, polling res.php, dùng kết quả.

Cái khó nằm ở chỗ khác. Một đội sourcing ở TP.HCM crawl bảng giá nhà cung cấp trên sàn B2B Trung Quốc trước khi nhập hàng lên Shopee hay Tiki thường không hỏng ở khâu OCR, mà hỏng vì pipeline tải ảnh CAPTCHA bằng một session rồi gửi form bằng session khác.

Nhận diện loại CAPTCHA trước khi chọn solver

Chọn sai solver là lý do phổ biến nhất khiến task lỗi ngay ở bước gửi:

Loại CAPTCHA Hay xuất hiện ở Solver CaptchaAI
Ảnh chữ Hán Cổng nhà nước, CSDL học thuật Image/OCR (language=2)
Phép tính bằng tiếng Trung Form đăng ký Image/OCR
Câu đố trượt GeeTest v3 Baidu, Bilibili, nhiều nền tảng lớn GeeTest v3
Bấm chữ Hán theo thứ tự Màn hình xác minh dạng click Image/OCR (chế độ tọa độ)
reCAPTCHA v2 Bản quốc tế của site Trung Quốc reCAPTCHA v2

CaptchaAI không hỗ trợ hCaptcha và FunCaptcha (Arkose Labs); GeeTest v4 mới ở trạng thái sắp ra mắt, còn CAPTCHA riêng của Tencent thì chưa — kiểm tra ngay ở khâu khảo sát.

Bốn bước từ lúc gặp CAPTCHA đến lúc gửi form

  1. Tải CAPTCHA — ảnh hoặc cặp tham số GeeTest — bằng đúng session sẽ dùng để gửi form.
  2. Gửi task tới in.php với method tương ứng: base64 cho ảnh, geetest cho câu đố trượt; phản hồi trả về ID task.
  3. Polling res.php mỗi 5 giây cho tới khi trạng thái khác CAPCHA_NOT_READY.
  4. Đưa kết quả vào request gốc, vẫn trong session cũ và trước khi challenge hết hạn.

Bước 1 và bước 4 là nơi phần lớn tích hợp gãy: tải ảnh bằng một requests.get rời rạc rồi gửi form bằng session khác, server thấy mã đúng nhưng phiên sai và vẫn từ chối.

Đọc ảnh chữ Hán bằng Python với language=2

Ảnh CAPTCHA tiếng Trung thường là chuỗi bốn đến sáu chữ Hán; solver Image/OCR đọc được cả chữ giản thể lẫn phồn thể. Đoạn code sau gồm ba hàm: giải ảnh từ file, giải ảnh tải từ URL trong cùng session, và giải 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",
)

Hàm đầu tiên phù hợp khi thử nghiệm. Hàm thứ hai mới là thứ chạy trong production vì nó giữ nguyên cookie giữa lúc tải ảnh và lúc gửi form. Nếu được, lấy ảnh ở kích thước gốc thay vì bản đã thu nhỏ trong DOM.

Node.js: cùng luồng đó viết bằng fetch

Pipeline chạy Node.js thì không cần dựng thêm service Python chỉ để gọi solver.

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

Lưu ý language: "2" phải là chuỗi khi đi qua URLSearchParams; số vòng polling nên tỷ lệ với loại CAPTCHA — 24 vòng cho ảnh, 36 cho GeeTest v3.

Encoding và session: hai nhóm lỗi đặc thù của site tiếng Trung

Những lỗi dưới đây gần như không xuất hiện trên site tiếng Anh:

Vấn đề Nguyên nhân Cách xử lý
Chữ Hán trả về bị vỡ Sai encoding khi decode Decode bằng UTF-8, đừng để thư viện tự đoán
Form bị từ chối dù mã đúng Không giữ cookie phiên Một session cho cả tải ảnh và gửi form
GeeTest báo challenge hết hạn TTL rất ngắn Gửi task trong vài giây sau khi tải trang
OCR trả về sai ký tự Ảnh trộn chữ Hán với chữ Latin Đặt language=2
Tỷ lệ giải thấp ở cổng nhà nước Nét chữ render phức tạp Lấy ảnh độ phân giải cao hơn

Chi phí tính theo thread, không theo lượt giải

CaptchaAI tính tiền theo thread — số luồng giải đồng thời — với số lượt giải không giới hạn trong tháng. Câu hỏi ngân sách vì thế là "cùng lúc có bao nhiêu CAPTCHA đang chờ". Ảnh chữ Hán có trần dưới 0,5 giây, GeeTest v3 dưới 12 giây; tính theo mức chậm hơn, mỗi thread vẫn chạy khoảng 5 lượt mỗi phút. Crawler đơn lẻ thường đủ với BASIC ($15/tháng, 5 thread); job crawl danh mục theo lịch hợp với STANDARD ($30/tháng, 15 thread). Giá tính bằng USD.

Câu hỏi thường gặp

CaptchaAI có giải được CAPTCHA của Tencent không?

Không. Tencent CAPTCHA nằm ngoài danh sách hỗ trợ, cũng như hCaptcha và FunCaptcha (Arkose Labs); GeeTest v4 chỉ ở mức sắp ra mắt. Phần dùng được gồm Image/OCR, grid-image, GeeTest v3, reCAPTCHA v2/v3 và Cloudflare Turnstile.

Đặt language=2 rồi mà kết quả vẫn sai chữ thì kiểm tra gì?

Theo thứ tự: ảnh gửi đi là bản gốc hay bản đã bị CSS thu nhỏ, phản hồi có decode bằng UTF-8 không, ảnh có trộn chữ Hán với chữ Latin không. Phần lớn ca "OCR sai" thực ra là ảnh quá nhỏ hoặc kết quả hỏng khi decode.

Lấy gtchallenge của GeeTest v3 ở đâu?

gt tĩnh theo từng site, challenge đổi theo mỗi phiên; cả hai nằm trong mã nguồn trang hoặc phản hồi của endpoint khởi tạo CAPTCHA. Gửi kèm pageurl; kết quả trả về gồm challenge, validateseccode để đính vào form.

Đọc thêm

Bắt đầu với API key của bạn

Cách nhanh nhất để kiểm chứng: lấy API key CaptchaAI, dán vào API_KEY trong ví dụ Python và chạy thử với một ảnh thật.

Hướng dẫn liên quan:

Os comentários estão desativados para este artigo.