Les formulaires web sont souvent le premier point de rupture d'un workflow automatisé. Que vous fassiez de la QA, de la validation de parcours ou des tests de soumission, un CAPTCHA suffit à bloquer tout le scénario. Pour garder une automatisation fiable, il faut intégrer la résolution directement dans la séquence de remplissage et de soumission.
Ce guide montre comment structurer un flux avec Selenium et CaptchaAI pour détecter le type de challenge, récupérer un token ou un texte résolu, puis soumettre le formulaire au bon moment.
Le vrai problème
Un formulaire protégé par CAPTCHA ne se contente pas de demander une vérification : il modifie aussi l'ordre logique de votre automatisation. Si vous résolvez trop tôt, le token expire. Si vous détectez mal le challenge, vous soumettez un formulaire incomplet. Si la page utilise beaucoup de JavaScript, il faut parfois combiner remplissage DOM, attente explicite et injection du token au bon endroit.
Architecture du workflow
┌────────────┐ ┌──────────────┐ ┌────────────┐ ┌──────────────┐
│ Load Form │────▶│ Fill Fields │────▶│ Detect & │────▶│ Submit Form │
│ (Selenium) │ │ │ │ Solve │ │ │
│ │ │ │ │ CAPTCHA │ │ │
└────────────┘ └──────────────┘ └────────────┘ └──────────────┘
Composants de base
Solveur CAPTCHA
import time
import requests
class FormCaptchaSolver:
BASE = "https://ocr.captchaai.com"
def __init__(self, api_key):
self.api_key = api_key
def solve(self, params, initial_wait=10):
params["key"] = self.api_key
params["json"] = 1
resp = requests.post(f"{self.BASE}/in.php", data=params).json()
if resp["status"] != 1:
raise Exception(f"Submit error: {resp['request']}")
task_id = resp["request"]
time.sleep(initial_wait)
for _ in range(60):
result = requests.get(
f"{self.BASE}/res.php",
params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
).json()
if result["request"] == "CAPCHA_NOT_READY":
time.sleep(5)
continue
if result["status"] == 1:
return result["request"]
raise Exception(f"Solve error: {result['request']}")
raise TimeoutError("CAPTCHA solve timed out")
Detecteur de CAPTCHA
import re
from selenium.webdriver.common.by import By
class CaptchaDetector:
def __init__(self, driver):
self.driver = driver
def detect(self):
"""Detect CAPTCHA type on current page."""
html = self.driver.page_source
# Turnstile
turnstile = self.driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile, [data-sitekey]")
for el in turnstile:
if "cf-turnstile" in (el.get_attribute("class") or ""):
return "turnstile", el.get_attribute("data-sitekey")
# reCAPTCHA
recaptcha = self.driver.find_elements(By.CSS_SELECTOR, "[data-sitekey]")
if recaptcha:
sitekey = recaptcha[0].get_attribute("data-sitekey")
if "recaptcha" in html.lower():
return "recaptcha_v2", sitekey
# Image CAPTCHA
img = self.driver.find_elements(By.CSS_SELECTOR, "img[src*='captcha'], img.captcha")
if img:
return "image", img[0].get_attribute("src")
return "none", None
Automatisation du formulaire
import base64
import requests as req
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
class FormAutomator:
def __init__(self, api_key):
self.solver = FormCaptchaSolver(api_key)
self.driver = webdriver.Chrome()
self.detector = CaptchaDetector(self.driver)
def fill_field(self, selector, value):
field = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, selector))
)
field.clear()
field.send_keys(value)
def select_option(self, selector, value):
from selenium.webdriver.support.ui import Select
select = Select(self.driver.find_element(By.CSS_SELECTOR, selector))
select.select_by_value(value)
def solve_captcha(self):
captcha_type, data = self.detector.detect()
page_url = self.driver.current_url
if captcha_type == "recaptcha_v2":
token = self.solver.solve({
"method": "userrecaptcha",
"googlekey": data,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
)
return True
if captcha_type == "turnstile":
token = self.solver.solve({
"method": "turnstile",
"sitekey": data,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=cf-turnstile-response]").value = "{token}";'
)
return True
if captcha_type == "image":
img_data = req.get(data).content
img_b64 = base64.b64encode(img_data).decode()
text = self.solver.solve({"method": "base64", "body": img_b64})
captcha_input = self.driver.find_element(
By.CSS_SELECTOR, "input[name*='captcha']"
)
captcha_input.clear()
captcha_input.send_keys(text)
return True
return False # No CAPTCHA detected
def submit_form(self, url, fields, submit_selector="button[type='submit']"):
"""
fields: list of (selector, value) tuples
"""
self.driver.get(url)
for selector, value in fields:
self.fill_field(selector, value)
self.solve_captcha()
submit = self.driver.find_element(By.CSS_SELECTOR, submit_selector)
submit.click()
return self.driver.current_url
def close(self):
self.driver.quit()
Exemple complet : formulaire de contact
automator = FormAutomator("YOUR_API_KEY")
try:
result_url = automator.submit_form(
url="https://example.com/contact",
fields=[
("#name", "John Doe"),
("#email", "john@example.com"),
("#subject", "Sales inquiry"),
("#message", "I'd like to learn more about your services."),
],
submit_selector="#submit-btn",
)
print(f"Form submitted. Redirected to: {result_url}")
finally:
automator.close()
Gerer plusieurs types de formulaires
Formulaire de connexion
result = automator.submit_form(
url="https://example.com/login",
fields=[
("#username", "testuser"),
("#password", "testpass123"),
],
submit_selector="#login-btn",
)
Formulaire d'inscription
result = automator.submit_form(
url="https://example.com/register",
fields=[
("#first-name", "Jane"),
("#last-name", "Smith"),
("#email", "jane@example.com"),
("#password", "SecurePass!123"),
("#confirm-password", "SecurePass!123"),
],
submit_selector="#register-btn",
)
Formulaire de recherche avec CAPTCHA
result = automator.submit_form(
url="https://example.com/search",
fields=[
("#query", "python developer"),
("#location", "San Francisco"),
],
submit_selector="#search-btn",
)
Depannage
| Probleme | Cause probable | Correctif |
|---|---|---|
| Token rejeté | Il a expiré avant le clic final | Résoudre le CAPTCHA juste avant la soumission |
| Champ introuvable | Chargement dynamique de la page | Ajoutez des waits explicites |
| Mauvais type détecté | Plusieurs mécanismes sont présents | Vérifiez l'ordre et la logique de détection |
| Le formulaire se recharge sans succès | Validation serveur incomplète | Contrôlez tous les champs obligatoires et les erreurs retour |
| Le callback reCAPTCHA ne part pas | La page attend une fonction JS spécifique | Déclenchant le callback attendu après injection |
FAQ
Peut-on soumettre un formulaire sans navigateur ?
Oui pour certains flux simples, surtout si le formulaire accepte une soumission HTTP classique. Mais des qu'il y a de la validation JavaScript, des callbacks ou un DOM dynamique, un navigateur reste le chemin le plus fiable.
Comment gérer un formulaire avec plusieurs CAPTCHA ?
Certains formulaires n'affichent un challenge qu'après une première validation incomplète. Dans ce cas, il faut relancer la détection et la résolution après chaque tentative qui modifie l'état de la page.
Et pour les formulaires AJAX ?
Quand la soumission passe par XHR ou fetch, il faut souvent injecter le token dans la charge utile envoyée par JavaScript plutôt que de compter uniquement sur un champ caché dans le DOM.
Guides connexes
- Guide complet : les bases de la production
- Référence rapide de l'API
Si vos tests ou workflows tombent sur des formulaires protégés, récupérez votre clé CaptchaAI et intégrez la résolution CAPTCHA comme une étape native de la soumission, pas comme un correctif de dernière minute.