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.9côté japonais,ko-KR,ko;q=0.9cô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
charsetdu 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.