Les CAPTCHA sont souvent intégrés dans une ou plusieurs iframes. Le widget CAPTCHA peut se trouver dans une iframe de paiement, qui elle-même se trouve dans une iframe modale. Pour résoudre ces CAPTCHA, vous devez naviguer dans l'arborescence iframe, extraire la clé du site, résoudre via CaptchaAI et réinjecter le jeton dans le contexte de cadre correct.
Comment fonctionnent les CAPTCHA iframe
Une structure imbriquée typique :
Main page
└── iframe#payment-frame (cross-origin)
└── iframe[src*="recaptcha/api2/anchor"] (Google-hosted)
└── reCAPTCHA widget
La clé de site réside dans l'iframe externe (celui qui restitue le widget). L'iframe Google le plus interne est le défi visuel : vous n'interagissez pas directement avec lui. L'attribut data-sitekey ou le paramètre k= dans l'iframe src est ce dont vous avez besoin.
Python : commutation d'iframe Selenium
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")
Résultat attendu :
Sitekey: 6Le-SITEKEY-abc123
Page URL: https://example.com/checkout
Token: 03AGdBq26ZfPxL...
Token injected, switched back to main frame
JavaScript : gestion des cadres du marionnettiste
Puppeteer dispose d'un support de trame de première classe - aucune commutation manuelle n'est nécessaire.
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');
})();
Iframes profondément imbriquées
Pour trois niveaux d'imbrication ou plus, chaînez les commutateurs de cadre :
Sélénium
# 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()
Marionnettiste
const frame1 = await (await page.$('iframe#iframe-1')).contentFrame();
const frame2 = await (await frame1.$('iframe#iframe-2')).contentFrame();
// Extract sitekey from frame2
Erreurs courantes
| Erreur | Que se passe-t-il | Corriger |
|---|---|---|
Utilisation de l'URL de la page principale comme pageurl |
Jeton rejeté par le site | Utilisez l'URL du cadre qui restitue le widget CAPTCHA |
| Ne pas revenir au cadre parent | Les actions suivantes échouent | Appeler switch_to.parent_frame() ou switch_to.default_content() après l'injection |
| Extraire la clé de site d'une mauvaise iframe | ERROR_WRONG_GOOGLEKEY |
La clé du site se trouve sur l'iframe qui contient .g-recaptcha, et non sur l'iframe interne du défi Google. |
| Iframe d'origine croisée bloquant JS | SecurityError dans la console |
Vous ne pouvez pas execute_script dans une iframe d'origine croisée : extrayez plutôt la clé de site de l'attribut src. |
FAQ
L'iframe est d'origine croisée. Puis-je quand même extraire la clé du site ?
Oui. La clé de site se trouve dans l'attribut src ou data-sitekey de l'élément iframe, qui sont lisibles à partir du cadre parent. Vous n'avez jamais besoin d'exécuter du JavaScript dans l'iframe multi-origine.
Dois-je utiliser l'URL de la page parent ou l'URL iframe pour pageurl ?
Utilisez l'URL de la page qui charge le widget reCAPTCHA. Il s'agit généralement de la propre URL de l'iframe, et non de la page de niveau supérieur. Vérifiez la configuration reCAPTCHA du site pour confirmer.
Comment savoir dans quelle frame injecter le token ?
Injectez le jeton dans le cadre qui contient l'élément textarea[name="g-recaptcha-response"] - c'est toujours le cadre qui a rendu le div .g-recaptcha.
Résolvez les CAPTCHA dans n'importe quelle profondeur d'iframe avec CaptchaAI
Obtenez votre clé API surcaptchaai.com.
Guides associés
- Gestion de plusieurs CAPTCHA sur une seule page
- Extraction des paramètres reCAPTCHA de la source de la page
- Résolution de CAPTCHA avec Selenium + Python