Deux mécanismes couvrent la quasi-totalité des CAPTCHA d'un site chinois : l'OCR d'images de caractères, envoyée avec language=2, et le puzzle coulissant GeeTest v3. Le reste n'est pas de la résolution mais de la plomberie : encodage UTF-8, cookies conservés, adresse IP acceptée par le portail. Voici les deux appels d'API, puis les réglages qui font échouer un formulaire malgré un défi résolu.
Les défis CAPTCHA sur un site chinois
Une même session peut demander une image de caractères à l'inscription, un GeeTest v3 à la connexion, puis un reCAPTCHA v2 côté international.
| Type de CAPTCHA | Où il apparaît | Méthode CaptchaAI |
|---|---|---|
| Image de caractères chinois | Portails administratifs, bases universitaires | Image/OCR (language=chi_sim) |
| Calcul arithmétique en chinois | Formulaires d'inscription | Image/OCR |
| Puzzle coulissant GeeTest v3 | Baidu, Bilibili, grandes plateformes | GeeTest v3 |
| Clic sur les caractères dans l'ordre | Connexion, achats | Image/OCR (mode coordonnées) |
| reCAPTCHA v2 | Versions internationales des sites chinois | reCAPTCHA v2 |
Les vérifications maison, comme celle de Tencent, n'ont pas de méthode dédiée : passez par l'OCR si le défi se réduit à du texte. hCaptcha et FunCaptcha ne sont pas pris en charge ; GeeTest v4 est annoncé comme à venir.
Préparer la session avant le premier appel d'API
Ces réglages ne relèvent pas de la résolution, mais ils décident du sort du formulaire.
| Point de friction | Ce qu'il faut faire |
|---|---|
| Caractères illisibles | Forcer l'UTF-8 sur la requête et la réponse |
| Cookies de session | Récupérer l'image dans la session qui enverra le formulaire |
gt et challenge calculés en JavaScript |
Les extraire du code de la page ou de l'appel d'API |
| Limitation de débit des CDN chinois | Répartir le trafic sur des proxys résidentiels chinois |
| Image régénérée à chaque chargement | Télécharger une fois, résoudre, envoyer |
Python : lire une image de caractères chinois
Les images affichent des consignes du type 请输入验证码 (« saisissez le code de vérification ») : language=2 bascule l'OCR sur les jeux de caractères chinois. Le script couvre l'image sur disque, l'image en session et le 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",
)
L'intervalle d'interrogation de 5 secondes reste prudent. Le GeeTest renvoie challenge, validate et seccode à recopier dans le formulaire.
JavaScript : le même flux côté Node.js
Sous Node.js, la logique ne change pas. Gardez le même client HTTP, donc les mêmes cookies, pour la page, l'image et l'envoi.
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}`);
Dépannage
| Symptôme | Cause probable | Correctif |
|---|---|---|
| Caractères tronqués dans le résultat | Réponse décodée en latin-1 | Décoder explicitement en UTF-8 |
challenge GeeTest refusé |
Durée de vie très courte | L'extraire et l'envoyer aussitôt, jamais depuis un cache |
| Texte reconnu incohérent | Caractères chinois et latins mélangés | Envoyer language=2 sur chaque tâche |
| Formulaire rejeté après résolution | Cookies perdus entre deux clients | Réutiliser une session unique |
| Réussite faible sur les portails publics | Rendu des caractères dégradé | Capturer l'image à sa résolution native |
Scénario : surveiller un catalogue fournisseur depuis l'Europe
Une équipe e-commerce lyonnaise relève chaque matin les fiches de ses fournisseurs sur une place de marché chinoise. Le worker tourne sur une instance OVHcloud, et le portail affiche une image de caractères dès quelques dizaines de pages par minute. Deux ajustements suffisent : router la collecte via des proxys résidentiels chinois, et résoudre en parallèle. La facturation CaptchaAI repose sur les threads simultanés : BASIC ($15/mois, 5 threads) absorbe un relevé matinal, ADVANCE ($90/mois, 50 threads) une surveillance continue. Ces pages sont publiques, mais ne conservez pas les données personnelles croisées au passage : la finalité de la collecte relève du RGPD.
FAQ
Pourquoi le texte chinois revient-il en caractères illisibles ?
Parce qu'un maillon de la chaîne a décodé la réponse en latin-1. Fixez l'UTF-8 sur le client HTTP, le stockage et les logs : un résultat correct peut devenir illisible au seul affichage.
CaptchaAI prend-il en charge hCaptcha ou les CAPTCHA propriétaires chinois ?
Non, ni hCaptcha ni FunCaptcha, et les systèmes maison des plateformes chinoises n'ont pas de méthode dédiée. Sont couverts : Image/OCR, grilles d'images, GeeTest v3, reCAPTCHA v2 et v3, Cloudflare Turnstile et Challenge, plus CaptchaFox (bêta), Friendly Captcha (bêta) et Lemin (bêta).
GeeTest v4 fonctionne-t-il sur les plateformes qui ont migré ?
Pas encore : GeeTest v4 est annoncé comme à venir. Le script chargé par la page expose sa version ; sur une v4, method=geetest ne convient pas.
Articles connexes
- Déclencher le callback reCAPTCHA v2 via l'API
- Résoudre les CAPTCHA en Ruby
- Résoudre les CAPTCHA en Scala
Prochaines étapes
Avant d'industrialiser, testez sur un seul défi : récupérez votre clé API CaptchaAI et envoyez une image en base64.
Guides associés :