Invisible reCAPTCHA supprime la case à cocher, mais ajoute de nouveaux modes de défaillance pour l'automatisation. Les plus gros problèmes sont ne pas détecter du tout le widget invisible, manquer la fonction de rappel, soumettre des jetons expirés et utiliser les paramètres standard v2 lorsque des paramètres invisibles sont requis.
Ce guide couvre tous les modèles d'erreur courants avec le correctif exact. Si vous avez besoin d'informations sur le fonctionnement de reCAPTCHA invisible, lisezComment fonctionne reCAPTCHA Invisible et comment le résoudred'abord.
Référence d'erreur rapide
| Erreur | Parce que | Corriger |
|---|---|---|
ERROR_WRONG_GOOGLEKEY |
Mauvaise clé de site ou clé de site provenant d'un domaine différent | Extraire la clé de site du div du widget invisible ou de l'appel grecaptcha.render() |
ERROR_PAGEURL |
Incompatibilité d'URL : URL de la page parent envoyée au lieu de l'URL iframe | Utilisez l'URL exacte où le widget invisible se charge |
ERROR_CAPTCHA_UNSOLVABLE |
Google a signalé la tâche comme impossible | Réessayez avec un nouveau proxy et des cookies ; vérifier si le site est passé à la v3 |
ERROR_BAD_TOKEN_OR_PAGEURL |
Jeton rejeté par le site cible | Vérifiez exactement les correspondances de l'URL de la page ; injecter via un rappel, pas un champ caché |
CAPCHA_NOT_READY |
Tâche en cours de traitement | Continuez à interroger toutes les 5 secondes ; les résolutions invisibles prennent 10 à 30 secondes |
ERROR_KEY_DOES_NOT_EXIST |
Clé API CaptchaAI non valide | Vérifiez la clé àcaptchaai.com/account |
| Jeton accepté mais le formulaire échoue | Rappel non exécuté après l'injection du jeton | Recherchez et appelez la fonction data-callback avec le jeton |
Erreur 1 : Ne pas détecter le reCAPTCHA invisible sur la page
Invisible reCAPTCHA n'a pas de case à cocher visible. Si votre scraper ne le détecte pas, les requêtes protégées par captcha échouent silencieusement avec des erreurs de soumission de formulaire ou des redirections.
Comment détecter les reCAPTCHA invisibles
Recherchez ces modèles dans la page HTML :
<!-- Pattern 1: div with data-size="invisible" -->
<div class="g-recaptcha" data-sitekey="6LdKlZEU..."
data-size="invisible"
data-callback="onSubmit"></div>
<!-- Pattern 2: button with data-sitekey and invisible size -->
<button class="g-recaptcha"
data-sitekey="6LdKlZEU..."
data-callback="onSubmit"
data-action="submit">Submit</button>
<!-- Pattern 3: programmatic render with size: invisible -->
<script>
grecaptcha.render('submit-btn', {
sitekey: '6LdKlZEU...',
callback: onSubmit,
size: 'invisible'
});
</script>
Script de détection (Python) :
import requests
from bs4 import BeautifulSoup
import re
def detect_invisible_recaptcha(url):
resp = requests.get(url)
soup = BeautifulSoup(resp.text, "html.parser")
# Check for data-size="invisible"
widget = soup.find("div", {"data-size": "invisible", "class": "g-recaptcha"})
if widget:
return {
"type": "invisible",
"sitekey": widget.get("data-sitekey"),
"callback": widget.get("data-callback")
}
# Check for programmatic render with invisible
scripts = soup.find_all("script")
for script in scripts:
if script.string and "size" in str(script.string) and "invisible" in str(script.string):
key_match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", script.string)
if key_match:
return {
"type": "invisible-programmatic",
"sitekey": key_match.group(1),
"callback": "check grecaptcha.render() call"
}
return None
Script de détection (Node.js) :
const axios = require("axios");
const cheerio = require("cheerio");
async function detectInvisibleRecaptcha(url) {
const { data } = await axios.get(url);
const $ = cheerio.load(data);
// Check for data-size="invisible"
const widget = $(".g-recaptcha[data-size='invisible']");
if (widget.length) {
return {
type: "invisible",
sitekey: widget.attr("data-sitekey"),
callback: widget.attr("data-callback"),
};
}
// Check script tags for programmatic invisible render
const scriptContent = $("script")
.map((_, el) => $(el).html())
.get()
.join("\n");
if (scriptContent.includes("invisible")) {
const keyMatch = scriptContent.match(/sitekey['"]?\s*[:=]\s*['"]([^'"]+)/);
if (keyMatch) {
return {
type: "invisible-programmatic",
sitekey: keyMatch[1],
callback: "check grecaptcha.render() call",
};
}
}
return null;
}
Erreur 2 : clé de site incorrecte – ERROR_WRONG_GOOGLEKEY
Cela se produit lorsque vous envoyez une clé de site qui ne correspond pas au widget reCAPTCHA invisible sur la page cible. Causes courantes :
- Copié la clé du site à partir d'une case à cocher v2 sur une autre page
- Utilisation d'une clé de site à partir de l'URL d'ancrage d'une autre version de reCAPTCHA
- La page contient plusieurs widgets reCAPTCHA et vous avez choisi le mauvais.
Correctif : extrayez la clé de site invisible correcte
import requests
from bs4 import BeautifulSoup
def get_invisible_sitekey(url):
resp = requests.get(url)
soup = BeautifulSoup(resp.text, "html.parser")
# Priority 1: invisible widget
widget = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
if widget:
return widget["data-sitekey"]
# Priority 2: any g-recaptcha div (may be invisible without data-size)
widget = soup.find(class_="g-recaptcha")
if widget and widget.get("data-sitekey"):
return widget["data-sitekey"]
return None
sitekey = get_invisible_sitekey("https://example.com/login")
print(f"Sitekey: {sitekey}")
Erreur 3 : rappel non exécuté – le formulaire est soumis mais rien ne se passe
Il s’agit de l’échec invisible numéro un de reCAPTCHA qui manque aux développeurs. Contrairement à la case à cocher v2 où l'injection du jeton dans g-recaptcha-response suffit, invisible reCAPTCHA utilise presque toujours une fonction de rappel JavaScript. Si vous injectez le jeton mais n'appelez pas le rappel, le formulaire ne sera jamais traité.
Comment fonctionne le flux de rappel
grecaptcha.execute()lance le défi invisible- Après résolution, Google appelle la fonction spécifiée dans
data-callback - Cette fonction de rappel soumet le formulaire ou effectue l'appel API
Correctif : recherchez et exécutez le rappel
Étape 1 : identifiez le nom du rappel :
# From HTML: data-callback="onSubmit"
# From JS: callback: onSubmit
# From grecaptcha.render: second argument with callback property
Étape 2 — Injecter le jeton ET appeler le rappel (Selenium) :
from selenium import webdriver
import requests
import time
driver = webdriver.Chrome()
driver.get("https://example.com/form")
# Get sitekey
sitekey = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-sitekey")
callback_name = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-callback")
# Solve with CaptchaAI
task_id = requests.get("https://ocr.captchaai.com/in.php", params={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": driver.current_url,
"invisible": 1
}).text.split("|")[1]
# Poll for result
token = None
for _ in range(60):
time.sleep(5)
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY",
"action": "get",
"id": task_id
}).text
if resp.startswith("OK|"):
token = resp.split("|")[1]
break
# Inject token into the response field
driver.execute_script(
f'document.getElementById("g-recaptcha-response").value = "{token}";'
)
# CRITICAL: Call the callback function
driver.execute_script(f'{callback_name}("{token}");')
Étape 2 — Injecter le jeton ET appeler le rappel (Marionnette) :
const puppeteer = require("puppeteer");
const axios = require("axios");
(async () => {
const browser = await puppeteer.launch({ headless: "new" });
const page = await browser.newPage();
await page.goto("https://example.com/form");
// Get sitekey and callback
const { sitekey, callback } = await page.evaluate(() => {
const el = document.querySelector(".g-recaptcha[data-size='invisible']");
return {
sitekey: el?.getAttribute("data-sitekey"),
callback: el?.getAttribute("data-callback"),
};
});
// Submit to CaptchaAI
const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
params: {
key: "YOUR_API_KEY",
method: "userrecaptcha",
googlekey: sitekey,
pageurl: page.url(),
invisible: 1,
},
});
const taskId = submitResp.data.split("|")[1];
// Poll for result
let token;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const result = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: "YOUR_API_KEY", action: "get", id: taskId },
});
if (result.data.startsWith("OK|")) {
token = result.data.split("|")[1];
break;
}
}
// Inject token and fire callback
await page.evaluate(
(tok, cb) => {
document.getElementById("g-recaptcha-response").value = tok;
if (cb && typeof window[cb] === "function") {
window[cb](tok);
}
},
token,
callback,
);
await browser.close();
})();
Erreur 4 : paramètre invisible=1 manquant
Lorsque vous résolvez un reCAPTCHA invisible via CaptchaAI, vous devez inclure invisible=1 dans votre demande. Sans cela, le solveur traite la tâche comme un défi de case à cocher v2 standard, ce qui peut conduire à ERROR_CAPTCHA_UNSOLVABLE ou à des jetons rejetés par le site cible.
Demande erronée ou correcte
# WRONG — missing invisible=1
params = {
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url
}
# CORRECT — includes invisible=1
params = {
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"invisible": 1 # Required for invisible reCAPTCHA
}
response = requests.get("https://ocr.captchaai.com/in.php", params=params)
Erreur 5 : le jeton a expiré avant la soumission
Les jetons reCAPTCHA invisibles expirent dans 120 secondes, comme la norme v2. Mais les flux de travail invisibles comportent souvent des étapes de traitement supplémentaires entre la résolution et la soumission, ce qui rend l'expiration plus probable.
Symptômes
- Le formulaire renvoie une erreur générique après l'injection du jeton
siteverifycôté serveur renvoietimeout-or-duplicate- Le jeton était valide mais a mis trop de temps à atteindre l'étape de soumission
Correctif : résolution juste à temps
Ne demandez la solution que lorsque vous êtes prêt à la soumettre immédiatement :
import requests
import time
def solve_invisible_recaptcha(api_key, sitekey, page_url):
# Submit task
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"invisible": 1
})
if not resp.text.startswith("OK|"):
raise Exception(f"Submit failed: {resp.text}")
task_id = resp.text.split("|")[1]
# Poll for result
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key,
"action": "get",
"id": task_id
})
if result.text.startswith("OK|"):
return result.text.split("|")[1]
if result.text != "CAPCHA_NOT_READY":
raise Exception(f"Solve failed: {result.text}")
raise Exception("Solve timed out after 5 minutes")
# Usage: solve JUST before you need to submit
# 1. Navigate to page and prepare form data first
# 2. THEN solve the captcha
# 3. Inject token and submit immediately
token = solve_invisible_recaptcha("YOUR_API_KEY", sitekey, page_url)
# Submit within 120 seconds of receiving the token
Erreur 6 : jeton rejeté – ERROR_BAD_TOKEN_OR_PAGEURL
Le site cible a vérifié le jeton auprès de Google et a échoué. Causes courantes :
| Parce que | Comment identifier | Corriger |
|---|---|---|
Mauvais pageurl |
L'URL ne correspond pas au domaine lors de l'enregistrement de la clé de site | Utilisez l'URL exacte où le widget se charge |
| Jeton utilisé sur un domaine différent | Réutilisation des jetons entre domaines | Résolvez avec le pageurl du domaine correct |
| Jeton déjà utilisé | Soumettre le même jeton deux fois | Demander une nouvelle solution pour chaque soumission |
| Incompatibilité IP | Votre IP diffère de celle du solveur | Ajoutez votre paramètre proxy pour correspondre à l'adresse IP de la session |
| Drapeau invisible manquant | Résolu selon la norme v2, utilisé sur une page invisible | Ajoutez invisible=1 à la demande de résolution |
Liste de contrôle de débogage
def debug_invisible_solve(api_key, sitekey, page_url, proxy=None):
"""Run a diagnostic solve with detailed logging."""
print(f"Sitekey: {sitekey}")
print(f"Page URL: {page_url}")
print(f"Proxy: {proxy or 'none'}")
params = {
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"invisible": 1
}
if proxy:
params["proxy"] = proxy
params["proxytype"] = "HTTP"
# Submit
resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
print(f"Submit response: {resp.text}")
if not resp.text.startswith("OK|"):
return None
task_id = resp.text.split("|")[1]
print(f"Task ID: {task_id}")
# Poll with timing
start = time.time()
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key, "action": "get", "id": task_id
})
elapsed = time.time() - start
print(f" [{elapsed:.0f}s] {result.text[:50]}")
if result.text.startswith("OK|"):
token = result.text.split("|")[1]
print(f"Token received after {elapsed:.0f}s")
print(f"Token length: {len(token)} characters")
print(f"Token starts with: {token[:30]}...")
return token
if result.text != "CAPCHA_NOT_READY":
print(f"FAILED: {result.text}")
return None
print("TIMEOUT after 5 minutes")
return None
Erreur 7 : plusieurs widgets reCAPTCHA sur une seule page
Certaines pages ont à la fois une case à cocher v2 visible ET un reCAPTCHA invisible. Si vous résolvez le mauvais problème, le jeton est valide mais ne correspond pas au widget gardant l'action dont vous avez besoin.
Correctif : ciblez le bon widget
from bs4 import BeautifulSoup
def find_all_recaptcha_widgets(html):
soup = BeautifulSoup(html, "html.parser")
widgets = []
for el in soup.find_all(class_="g-recaptcha"):
widgets.append({
"sitekey": el.get("data-sitekey"),
"size": el.get("data-size", "normal"),
"callback": el.get("data-callback"),
"tag": el.name,
"id": el.get("id")
})
return widgets
# Example output:
# [
# {"sitekey": "6LdA...", "size": "normal", "callback": None, "tag": "div", "id": "recaptcha-login"},
# {"sitekey": "6LdB...", "size": "invisible", "callback": "onRegister", "tag": "div", "id": "recaptcha-register"}
# ]
# Use the widget with size="invisible" for the invisible solve
Solveur reCAPTCHA invisible complet avec gestion des erreurs
Ce wrapper prêt pour la production gère toutes les erreurs ci-dessus :
import requests
import time
import logging
logger = logging.getLogger(__name__)
class InvisibleRecaptchaSolver:
def __init__(self, api_key, max_retries=3):
self.api_key = api_key
self.max_retries = max_retries
self.base_url = "https://ocr.captchaai.com"
def solve(self, sitekey, page_url, proxy=None):
"""Solve invisible reCAPTCHA with automatic retry on transient errors."""
for attempt in range(1, self.max_retries + 1):
try:
token = self._attempt_solve(sitekey, page_url, proxy)
if token:
return token
except Exception as e:
logger.warning(f"Attempt {attempt} failed: {e}")
if attempt < self.max_retries:
time.sleep(2 ** attempt)
raise Exception(f"Failed to solve after {self.max_retries} attempts")
def _attempt_solve(self, sitekey, page_url, proxy):
params = {
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"invisible": 1
}
if proxy:
params["proxy"] = proxy
params["proxytype"] = "HTTP"
# Submit task
resp = requests.get(f"{self.base_url}/in.php", params=params)
if "ERROR" in resp.text:
error = resp.text.strip()
if error in ("ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"):
raise Exception(f"Configuration error (do not retry): {error}")
if error == "ERROR_ZERO_BALANCE":
raise Exception("Account balance is zero — add funds")
raise Exception(f"Submit error: {error}")
if not resp.text.startswith("OK|"):
raise Exception(f"Unexpected submit response: {resp.text}")
task_id = resp.text.split("|")[1]
# Poll for result
for _ in range(60):
time.sleep(5)
result = requests.get(f"{self.base_url}/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id
})
if result.text.startswith("OK|"):
return result.text.split("|")[1]
if result.text == "CAPCHA_NOT_READY":
continue
if result.text == "ERROR_CAPTCHA_UNSOLVABLE":
logger.warning("Captcha unsolvable — will retry with new task")
return None
raise Exception(f"Poll error: {result.text}")
raise Exception("Solve timed out after 5 minutes")
# Usage
solver = InvisibleRecaptchaSolver("YOUR_API_KEY")
token = solver.solve(
sitekey="6LdKlZEU...",
page_url="https://example.com/login"
)
print(f"Token: {token[:50]}...")
const axios = require("axios");
class InvisibleRecaptchaSolver {
constructor(apiKey, maxRetries = 3) {
this.apiKey = apiKey;
this.maxRetries = maxRetries;
this.baseUrl = "https://ocr.captchaai.com";
}
async solve(sitekey, pageUrl, proxy) {
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
const token = await this._attemptSolve(sitekey, pageUrl, proxy);
if (token) return token;
} catch (err) {
console.warn(`Attempt ${attempt} failed: ${err.message}`);
if (attempt < this.maxRetries) {
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
}
}
}
throw new Error(`Failed to solve after ${this.maxRetries} attempts`);
}
async _attemptSolve(sitekey, pageUrl, proxy) {
const params = {
key: this.apiKey,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: pageUrl,
invisible: 1,
};
if (proxy) {
params.proxy = proxy;
params.proxytype = "HTTP";
}
// Submit task
const submitResp = await axios.get(`${this.baseUrl}/in.php`, { params });
if (submitResp.data.includes("ERROR")) {
const error = submitResp.data.trim();
if (["ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"].includes(error)) {
throw new Error(`Configuration error (do not retry): ${error}`);
}
throw new Error(`Submit error: ${error}`);
}
const taskId = submitResp.data.split("|")[1];
// Poll for result
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const result = await axios.get(`${this.baseUrl}/res.php`, {
params: { key: this.apiKey, action: "get", id: taskId },
});
if (result.data.startsWith("OK|")) {
return result.data.split("|")[1];
}
if (result.data === "CAPCHA_NOT_READY") continue;
if (result.data === "ERROR_CAPTCHA_UNSOLVABLE") return null;
throw new Error(`Poll error: ${result.data}`);
}
throw new Error("Solve timed out after 5 minutes");
}
}
// Usage
const solver = new InvisibleRecaptchaSolver("YOUR_API_KEY");
solver.solve("6LdKlZEU...", "https://example.com/login").then((token) => {
console.log(`Token: ${token.substring(0, 50)}...`);
});
Liste de contrôle de dépannage
Parcourez cette liste de contrôle lorsque la résolution invisible de reCAPTCHA échoue :
| Étape | Vérifier | Commande/Action |
|---|---|---|
| 1 | Confirmez qu'il est invisible, pas la norme v2 | Recherchez data-size="invisible" ou size: 'invisible' dans l'appel de rendu |
| 2 | Vérifiez que la clé du site est correcte | Comparez avec data-sitekey sur le widget invisible spécifiquement |
| 3 | Confirmez invisible=1 dans la requête API |
Vérifiez vos paramètres in.php |
| 4 | Vérifiez que pageurl correspond exactement |
Utilisez l'URL DevTools du navigateur, pas une URL de redirection |
| 5 | Rechercher le nom de la fonction de rappel | Recherchez l'attribut data-callback ou callback dans grecaptcha.render() |
| 6 | Vérifier l'injection de jeton + appel de rappel | Les deux étapes sont nécessaires : le jeton seul ne suffit pas |
| 7 | Vérifier la fraîcheur du jeton | Le jeton doit être utilisé dans les 120 secondes |
| 8 | Testez avec proxy si l'IP est importante | Ajouter les paramètres proxy et proxytype |
FAQ
En quoi le reCAPTCHA invisible est-il différent de la norme v2 pour la résolution ?
La méthode API est la même (method=userrecaptcha), mais vous devez ajouter invisible=1 à votre requête. La différence critique se situe du côté de l'injection : le reCAPTCHA invisible nécessite presque toujours l'appel d'une fonction de rappel JavaScript après l'injection du jeton, alors que la norme v2 fonctionne généralement avec uniquement le champ caché.
Pourquoi mon jeton fonctionne-t-il en test mais échoue-t-il en production ?
Très probablement une incompatibilité IP. Lors des tests, le solveur et votre navigateur peuvent partager des adresses IP similaires. En production, l'IP du solveur et l'IP de votre serveur diffèrent. Ajoutez un paramètre proxy qui correspond à l'adresse IP de votre session pour résoudre ce problème.
Combien de temps faut-il pour que reCAPTCHA invisible soit résolu ?
Les temps de résolution typiques sont de 10 à 30 secondes via CaptchaAI. Les défis invisibles sont généralement plus rapides que les défis de cases à cocher v2 car ils ne nécessitent pas de reconnaissance d'image : ils reposent sur une analyse des risques.
Puis-je résoudre le reCAPTCHA invisible sans navigateur ?
Oui. Étant donné que la résolution s'effectue côté serveur via l'API, vous n'avez besoin que de la clé du site et de l'URL de la page. Le navigateur n'est nécessaire que si vous devez exécuter la fonction de rappel sur la page réelle. Pour les flux de travail API purs, extrayez la clé du site, résolvez via CaptchaAI et soumettez le jeton avec votre requête HTTP.
Prochaines étapes
- Comment fonctionne reCAPTCHA Invisible et comment le résoudre— contexte sur le mécanisme invisible
- Comment résoudre reCAPTCHA v2 à l'aide de l'API— résolution standard v2 pour comparaison
- Erreurs et correctifs courants de résolution de reCAPTCHA v2— Modèles d'erreur standard v2
- Codes d’erreur CaptchaAI : référence complète- tableau complet des codes d'erreur
Articles connexes
- Résoudre Recaptcha Invisible Python
- Erreurs et correctifs courants de Captcha d’image de grille
- Recaptcha V2 contre Invisible