Un curseur à faire glisser, une image à pivoter, un puzzle, un widget JavaScript maison : ces défis n'ont aucune méthode dédiée dans une API de résolution. Une seule technique les couvre pourtant presque tous — capturer le défi sous forme d'image et l'envoyer à l'endpoint image/OCR de CaptchaAI avec des instructions en texte clair. Le solveur renvoie une valeur brute (position en pixels, angle, ordre de clics, transcription) que votre script n'a plus qu'à traduire en action dans le navigateur.
Périmètre : ces techniques servent à automatiser vos propres tests QA et vos environnements autorisés. Cadrez la capture sur le seul widget concerné et vérifiez vos obligations RGPD sur la minimisation des données.
La méthode qui couvre presque tout
Le principe tient en un appel : envoyez l'image encodée en base64 à in.php avec un champ textinstructions qui décrit précisément la réponse attendue, puis interrogez res.php jusqu'à obtenir le résultat.
import requests
import base64
import time
import os
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
def solve_custom_captcha(image_b64, instructions):
"""Solve any visual CAPTCHA using image + text instructions."""
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "base64",
"body": image_b64,
"textinstructions": instructions,
"json": 1,
}, timeout=30)
result = resp.json()
if result.get("status") != 1:
raise RuntimeError(result.get("request"))
task_id = result["request"]
time.sleep(10)
for _ in range(30):
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get",
"id": task_id, "json": 1,
}, timeout=15)
data = resp.json()
if data.get("status") == 1:
return data["request"]
if data["request"] != "CAPCHA_NOT_READY":
raise RuntimeError(data["request"])
time.sleep(5)
raise TimeoutError("Solve timeout")
Trois étapes suffisent à comprendre le flux :
- La première requête renvoie un
task_id. - La boucle interroge le résultat toutes les cinq secondes.
- La valeur
CAPCHA_NOT_READYsignifie « pas encore prêt » ; toute autre valeur est une erreur à remonter.
Cette fonction sert de socle à tous les cas particuliers ci-dessous : seul le texte des instructions change.
Reconnaître le format en un coup d'œil
Avant d'écrire du code, identifiez la famille du défi. Le tableau relie chaque format inhabituel à la méthode la plus adaptée.
| Type de défi | Ce que voit l'utilisateur | Méthode côté API |
|---|---|---|
| CAPTCHA à curseur | Faire glisser jusqu'à une position | Capture d'écran + instructions textuelles |
| Puzzle (pièce à emboîter) | Glisser la pièce au bon endroit | Peut relever d'une résolution de type GeeTest v3 |
| CAPTCHA audio | Écouter puis saisir | Envoyer le fichier audio encodé |
| Rotation d'image | Pivoter jusqu'à la bonne orientation | Capture d'écran + instructions |
| Ordre de sélection | Cliquer les éléments dans l'ordre | Approche grille d'images |
| Équation mathématique | Résoudre un calcul | Paramètre calc=1 |
| Widget interactif sur mesure | Composant JS propre au site | Capture d'écran + instructions textuelles |
Écrire des instructions exploitables
La qualité de la réponse dépend presque entièrement du texte des instructions. Quatre règles suffisent :
- Imposez le format de sortie : « Renvoyez uniquement le nombre ».
- Donnez le repère spatial : « de gauche à droite, de haut en bas ».
- Posez une seule question par appel ; découpez si besoin.
- Cadrez la capture au plus près du défi, sans le reste de la page.
CAPTCHA à curseur
Un curseur demande de faire glisser une poignée jusqu'à une position précise : capturez le composant, demandez l'offset horizontal en pixels, puis rejouez le déplacement avec ActionChains.
# slider_captcha.py
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
def solve_slider_captcha(driver, captcha_selector):
"""Screenshot slider CAPTCHA and solve via CaptchaAI."""
captcha = driver.find_element(By.CSS_SELECTOR, captcha_selector)
image_b64 = captcha.screenshot_as_base64
result = solve_custom_captcha(
image_b64,
"What pixel position should the slider be dragged to? "
"Return only the X offset number."
)
try:
offset = int(result)
except ValueError:
return False
# Drag slider to position
slider = driver.find_element(By.CSS_SELECTOR, ".slider-handle")
ActionChains(driver).click_and_hold(slider).move_by_offset(offset, 0).release().perform()
return True
Rotation d'image
# rotation_captcha.py
def solve_rotation_captcha(driver, captcha_selector):
"""Solve rotation CAPTCHA."""
captcha = driver.find_element(By.CSS_SELECTOR, captcha_selector)
image_b64 = captcha.screenshot_as_base64
result = solve_custom_captcha(
image_b64,
"How many degrees should this image be rotated clockwise "
"to be in the correct upright orientation? Return only the number."
)
try:
degrees = int(result)
except ValueError:
return False
# Click rotation button the correct number of times
rotate_btn = driver.find_element(By.CSS_SELECTOR, ".rotate-button")
clicks = degrees // 90 # Each click rotates 90 degrees
for _ in range(clicks):
rotate_btn.click()
time.sleep(0.3)
return True
Demandez l'angle en degrés dans le sens horaire, puis cliquez le bon nombre de fois. Adaptez le diviseur // 90 si le widget tourne par pas de 45 degrés.
Ordre de sélection
Demandez au solveur une liste de positions numérotées à partir de 1, puis cliquez les éléments dans cet ordre.
# order_captcha.py
def solve_order_captcha(driver, captcha_selector, item_selector):
"""Solve click-in-order CAPTCHA."""
captcha = driver.find_element(By.CSS_SELECTOR, captcha_selector)
image_b64 = captcha.screenshot_as_base64
result = solve_custom_captcha(
image_b64,
"What is the correct order? Return as comma-separated "
"numbers (1-indexed) representing positions left-to-right, top-to-bottom."
)
# Parse order
try:
order = [int(x.strip()) for x in result.split(",")]
except ValueError:
return False
# Click items in order
items = driver.find_elements(By.CSS_SELECTOR, item_selector)
for idx in order:
if 1 <= idx <= len(items):
items[idx - 1].click()
time.sleep(0.5)
return True
Variante audio
# audio_captcha.py
import requests
def solve_audio_captcha(audio_url):
"""Download and solve an audio CAPTCHA."""
# Download audio
resp = requests.get(audio_url, timeout=30)
audio_b64 = base64.b64encode(resp.content).decode("ascii")
# Submit as image with instructions
# CaptchaAI may support audio via the base64 method
result = solve_custom_captcha(
audio_b64,
"This is an audio CAPTCHA. Transcribe the spoken characters."
)
return result
Beaucoup de formulaires exposent une alternative audio pour l'accessibilité. Téléchargez le fichier, encodez-le en base64 et demandez une transcription. Vérifiez la réponse plutôt que de la supposer valide.
Widget entièrement inconnu
Face à un composant maison, restez générique : capturez tout le widget, reprenez les consignes affichées et injectez la réponse dans le premier champ trouvé.
# custom_widget.py
from selenium import webdriver
from selenium.webdriver.common.by import By
def handle_custom_widget(driver, widget_selector):
"""Handle an unknown custom CAPTCHA widget."""
# Step 1: Screenshot the entire widget
widget = driver.find_element(By.CSS_SELECTOR, widget_selector)
image_b64 = widget.screenshot_as_base64
# Step 2: Get any visible instructions
try:
instructions_el = widget.find_element(By.CSS_SELECTOR, ".instructions, .prompt, p")
visible_instructions = instructions_el.text
except Exception:
visible_instructions = "Solve this CAPTCHA"
# Step 3: Submit with descriptive instructions
result = solve_custom_captcha(
image_b64,
f"CAPTCHA instructions: {visible_instructions}. "
f"Return the answer text."
)
# Step 4: Try to submit result
try:
input_el = widget.find_element(By.CSS_SELECTOR, "input")
input_el.clear()
input_el.send_keys(result)
except Exception:
# No input — try clicking based on result
driver.execute_script("""
var input = document.querySelector('input[name*="captcha"]');
if (input) input.value = arguments[0];
""", result)
return result
Détection automatique du type
# detector.py
import re
def detect_captcha_type(page_html):
"""Detect which CAPTCHA type is on a page."""
checks = {
"recaptcha_v2": r'data-sitekey.*g-recaptcha',
"recaptcha_v3": r'recaptcha/api\.js\?render=',
"turnstile": r'cf-turnstile|challenges\.cloudflare\.com/turnstile',
"geetest": r'gt\b.*challenge|geetest',
"bls": r'method.*bls|bls-captcha',
"image_text": r'captcha.*\.(png|jpg|gif|jpeg)',
"slider": r'slider.*captcha|slide.*verify',
"audio": r'audio.*captcha|captcha.*audio',
}
detected = []
for captcha_type, pattern in checks.items():
if re.search(pattern, page_html, re.IGNORECASE):
detected.append(captcha_type)
return detected if detected else ["unknown"]
Quand vos cibles varient, aiguillez chaque page vers la bonne fonction plutôt que de coder un format en dur. Une fois le type identifié, redirigez vers la méthode dédiée (reCAPTCHA, Turnstile, GeeTest v3, BLS) ou, à défaut, vers solve_custom_captcha.
Coût et débit
Les CAPTCHA personnalisés passent par la même méthode image/OCR que les images classiques : ils sont facturés au thread concurrent, jamais à la résolution. Chaque défi en cours occupe un thread ; dès qu'il se termine, le thread reprend le suivant. Le plan d'entrée BASIC ($15/mois, 5 threads) traite donc cinq défis en parallèle sans surcoût par résolution ; montez en gamme selon le débit voulu. La facturation reste en dollars US, jamais convertie en euros.
Dépannage
| Problème | Cause probable | Correctif |
|---|---|---|
ERROR_CAPTCHA_UNSOLVABLE |
Image floue ou instructions vagues | Améliorez la netteté de la capture et précisez les instructions |
| Format de réponse inattendu | Le solveur a renvoyé une description au lieu d'une valeur | Contraignez la sortie : « Renvoyez uniquement le nombre » |
| Widget non capturé | Élément hors de la zone visible | Faites défiler jusqu'à l'élément avant la capture d'écran |
| L'interaction échoue | Coordonnées de clic erronées | Reliez soigneusement la réponse aux éléments réels de l'interface |
FAQ
Quels formats de CAPTCHA cette méthode couvre-t-elle ?
Les défis visuels sans méthode dédiée : curseur, rotation, ordre de clics, widgets maison. Pour les familles prises en charge nativement — reCAPTCHA v2/v3, Cloudflare Turnstile et Challenge, GeeTest v3, image/OCR, grilles d'images, BLS — utilisez plutôt leur méthode attitrée.
Comment obtenir une réponse directement exploitable ?
Contraignez le format dans le champ textinstructions (« Renvoyez uniquement le nombre »), posez une seule question par appel et donnez le repère spatial. Un try/except autour de la conversion protège le script si la réponse reste inattendue.
Un curseur relève-t-il de cette méthode ou de GeeTest v3 ?
Cela dépend de son origine. Si le curseur est un défi GeeTest v3, passez par la méthode geetest dédiée, prise en charge nativement. S'il s'agit d'un composant maison sans équivalent, la capture d'écran assortie d'instructions reste la bonne approche.
Capturer un CAPTCHA pose-t-il un problème RGPD ?
La capture elle-même est anodine, mais elle peut inclure des données affichées autour du widget. Cadrez au plus près du défi, ne conservez pas les images plus longtemps que nécessaire et vérifiez vos obligations de minimisation.
Guides connexes
- Résoudre les CAPTCHA à plusieurs caractères
- Bien encoder vos images en Base64
Résolvez vos CAPTCHA les plus atypiques — démarrez avec CaptchaAI.