Certains sites utilisent reCAPTCHA sur leur page de connexion et Turnstile sur leur page de paiement. Autres tests A/B entre fournisseurs — la même URL affiche reCAPTCHA pour un visiteur et Turnstile pour un autre. Le codage en dur d'une seule méthode de solveur signifie que votre automatisation s'arrête chaque fois qu'elle rencontre l'autre type.
Pourquoi les sites utilisent plusieurs fournisseurs CAPTCHA
| Scénario | Comment cela apparaît |
|---|---|
| Différentes pages, différents fournisseurs | Connexion = reCAPTCHA, paiement = Tourniquet |
| Fournisseurs de tests A/B | La même page affiche aléatoirement l'un ou l'autre type |
| Migration en cours | Les anciennes pages ont reCAPTCHA, les nouvelles pages ont Turnstile |
| Repli en cas d'échec | Le fournisseur principal échoue. → revient au fournisseur secondaire. |
| Variation régionale | reCAPTCHA pour les visiteurs américains, Turnstile pour l'UE (GDPR) |
Python : détection et résolution automatiques
import requests
import time
import re
from dataclasses import dataclass
API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
@dataclass
class CaptchaInfo:
provider: str # "recaptcha" or "turnstile"
method: str # API method name
sitekey: str
pageurl: str
response_field: str # Form field name for the token
def detect_captcha_type(html, pageurl):
"""
Detect which CAPTCHA provider is on the page.
Returns CaptchaInfo or None.
"""
# Check for Turnstile
turnstile_match = re.search(
r'class=["\'][^"\']*cf-turnstile[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)["\']',
html,
)
if not turnstile_match:
turnstile_match = re.search(
r'data-sitekey=["\']([^"\']+)["\'][^>]*class=["\'][^"\']*cf-turnstile',
html,
)
if turnstile_match:
return CaptchaInfo(
provider="turnstile",
method="turnstile",
sitekey=turnstile_match.group(1),
pageurl=pageurl,
response_field="cf-turnstile-response",
)
# Check for reCAPTCHA
recaptcha_match = re.search(
r'class=["\'][^"\']*g-recaptcha[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)["\']',
html,
)
if not recaptcha_match:
recaptcha_match = re.search(
r'data-sitekey=["\']([^"\']+)["\'][^>]*class=["\'][^"\']*g-recaptcha',
html,
)
# Also check for script-rendered reCAPTCHA
if not recaptcha_match:
recaptcha_match = re.search(
r'grecaptcha\.render\([^,]+,\s*\{[^}]*["\']sitekey["\']\s*:\s*["\']([^"\']+)["\']',
html,
)
if recaptcha_match:
return CaptchaInfo(
provider="recaptcha",
method="userrecaptcha",
sitekey=recaptcha_match.group(1),
pageurl=pageurl,
response_field="g-recaptcha-response",
)
return None
def solve_captcha(info):
"""Solve any detected CAPTCHA type via CaptchaAI."""
params = {
"key": API_KEY,
"method": info.method,
"json": 1,
}
if info.method == "userrecaptcha":
params["googlekey"] = info.sitekey
params["pageurl"] = info.pageurl
elif info.method == "turnstile":
params["sitekey"] = info.sitekey
params["pageurl"] = info.pageurl
resp = requests.post(SUBMIT_URL, data=params, timeout=30).json()
if resp.get("status") != 1:
raise RuntimeError(f"Submit failed: {resp.get('request')}")
task_id = resp["request"]
for _ in range(60):
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 process_page(session, url):
"""Fetch page, detect CAPTCHA type, solve, and return form-ready data."""
response = session.get(url)
captcha_info = detect_captcha_type(response.text, url)
if not captcha_info:
print(f"No CAPTCHA detected on {url}")
return None
print(f"Detected {captcha_info.provider} on {url}")
print(f" Sitekey: {captcha_info.sitekey[:30]}...")
token = solve_captcha(captcha_info)
print(f" Solved: {token[:30]}...")
return {
"provider": captcha_info.provider,
"response_field": captcha_info.response_field,
"token": token,
}
# Usage: Handle multiple pages with different providers
session = requests.Session()
pages = [
"https://example.com/login", # Might have reCAPTCHA
"https://example.com/checkout", # Might have Turnstile
]
for url in pages:
result = process_page(session, url)
if result:
form_data = {result["response_field"]: result["token"]}
# Add other form fields...
# session.post(url, data=form_data)
JavaScript : détection dynamique de CAPTCHA
const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
function detectCaptchaType(html, pageurl) {
// Turnstile
const turnstileMatch = html.match(/cf-turnstile[^>]*data-sitekey=["']([^"']+)["']/);
if (turnstileMatch) {
return { provider: "turnstile", method: "turnstile", sitekey: turnstileMatch[1], pageurl, field: "cf-turnstile-response" };
}
// reCAPTCHA
const recaptchaMatch = html.match(/g-recaptcha[^>]*data-sitekey=["']([^"']+)["']/);
if (recaptchaMatch) {
return { provider: "recaptcha", method: "userrecaptcha", sitekey: recaptchaMatch[1], pageurl, field: "g-recaptcha-response" };
}
// Script-rendered reCAPTCHA
const scriptMatch = html.match(/sitekey["']\s*:\s*["']([^"']+)["']/);
if (scriptMatch) {
return { provider: "recaptcha", method: "userrecaptcha", sitekey: scriptMatch[1], pageurl, field: "g-recaptcha-response" };
}
return null;
}
async function solveCaptcha(info) {
const body = new URLSearchParams({ key: API_KEY, method: info.method, json: "1" });
if (info.method === "userrecaptcha") { body.set("googlekey", info.sitekey); body.set("pageurl", info.pageurl); }
else if (info.method === "turnstile") { body.set("sitekey", info.sitekey); body.set("pageurl", info.pageurl); }
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 < 60; 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 processPage(url) {
const response = await fetch(url);
const html = await response.text();
const info = detectCaptchaType(html, url);
if (!info) { console.log(`No CAPTCHA on ${url}`); return null; }
console.log(`${info.provider} detected on ${url}`);
const token = await solveCaptcha(info);
return { provider: info.provider, field: info.field, token };
}
// Usage
const pages = ["https://example.com/login", "https://example.com/checkout"];
for (const url of pages) {
const result = await processPage(url);
if (result) {
console.log(`Solved ${result.provider}: ${result.token.substring(0, 30)}...`);
}
}
Référence de détection des fournisseurs
| Fournisseur | Marqueur HTML | URL du script | Champ de réponse |
|---|---|---|---|
| reCAPTCHA v2 | class="g-recaptcha" |
google.com/recaptcha/api.js |
g-recaptcha-response |
| Cloudflare Turnstile | class="cf-turnstile" |
challenges.cloudflare.com/turnstile |
cf-turnstile-response |
| hCaptcha | class="h-captcha" |
js.hcaptcha.com/1/api.js |
h-captcha-response |
Dépannage
| Problème | Parce que | Corriger |
|---|---|---|
| Mauvais type de CAPTCHA détecté | Regex correspond au mauvais élément | Vérifiez d'abord les noms de classe spécifiques au fournisseur, pas seulement data-sitekey |
| Jeton rejeté après une résolution correcte | Nom de champ de réponse incorrect utilisé | Faire correspondre le nom du champ au fournisseur : g-recaptcha-response vs cf-turnstile-response |
| Le type CAPTCHA change entre les visites | Test A/B ou sélection géo-basée | Détectez toujours de manière dynamique ; ne jamais coder en dur le fournisseur |
| Les deux fournisseurs détectés sur une seule page | L'un d'entre eux peut être caché/inactive | Vérifiez la visibilité des éléments : résolvez uniquement le CAPTCHA visible |
| La détection échoue pour les CAPTCHA rendus par script | Aucun marqueur HTML dans la source | Recherchez les appels grecaptcha.render() ou turnstile.render() dans les scripts |
FAQ
Une page peut-elle utiliser à la fois reCAPTCHA et Turnstile simultanément ?
Rarement sur le même formulaire, mais cela se produit dans différentes parties d'un site. Si les deux apparaissent sur une seule page, généralement un seul est actif – vérifiez quel widget est visible et possède un data-sitekey non vide.
CaptchaAI utilise-t-il la même API pour les deux fournisseurs ?
Les mêmes points de terminaison (in.php / res.php) mais des valeurs method différentes. reCAPTCHA utilise method=userrecaptcha avec googlekey, tandis que Turnstile utilise method=turnstile avec sitekey. Le modèle de détection automatique gère ce mappage.
Comment gérer le basculement du fournisseur ?
Si un site passe de reCAPTCHA à Turnstile en cours de session (par exemple, après l'échec du chargement de reCAPTCHA), détectez à nouveau le type de CAPTCHA sur chaque demande de page plutôt que de mettre en cache le fournisseur dès la première visite.
Articles connexes
- Comment résoudre le rappel Recaptcha V2 à l'aide de l'API
- Cloudflare Challenge vs détection de tourniquet
- Comparaison Geetest et Cloudflare Turnstile
Prochaines étapes
Gérer les sites avec plusieurs fournisseurs CAPTCHA –récupérez votre clé API CaptchaAIet mettre en œuvre la détection automatique.
Guides associés :
- Gestion de plusieurs CAPTCHA sur une seule page
- Chaînes CAPTCHA : formulaires séquentiels en plusieurs étapes
- CAPTCHA dans les modaux pop-up : détection et injection