Un utilisateur clique sur « Soumettre » et un modal apparaît avec un défi CAPTCHA. La clé de site n'est pas dans la source de la page initiale - elle se charge dynamiquement lorsque le modal s'ouvre. Votre script d'automatisation doit déclencher le modal, attendre que le CAPTCHA soit rendu, extraire les paramètres, résoudre et injecter le jeton avant l'expiration du modal ou l'expiration de la session utilisateur.
Modèles CAPTCHA modaux
| Modèle | Déclencheur | Défi |
|---|---|---|
| Mode de connexion | Cliquez sur le bouton « Connexion » | CAPTCHA se charge à l'intérieur du div de superposition |
| Interstitiel anti-bot | Automatique après un comportement suspect | Page des blocs modaux en plein écran |
| Confirmation de paiement | Soumettre le formulaire de paiement | Le modal apparaît pour vérification |
| Boîte de dialogue de limite de débit | Trop de demandes détectées | Modal avec porte CAPTCHA |
| Consentement aux cookies + CAPTCHA | Première visite | CAPTCHA intégré dans la boîte de dialogue de consentement |
Python : gestionnaire de CAPTCHA modal de dramaturge
import requests
import time
from playwright.sync_api import sync_playwright
API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
def solve_captcha(sitekey, pageurl, method="userrecaptcha"):
"""Submit and poll a CAPTCHA."""
params = {
"key": API_KEY,
"method": method,
"json": 1,
}
if method == "userrecaptcha":
params["googlekey"] = sitekey
params["pageurl"] = pageurl
elif method == "turnstile":
params["sitekey"] = sitekey
params["pageurl"] = 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 detect_modal_captcha(page):
"""
Detect CAPTCHA inside a visible modal/dialog.
Returns (sitekey, method) or (None, None).
"""
return page.evaluate("""
() => {
// Find visible modals
const modalSelectors = [
'.modal.show',
'.modal[style*="display: block"]',
'dialog[open]',
'[role="dialog"]:not([aria-hidden="true"])',
'.overlay.visible',
'.popup.active',
'[class*="modal"][class*="open"]',
];
let modal = null;
for (const sel of modalSelectors) {
const el = document.querySelector(sel);
if (el && el.offsetParent !== null) {
modal = el;
break;
}
}
// If no modal found, search entire document
const searchRoot = modal || document;
// Check for reCAPTCHA
const recaptcha = searchRoot.querySelector('.g-recaptcha[data-sitekey]');
if (recaptcha) {
return { sitekey: recaptcha.dataset.sitekey, method: 'userrecaptcha' };
}
// Check for Turnstile
const turnstile = searchRoot.querySelector('.cf-turnstile[data-sitekey]');
if (turnstile) {
return { sitekey: turnstile.dataset.sitekey, method: 'turnstile' };
}
// Check for hCaptcha
const hcaptcha = searchRoot.querySelector('.h-captcha[data-sitekey]');
if (hcaptcha) {
return { sitekey: hcaptcha.dataset.sitekey, method: 'hcaptcha' };
}
return null;
}
""")
def inject_token_in_modal(page, token, method="userrecaptcha"):
"""Inject token into the CAPTCHA inside the modal."""
if method == "userrecaptcha":
page.evaluate("""
(token) => {
// Find response textarea (may be inside modal)
const textareas = document.querySelectorAll('#g-recaptcha-response, [name="g-recaptcha-response"]');
textareas.forEach(ta => {
ta.value = token;
ta.style.display = 'block';
});
// Trigger callback
if (typeof ___grecaptcha_cfg !== 'undefined') {
Object.values(___grecaptcha_cfg.clients).forEach(client => {
Object.values(client).forEach(val => {
if (val && typeof val === 'object') {
Object.values(val).forEach(v => {
if (v && typeof v.callback === 'function') v.callback(token);
});
}
});
});
}
}
""", token)
elif method == "turnstile":
page.evaluate("""
(token) => {
const inputs = document.querySelectorAll('[name="cf-turnstile-response"]');
inputs.forEach(inp => { inp.value = token; });
if (typeof window.turnstileCallback === 'function') {
window.turnstileCallback(token);
}
}
""", token)
def handle_modal_captcha(page, trigger_selector=None, timeout=10000):
"""
Full workflow: trigger modal, detect CAPTCHA, solve, inject.
"""
# Step 1: Trigger the modal if needed
if trigger_selector:
print(f"Clicking trigger: {trigger_selector}")
page.click(trigger_selector)
# Step 2: Wait for modal to become visible
print("Waiting for modal...")
modal_selectors = [
".modal.show",
"dialog[open]",
'[role="dialog"]:not([aria-hidden="true"])',
".popup.active",
]
modal_visible = False
for selector in modal_selectors:
try:
page.wait_for_selector(selector, timeout=timeout)
modal_visible = True
print(f" Modal detected: {selector}")
break
except Exception:
continue
if not modal_visible:
print(" No modal detected")
return None
# Step 3: Wait for CAPTCHA to render inside modal
time.sleep(2) # Brief pause for dynamic CAPTCHA loading
captcha_info = detect_modal_captcha(page)
if not captcha_info:
print(" No CAPTCHA found in modal")
return None
sitekey = captcha_info["sitekey"]
method = captcha_info["method"]
print(f" Found {method} CAPTCHA: {sitekey[:20]}...")
# Step 4: Solve via CaptchaAI
token = solve_captcha(sitekey, page.url, method)
print(f" Solved: {token[:30]}...")
# Step 5: Inject token
inject_token_in_modal(page, token, method)
print(" Token injected")
return token
def main():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com")
page.wait_for_load_state("networkidle")
# Handle CAPTCHA that appears in login modal
token = handle_modal_captcha(
page,
trigger_selector="button#login-btn",
timeout=10000,
)
if token:
# Fill form fields inside modal
page.fill('dialog input[name="email"]', "user@example.com")
page.fill('dialog input[name="password"]', "password123")
# Submit modal form
page.click('dialog button[type="submit"]')
page.wait_for_load_state("networkidle")
browser.close()
main()
JavaScript : gestionnaire modal du marionnettiste
const puppeteer = require("puppeteer");
const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
async function solveCaptcha(sitekey, pageurl, method = "userrecaptcha") {
const body = new URLSearchParams({ key: API_KEY, method, json: "1" });
if (method === "userrecaptcha") { body.set("googlekey", sitekey); body.set("pageurl", pageurl); }
else if (method === "turnstile") { body.set("sitekey", sitekey); body.set("pageurl", 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 waitForModal(page, timeout = 10000) {
const selectors = [".modal.show", "dialog[open]", '[role="dialog"]', ".popup.active"];
for (const sel of selectors) {
try {
await page.waitForSelector(sel, { visible: true, timeout });
return sel;
} catch {}
}
return null;
}
async function detectModalCaptcha(page) {
return page.evaluate(() => {
const checks = [
{ sel: ".g-recaptcha[data-sitekey]", method: "userrecaptcha" },
{ sel: ".cf-turnstile[data-sitekey]", method: "turnstile" },
];
for (const { sel, method } of checks) {
const el = document.querySelector(sel);
if (el && el.dataset.sitekey) return { sitekey: el.dataset.sitekey, method };
}
return null;
});
}
async function handleModalCaptcha(page, triggerSelector) {
// Trigger modal
if (triggerSelector) await page.click(triggerSelector);
// Wait for modal
const modalSel = await waitForModal(page);
if (!modalSel) { console.log("No modal found"); return null; }
console.log(`Modal visible: ${modalSel}`);
// Wait for CAPTCHA render
await new Promise((r) => setTimeout(r, 2000));
const info = await detectModalCaptcha(page);
if (!info) { console.log("No CAPTCHA in modal"); return null; }
console.log(`Found ${info.method}: ${info.sitekey.substring(0, 20)}...`);
const token = await solveCaptcha(info.sitekey, page.url(), info.method);
console.log(`Solved: ${token.substring(0, 30)}...`);
// Inject token
await page.evaluate((t, method) => {
if (method === "userrecaptcha") {
document.querySelectorAll("#g-recaptcha-response").forEach((el) => { el.value = t; });
} else if (method === "turnstile") {
document.querySelectorAll('[name="cf-turnstile-response"]').forEach((el) => { el.value = t; });
}
}, token, info.method);
return token;
}
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "networkidle2" });
const token = await handleModalCaptcha(page, "button#login-btn");
if (token) {
await page.type('dialog input[name="email"]', "user@example.com");
await page.click('dialog button[type="submit"]');
await page.waitForNavigation();
}
await browser.close();
})();
Considérations relatives au timing modal
| Facteur | Impact | Atténuation |
|---|---|---|
| Délai de fermeture automatique modale | Le modal peut se fermer avant la fin de la résolution | Commencez la résolution immédiatement après la détection |
| Expiration de la session pendant la résolution | La session du serveur expire en attente modale | Gardez la session vivante avec un rythme cardiaque en arrière-plan |
| Délai de rendu CAPTCHA en modal | Le widget prend 1 à 3 s pour se charger en modal | Attendez 2 s après que le modal soit visible avant d'extraire la clé du site |
| Expiration du jeton lors du remplissage du formulaire | Le jeton expire lors du remplissage du formulaire modal | Résolvez CAPTCHA en dernier, après avoir rempli les autres champs |
Dépannage
| Problème | Parce que | Corriger |
|---|---|---|
| Modal détecté mais aucun CAPTCHA trouvé | CAPTCHA se charge de manière asynchrone après l'ouverture modale | Augmenter le temps d'attente ; utilisez MutationObserver pour détecter l'insertion d'un widget |
| Jeton injecté mais le modal ne se ferme pas | Fonction de rappel non déclenchée | Recherchez et invoquez explicitement le rappel CAPTCHA |
| Le modal se ferme pendant la résolution | Délai d'expiration du rejet automatique | Désactiver le délai d'attente modal via JS : clearTimeout() sur le minuteur modal |
| CAPTCHA clé de site différente à chaque fois | Modal génère des instances CAPTCHA dynamiques | Extrayez toujours la clé de site à partir du DOM modal, ne mettez jamais en cache |
| Le déclencheur de clic n'ouvre pas le modal | Élément non interactif ou derrière une superposition | Utilisez page.dispatchEvent ou attendez que l'élément soit cliquable |
FAQ
Comment détecter un modal qui s'ouvre automatiquement sans déclencheur de clic ?
Utilisez un MutationObserver pour surveiller les nouveaux éléments apparaissant dans le DOM. Configurez-le avant d’accéder à la page. Lorsqu'un élément modal est ajouté et devient visible, votre observateur se déclenche et vous pouvez démarrer le flux de détection CAPTCHA.
Que se passe-t-il si le CAPTCHA se trouve dans une iframe modale ?
Si le modal contient une iframe avec le CAPTCHA, combinez cette approche avec la gestion des iframes. Après avoir détecté le modal, passez au contexte iframe à l'intérieur du modal pour extraire la clé du site.
Dois-je remplir les champs du formulaire avant ou après avoir résolu le CAPTCHA ?
Avant. Remplissez d'abord tous les autres champs du formulaire, puis résolvez le CAPTCHA en dernier. Cela minimise le temps entre l'obtention du jeton et la soumission du formulaire, réduisant ainsi le risque d'expiration.
Articles connexes
- Comment résoudre le rappel Recaptcha V2 à l'aide de l'API
- Comparaison Geetest et Cloudflare Turnstile
- Gestion du tourniquet Recaptcha V2 sur le même site
Prochaines étapes
Gérez les CAPTCHA dans les modaux pop-up de manière transparente :récupérez votre clé API CaptchaAIet implémenter la détection modale.
Guides associés :
- Gestion de plusieurs CAPTCHA sur une seule page
- Gestion du CAPTCHA Shadow DOM
- Extraction Iframe CAPTCHA : cadres imbriqués