Un CAPTCHA enfermé dans une iframe se résout comme les autres, à une nuance près : il faut lire le sitekey dans le cadre qui affiche le widget, puis réinjecter le token dans ce même cadre. Sur un parcours de paiement e-commerce, le widget reCAPTCHA vit souvent dans une iframe de paiement, elle-même chargée dans une iframe modale — trois contextes JavaScript à traverser avant d'atteindre la clé du site.
La démarche tient en quatre gestes, identiques quelle que soit la profondeur d'imbrication :
- descendre jusqu'au cadre qui restitue le widget ;
- y lire le sitekey (
data-sitekeyou paramètrek=) ; - résoudre le CAPTCHA via l'API CaptchaAI ;
- réinjecter le token dans ce cadre, puis remonter pour valider le formulaire.
Où se cache le sitekey dans l'arborescence d'iframes
Une structure imbriquée typique ressemble à ceci :
Main page
└── iframe#payment-frame (cross-origin)
└── iframe[src*="recaptcha/api2/anchor"] (Google-hosted)
└── reCAPTCHA widget
Trois points suffisent à s'y retrouver :
- La clé du site vit dans l'iframe externe, celle qui restitue le widget — jamais dans l'iframe interne de Google.
- L'iframe Google la plus interne n'affiche que le défi visuel. Vous n'interagissez jamais avec elle directement.
- Le sitekey se lit depuis le cadre parent :
data-sitekeysur le conteneur.g-recaptcha, ou paramètrek=dans lesrcde l'iframe reCAPTCHA. Ces valeurs restent lisibles même en origine croisée.
Selenium : entrer dans le cadre et extraire le sitekey (Python)
Avec Selenium, chaque iframe est un contexte séparé : vous devez y entrer avant de chercher un élément dedans. Le script ci-dessous suit toujours le même enchaînement :
- entrer dans le cadre de paiement ;
- y trouver l'iframe reCAPTCHA et lire le sitekey ;
- envoyer la tâche à CaptchaAI et interroger le résultat ;
- réinjecter le token sans avoir quitté le cadre ;
- revenir au contexte principal.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import requests
import re
import time
API_KEY = "YOUR_API_KEY"
driver = webdriver.Chrome()
driver.get("https://example.com/checkout")
# Step 1: Switch into the outer iframe
wait = WebDriverWait(driver, 15)
outer_iframe = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, "iframe#payment-frame"))
)
driver.switch_to.frame(outer_iframe)
# Step 2: Find the reCAPTCHA iframe and extract sitekey
recaptcha_iframe = wait.until(
EC.presence_of_element_located(
(By.CSS_SELECTOR, 'iframe[src*="recaptcha/api2/anchor"]')
)
)
src = recaptcha_iframe.get_attribute("src")
sitekey = re.search(r"k=([A-Za-z0-9_-]+)", src).group(1)
page_url = driver.current_url
print(f"Sitekey: {sitekey}")
print(f"Page URL: {page_url}")
# Step 3: Solve with CaptchaAI
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1"
})
task_id = resp.json()["request"]
token = None
for _ in range(24):
time.sleep(5)
poll = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1"
}).json()
if poll["status"] == 1:
token = poll["request"]
break
if poll["request"] != "CAPCHA_NOT_READY":
raise Exception(poll["request"])
print(f"Token: {token[:50]}...")
# Step 4: Inject token (still inside the outer iframe)
driver.execute_script("""
document.querySelector('textarea[name="g-recaptcha-response"]').value = arguments[0];
if (typeof grecaptcha !== 'undefined') {
grecaptcha.getResponse = function() { return arguments[0]; };
}
""", token)
# Step 5: Switch back to main page
driver.switch_to.default_content()
print("Token injected, switched back to main frame")
Le piège classique est l'étape 4 : l'injection se fait pendant que le driver est encore dans le cadre de paiement, là où vit le textarea[name="g-recaptcha-response"]. Ressorti trop tôt, le sélecteur ne trouve rien. Sortie attendue :
Sitekey: 6Le-SITEKEY-abc123
Page URL: https://example.com/checkout
Token: 03AGdBq26ZfPxL...
Token injected, switched back to main frame
Puppeteer : les cadres sans changement manuel
Puppeteer traite les cadres nativement. contentFrame() vous rend le contexte de l'iframe, sans les allers-retours switch_to de Selenium : chaque cadre devient un objet sur lequel vous cherchez vos éléments.
const puppeteer = require('puppeteer');
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com/checkout', {
waitUntil: 'networkidle2'
});
// Step 1: Find the payment iframe
const paymentFrameHandle = await page.waitForSelector('iframe#payment-frame');
const paymentFrame = await paymentFrameHandle.contentFrame();
// Step 2: Find reCAPTCHA iframe inside the payment frame
const recaptchaFrameHandle = await paymentFrame.waitForSelector(
'iframe[src*="recaptcha/api2/anchor"]'
);
const src = await paymentFrame.evaluate(
el => el.getAttribute('src'), recaptchaFrameHandle
);
const sitekey = src.match(/k=([A-Za-z0-9_-]+)/)[1];
const pageUrl = page.url();
console.log(`Sitekey: ${sitekey}`);
// Step 3: Solve with CaptchaAI
const submitResp = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY,
method: 'userrecaptcha',
googlekey: sitekey,
pageurl: pageUrl,
json: 1
}
});
const taskId = submitResp.data.request;
let token = null;
for (let i = 0; i < 24; i++) {
await new Promise(r => setTimeout(r, 5000));
const pollResp = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 }
});
if (pollResp.data.status === 1) {
token = pollResp.data.request;
break;
}
if (pollResp.data.request !== 'CAPCHA_NOT_READY') {
throw new Error(pollResp.data.request);
}
}
console.log(`Token: ${token.substring(0, 50)}...`);
// Step 4: Inject token into the payment frame
await paymentFrame.evaluate((tkn) => {
const textarea = document.querySelector(
'textarea[name="g-recaptcha-response"]'
);
textarea.value = tkn;
const callback = document.querySelector('.g-recaptcha')
?.getAttribute('data-callback');
if (callback && typeof window[callback] === 'function') {
window[callback](tkn);
}
}, token);
console.log('Token injected into payment iframe');
})();
Notez l'appel au data-callback : beaucoup de widgets attendent le déclenchement de cette fonction pour valider le défi. Poser la valeur du textarea ne suffit pas toujours.
Trois niveaux d'imbrication ou plus
Au-delà de deux cadres, on chaîne les entrées : descendre niveau par niveau jusqu'au cadre qui porte le sitekey, puis remonter pour valider.
Selenium
# Main → iframe-1 → iframe-2 → CAPTCHA
driver.switch_to.frame(driver.find_element(By.ID, "iframe-1"))
driver.switch_to.frame(driver.find_element(By.ID, "iframe-2"))
# Now extract sitekey from this context
# To go back up one level:
driver.switch_to.parent_frame()
# To go back to main:
driver.switch_to.default_content()
Puppeteer
const frame1 = await (await page.$('iframe#iframe-1')).contentFrame();
const frame2 = await (await frame1.$('iframe#iframe-2')).contentFrame();
// Extract sitekey from frame2
Deux réflexes évitent la plupart des échecs à ce stade :
- avec Selenium, remontez toujours (
parent_frame()oudefault_content()) avant l'action suivante — l'oubli est la première cause de plantage ; - avec Puppeteer, gardez la référence renvoyée par
contentFrame(): elle reste valide tant que le cadre existe.
Passer à l'échelle sans multiplier les navigateurs
Un scraper qui parcourt des centaines de checkouts a besoin de résolutions concurrentes. La facturation de CaptchaAI repose sur le thread, ce qui évite de faire tourner un navigateur par CAPTCHA :
- une résolution en cours occupe un thread, puis le libère une fois terminée ;
- le plan BASIC ($15/mois, 5 threads) traite cinq CAPTCHA en parallèle, sans plafond quotidien ni coût par résolution ;
- gardez un seul navigateur par worker et déportez uniquement la résolution vers l'API ;
- si vous journalisez les URL de checkout, minimisez les données personnelles conservées pour rester aligné avec vos obligations RGPD.
Erreurs fréquentes et correctifs
Quatre erreurs reviennent en boucle sur les iframes imbriquées. Voici comment les reconnaître et les corriger.
| Erreur | Symptôme | Correctif |
|---|---|---|
L'URL de la page principale passée en pageurl |
Token refusé par le site | Passez l'URL du cadre qui restitue le widget, pas celle de la page racine |
| Aucune remontée vers le cadre parent | Les actions suivantes échouent | Appelez switch_to.parent_frame() ou switch_to.default_content() juste après l'injection |
| Sitekey lu sur la mauvaise iframe | ERROR_WRONG_GOOGLEKEY |
La clé vit sur l'iframe qui contient .g-recaptcha, jamais sur l'iframe interne du défi Google |
| Script injecté dans une iframe d'origine croisée | SecurityError dans la console |
Vous ne pouvez pas execute_script dans un cadre cross-origin : lisez le sitekey depuis l'attribut src |
Questions fréquentes
Puppeteer gère-t-il mieux les iframes que Selenium ?
Il les gère plus simplement, pas « mieux ». Puppeteer expose chaque cadre via contentFrame() sans changement de contexte, là où Selenium impose switch_to.frame() puis une remontée manuelle. Choisissez selon la pile de votre projet.
Le token peut-il expirer pendant que je change de cadre ?
Oui : un token reCAPTCHA reste valide environ deux minutes. Résolvez le CAPTCHA au moment de l'injecter, pas à l'avance — enchaînez extraction, résolution et injection dans la même passe.
Comment traiter une iframe Cloudflare Turnstile imbriquée ?
Le principe est identique : le sitekey se lit dans l'attribut data-sitekey du conteneur Turnstile et le token retourne dans le champ caché du widget. Seule la méthode d'API change — turnstile au lieu de userrecaptcha.
Pourquoi j'obtiens ERROR_WRONG_GOOGLEKEY sur une iframe ?
Vous avez lu le sitekey sur le mauvais cadre — souvent l'iframe interne de Google plutôt que celle qui porte .g-recaptcha. Reprenez la clé sur le data-sitekey du conteneur externe ou sur le paramètre k= de son src.
Résolvez vos CAPTCHA en iframe, quelle que soit la profondeur
Le plus dur n'est jamais la résolution : c'est de viser le bon cadre. Le sitekey extrait et le token réinjecté au bon endroit, l'API CaptchaAI fait le reste, en reCAPTCHA v2 comme en Cloudflare Turnstile. Récupérez votre clé API sur captchaai.com et testez la démarche sur votre propre parcours de paiement.