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

Giải CAPTCHA trên các trang web của Nhật Bản và Hàn Quốc

Gửi ảnh CAPTCHA tiếng Nhật hoặc tiếng Hàn tới OCR mà nhận về chuỗi vô nghĩa, nguyên nhân gần như luôn nằm ở một tham số: language. Đặt language=2 trong request tới in.php, CaptchaAI dùng mô hình cho bộ ký tự CJK thay vì bảng chữ Latin, và kết quả từ res.php là đúng chuỗi hiragana, katakana, kanji hay hangul trên ảnh.

Vì sao OCR Latin hỏng trên chữ Nhật và chữ Hàn

Mô hình OCR mặc định chỉ học vài chục glyph Latin. Chữ CJK phá vỡ giả định đó: số ký tự lớn gấp hàng trăm lần và một ảnh tiếng Nhật có thể trộn ba hệ chữ.

Hệ chữ viết Quy mô ký tự Điều cần lưu ý
Hiragana (ひらがな) 46 ký tự Từ thuần Nhật; nét tròn
Katakana (カタカナ) 46 ký tự Từ vay mượn; nét góc cạnh
Kanji (漢字) Hàng nghìn CAPTCHA lấy tập phổ thông
Hangul (한글) 24 chữ cái, ~11.000 khối Cắt mép khối là sai cả cụm
Trộn hệ (Nhật) Nhiều hệ trong một ảnh Trường hợp khó nhất

CAPTCHA thường gặp trên site Nhật và site Hàn

Thị trường Loại phổ biến Bộ ký tự Solver CaptchaAI
Nhật Bản Ảnh hiragana/katakana, reCAPTCHA v2/v3 Hiragana, katakana, kanji Image/OCR
Hàn Quốc Ảnh hangul, reCAPTCHA, thanh trượt tự xây Hangul, Latin Image/OCR
Cả hai reCAPTCHA v2/v3 giao diện bản địa hóa Không áp dụng (dạng token) reCAPTCHA

Lưu ý phạm vi hỗ trợ: CaptchaAI chưa hỗ trợ hCaptcha và FunCaptcha, GeeTest v4 mới ở mức sắp ra mắt. Với reCAPTCHA, giao diện tiếng Nhật hay tiếng Hàn không đổi cách xử lý; bộ ký tự chỉ là vấn đề của CAPTCHA hình ảnh.

Giải CAPTCHA hình ảnh tiếng Nhật bằng Python

Code dưới đây mã hóa ảnh sang base64, gửi task với language=2, rồi polling res.php mỗi 5 giây. Hàm cuối tải ảnh ngay trong Session đang mở để không mất cookie phiên lúc chờ.

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

Kết quả là chuỗi ký tự đã nhận dạng, điền thẳng vào ô nhập của form. CAPCHA_NOT_READY là trạng thái bình thường, cứ polling tiếp.

Giải CAPTCHA Nhật và Hàn bằng Node.js

Bản Node.js theo đúng luồng đó; hàm thứ hai dùng khi ảnh chỉ tải được kèm cookie phiên và header Accept-Language gốc.

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

Tình huống thực tế: theo dõi giá hàng Nhật, Hàn từ TP.HCM

Mô hình quen thuộc ở Việt Nam: một agency đối chiếu giá hàng nhập từ Nhật, Hàn với giá niêm yết trên Shopee, Lazada, Tiki. Nguồn trong nước không chặn, còn cổng đại lý Nhật và Hàn dựng CAPTCHA hình ảnh ngay ở trang đăng nhập.

Tách riêng một bước lo giải CAPTCHA, đặt language=2 cho mọi ảnh CJK và lưu id task kèm chuỗi trả về để đối soát khi số liệu lệch. CaptchaAI tính theo thread chứ không theo lượt giải, nên thứ cần ước lượng là số CAPTCHA giải đồng thời: job chạy đêm nằm gọn trong BASIC ($15/tháng, 5 thread), thêm nguồn thì lên STANDARD ($30/tháng, 15 thread). Chỉ lấy dữ liệu công khai và giữ log tối thiểu theo tinh thần Nghị định 13/2023/NĐ-CP.

Lỗi hay gặp và cách xử lý

Vấn đề Nguyên nhân Cách xử lý
Hiragana bị đọc thành katakana Nhiều cặp glyph gần giống Đặt language=2
Chuỗi hangul bị vỡ ký tự Phản hồi giải mã theo Latin-1 Ép UTF-8: response.encoding = 'utf-8'
Ảnh trộn hệ chữ cho kết quả sai Nhiều bộ ký tự trong một ảnh Giữ ảnh gốc, không nén lại
Độ chính xác thấp trên chữ cách điệu Ký tự bị bóp méo, nhiễu Lấy độ phân giải cao nhất
Phiên hết hạn sau khi giải Thời gian giải dài Mở phiên sát lúc lấy ảnh

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

CaptchaAI có giải được hCaptcha trên site Nhật và Hàn không?

Không. hCaptcha và FunCaptcha không nằm trong các loại được hỗ trợ. Phần lớn form tại hai thị trường này vẫn là CAPTCHA hình ảnh hoặc reCAPTCHA.

Giải CAPTCHA ký tự CJK có tốn thêm phí không?

Không. Giá tính theo số thread giải đồng thời, không phụ thuộc loại CAPTCHA, và mỗi thread không giới hạn số lượt giải trong tháng. Chọn gói theo mức song song: BASIC ($15/tháng, 5 thread) cho job nhỏ, ADVANCE ($90/tháng, 50 thread) cho pipeline nhiều nguồn.

Chuỗi hangul in ra terminal bị hỏng font thì làm gì?

Đó là lỗi mã hóa phía bạn, không phải kết quả sai. Ép response.encoding = 'utf-8' trước khi đọc .text và kiểm tra terminal, file log có dùng UTF-8 không.

Có nên tiền xử lý ảnh trước khi gửi không?

Thường là không. Hãy gửi nguyên ảnh ở độ phân giải gốc; resize hay nén lại dễ làm mất chi tiết nét — thứ phân biệt các ký tự CJK gần giống nhau.

Bài viết liên quan

Bước tiếp theo

Pipeline đã chạy ổn với CAPTCHA Latin thì phần còn thiếu chỉ là language=2 và mã hóa UTF-8. Lấy API key CaptchaAI và chạy thử đoạn Python ở trên với một ảnh thật.

Đọc thêm:

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