Use Cases

Résoudre les CAPTCHA sur les sites Web japonais et coréens

Un CAPTCHA image en 漢字 ou en 한글 s'envoie comme n'importe quelle autre image : in.php, method=base64, puis interrogation de res.php. Une seule chose change, le paramètre language=2, qui bascule le moteur OCR sur les jeux de caractères CJK. Sans lui, vous récupérez une chaîne latine approximative et le formulaire refuse la vérification.

Le vrai travail se joue ailleurs : garder la session ouverte pendant la résolution et ne pas casser l'encodage UTF-8 entre la réponse de l'API et le champ à remplir. Cette page traite les deux, avec du code Python et Node.js à copier tel quel.

Pourquoi l'OCR latin échoue sur ces jeux de caractères

Un site japonais peut mélanger trois systèmes d'écriture dans une même image, avec parfois quelques lettres latines. Un moteur entraîné sur l'alphabet latin n'est pas seulement imprécis : il n'a pas les classes de sortie correspondantes.

Système d'écriture Caractères Ce que cela implique
Hiragana (ひらがな) 46 de base Mots japonais natifs
Katakana (カタカナ) 46 de base Emprunts étrangers
Kanji (漢字) Plusieurs milliers Partagés avec le chinois ; sous-ensemble courant dans les CAPTCHA
Hangul (한글) 24 lettres, ~11 000 blocs Syllabes carrées, empilées
Mixte (JP) Hiragana + katakana + kanji + latin Le cas le plus difficile

Le hangul ajoute sa propre difficulté : les lettres sont empilées en blocs syllabiques, donc la segmentation ne peut pas se faire de gauche à droite comme sur du latin.

Ce que vous rencontrez côté japonais et côté coréen

Région Types courants Jeux de caractères Solveur CaptchaAI
Japon Image avec hiragana/katakana, reCAPTCHA v2/v3 Hiragana, katakana, kanji, latin Image/OCR
Corée Image avec hangul, reCAPTCHA v2/v3, curseurs propriétaires Hangul, latin Image/OCR
Les deux reCAPTCHA v2/v3 (interface localisée) Sans objet (basé sur un token) reCAPTCHA

Deux précisions avant la première ligne de code. CaptchaAI ne prend pas en charge hCaptcha ni FunCaptcha (Arkose Labs) : si la page en affiche un, rien de ce qui suit ne s'applique. Et un reCAPTCHA servi depuis un domaine .jp ou .kr reste un reCAPTCHA ordinaire : l'interface est traduite, pas le mécanisme du token.

Résoudre un CAPTCHA image japonais en Python

Le script couvre trois cas : une image sur disque, un CAPTCHA coréen, une image téléchargée dans une session requests ouverte. Le polling interroge res.php toutes les 5 secondes, 24 fois au maximum.

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

Résoudre un CAPTCHA japonais ou coréen en Node.js

La logique est identique : solveAsianCaptcha() lit un fichier local, solveFromUrl() télécharge l'image en réutilisant vos cookies et l'en-tête Accept-Language. La seconde sert le plus souvent : beaucoup de portails japonais ne servent l'image qu'à une session ouverte.

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

Scénario : une veille tarifaire pilotée depuis Lyon

Une équipe produit lyonnaise relève chaque semaine les prix publics de fournisseurs japonais et coréens, depuis des workers hébergés sur OVHcloud et sur la région AWS eu-west-3 (Paris). Les deux formulaires ciblés affichent un CAPTCHA image dès la troisième requête. Trois décisions ont réglé le problème, et aucune ne concerne l'OCR :

  • Le fuseau horaire. Les catalogues sont mis à jour en début de matinée locale, soit la nuit en Europe : programmer la collecte vers 2 h, heure de Paris, a nettement réduit le nombre de défis servis.
  • L'en-tête Accept-Language. ja-JP,ja;q=0.9 côté japonais, ko-KR,ko;q=0.9 côté coréen. Sinon, certains formulaires renvoient une variante anglaise aux règles différentes.
  • Le RGPD. Les logs contenaient des identifiants de session ; l'équipe ne conserve plus que l'URL, le statut HTTP et le temps de résolution.

Encodage : ne perdez pas les caractères en chemin

C'est le piège le plus fréquent, et il ne doit rien à la qualité de la résolution. L'API renvoie de l'UTF-8 ; si une couche du pipeline le relit en Latin-1, kanji et blocs hangul deviennent illisibles avant d'atteindre le formulaire.

  • Forcez response.encoding = 'utf-8' sur la page cible, pas seulement sur la réponse de l'API.
  • Vérifiez le charset du formulaire : absent, le serveur devine mal.
  • Journalisez la valeur en repr() : un terminal mal configuré affiche des points d'interrogation sur une chaîne correcte.

Dépannage

Problème Cause probable Correctif
Hiragana confondu avec katakana Formes visuellement proches Vérifiez que language=2 part bien à chaque requête
Hangul tronqué Réponse décodée en Latin-1 Forcez l'UTF-8 : response.encoding = 'utf-8'
Écritures mixtes en échec Plusieurs jeux dans la même image Envoyez l'image entière, sans recadrage
Précision faible sur du texte stylisé Image distordue ou trop petite Récupérez l'image à sa résolution native
Session expirée après la résolution Délai trop long avant l'envoi Ouvrez la session juste avant la demande d'image
CAPCHA_NOT_READY en boucle Polling trop agressif Gardez 5 secondes entre deux res.php

Combien de threads prévoir

Une image CJK occupe un thread de l'appel à in.php jusqu'à la réponse de res.php. Votre débit dépend donc des threads simultanés, pas d'un quota : chaque plan inclut des résolutions illimitées par thread, facturées en dollars US.

Volume Plan adapté Capacité
Quelques centaines d'images par semaine BASIC ($15/mois, 5 threads) 5 résolutions en parallèle
Collecte quotidienne sur plusieurs sites STANDARD ($30/mois, 15 threads) 15 résolutions en parallèle
Pipeline de scraping continu ADVANCE ($90/mois, 50 threads) 50 résolutions en parallèle

FAQ

Faut-il des paramètres différents pour le japonais et le coréen ?

Non. Les deux passent par language=2, qui couvre les caractères CJK. La seule différence est côté site : l'en-tête Accept-Language et les cookies de session.

CaptchaAI prend-il en charge hCaptcha sur les sites japonais ?

Non, hCaptcha n'est pas pris en charge, pas plus que FunCaptcha (Arkose Labs). Les types couverts ici : image/OCR, grilles d'images et reCAPTCHA v2/v3.

Pourquoi le hangul résolu s'affiche-t-il en caractères illisibles ?

Parce que la chaîne UTF-8 a été relue avec un autre encodage. Forcez l'UTF-8 partout et contrôlez la valeur en repr() : le plus souvent, la résolution était bonne et c'est l'affichage qui trompait.

Que faire si la session expire pendant la résolution ?

Réorganisez l'ordre des opérations : ouvrez la session, demandez l'image, résolvez, soumettez sans attendre. Si la fenêtre reste courte, relancez une session propre à chaque échec au lieu de réutiliser un cookie périmé.

Pour aller plus loin

Le chinois pose des problèmes voisins avec un autre jeu de caractères : voyez la résolution des CAPTCHA sur les sites chinois, le jeu de caractères des CAPTCHA image multilingues et les paramètres de localisation des CAPTCHA.

Prochaines étapes

Testez le script Python sur une image japonaise réelle : récupérez votre clé API CaptchaAI, lancez une résolution en language=2, puis mesurez votre temps de résolution médian sur une centaine d'images avant de choisir un plan.

Les commentaires sont désactivés pour cet article.