Un crawl de catalogue e-commerce s'arrête rarement sur une erreur HTTP propre : il s'arrête sur une image de texte déformé et un champ de saisie. Trois gestes suffisent à le relancer — détecter la page de défi, envoyer l'image à l'API CaptchaAI avec method=base64, renvoyer le formulaire avec la chaîne obtenue — et ce guide les écrit en Python. Le CAPTCHA image (OCR) fait partie des types pris en charge en disponibilité générale par CaptchaAI, comme reCAPTCHA v2 et v3, Cloudflare Turnstile ou GeeTest v3.
Pourquoi les sites marchands déclenchent un défi CAPTCHA
Un site de vente au détail n'affiche pas une image à résoudre au hasard : il réagit à un faisceau de signaux accumulés sur votre session.
| Signal | Ce que voit le site |
|---|---|
| Volume de requêtes | Trop d'appels depuis une seule adresse IP sur une fenêtre courte |
| Cookies absents | Aucun cookie de session, donc aucune continuité entre les pages |
| En-têtes incohérents | User-Agent automatisé, Accept-Language manquant |
| Réputation IP | Plages datacenter ou proxys déjà signalés |
Une fois le seuil franchi, la réponse HTTP reste souvent un code 200 : c'est le corps de la page qui change. Tant que le défi n'est pas validé, la session reste sur le même écran.
Scénario : surveiller un catalogue e-commerce depuis Paris
Une équipe qui alimente un comparateur de prix suit 4 000 références par jour sur plusieurs enseignes, depuis des workers Python hébergés chez OVHcloud ou sur AWS eu-west-3 (Paris). Le pipeline tourne trois semaines sans incident, puis le taux de pages exploitables tombe à 60 %. Aucune erreur dans les logs : simplement des sélecteurs de prix qui renvoient None, parce que le HTML reçu est en réalité un défi CAPTCHA.
Deux corrections, dans cet ordre : détecter le défi explicitement au lieu de laisser le parseur échouer en silence, puis brancher la résolution OCR pour que le worker reprenne sa page.
Côté conformité, une veille de prix publics ne collecte pas de données personnelles : journalisez les URL et les horodatages, pas des avis clients nominatifs. C'est la lecture RGPD la plus simple à tenir.
Prérequis pour la gestion des CAPTCHA en scraping
| Prérequis | Détails |
|---|---|
| Clé API CaptchaAI | Depuis captchaai.com |
| Python 3.7+ | Avec requests et beautifulsoup4 |
| Proxys résidentiels | Recommandés pour un crawl soutenu |
| Un plan adapté au parallélisme | Les threads plafonnent les résolutions simultanées, pas le total mensuel |
Résoudre le CAPTCHA image d'un site de vente au détail
Étape 1 : détecter la page de défi
Cherchez la signature du défi dans le corps de la réponse avant de parser quoi que ce soit : une fonction de détection dédiée rend le phénomène mesurable. Les extraits ci-dessous visent une page produit publique d'Amazon ; la mécanique se transpose aux autres enseignes, seuls les sélecteurs changent.
import requests
from bs4 import BeautifulSoup
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept-Language": "en-US,en;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
})
def is_captcha_page(html):
return "Type the characters you see in this image" in html or \
"captcha" in html.lower()
url = "https://www.amazon.com/dp/B0EXAMPLE"
resp = session.get(url)
if is_captcha_page(resp.text):
print("CAPTCHA detected!")
else:
print("Page loaded successfully")
Étape 2 : extraire l'image et la résoudre
L'image est téléchargée avec la même session — donc les mêmes cookies — puis encodée en base64 et envoyée à in.php. Le résultat s'obtient en interrogeant res.php : tant que la réponse vaut CAPCHA_NOT_READY, la résolution est en cours.
import base64
API_KEY = "YOUR_API_KEY"
def solve_amazon_captcha(session, captcha_page_html, captcha_page_url):
soup = BeautifulSoup(captcha_page_html, "html.parser")
# Find the CAPTCHA image
img_tag = soup.find("img", src=lambda s: s and "captcha" in s.lower())
if not img_tag:
raise Exception("CAPTCHA image not found")
img_url = img_tag["src"]
# Download the image
img_resp = session.get(img_url)
img_base64 = base64.b64encode(img_resp.content).decode()
# Submit to CaptchaAI
submit_resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "base64",
"body": img_base64
})
task_id = submit_resp.text.split("|")[1]
# Poll for result
import time
for _ in range(30):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
return result.text.split("|")[1]
raise Exception(f"Solve error: {result.text}")
raise TimeoutError("Solve timed out")
Étape 3 : renvoyer le formulaire
C'est l'étape la plus souvent ratée. Reprenez tous les champs cachés du formulaire d'origine, ajoutez-y la réponse, puis postez vers l'action convertie en URL absolue.
def submit_captcha_solution(session, captcha_page_html, solution, captcha_page_url):
soup = BeautifulSoup(captcha_page_html, "html.parser")
form = soup.find("form")
# Build form data
form_data = {}
for inp in form.find_all("input"):
name = inp.get("name")
if name:
form_data[name] = inp.get("value", "")
# Set the CAPTCHA answer
form_data["field-keywords"] = solution
# Submit
action = form.get("action", captcha_page_url)
if action.startswith("/"):
from urllib.parse import urljoin
action = urljoin(captcha_page_url, action)
resp = session.post(action, data=form_data)
return resp
Script complet : collecte produit avec gestion CAPTCHA
Assemblé, le parcours tient dans une fonction : requête, détection, résolution, renvoi du formulaire, extraction des champs produit.
import requests
import base64
import time
from bs4 import BeautifulSoup
API_KEY = "YOUR_API_KEY"
def scrape_amazon_product(url):
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept-Language": "en-US,en;q=0.9"
})
resp = session.get(url)
# Handle CAPTCHA if present
if "captcha" in resp.text.lower():
soup = BeautifulSoup(resp.text, "html.parser")
img = soup.find("img", src=lambda s: s and "captcha" in s.lower())
if img:
# Download and solve
img_data = session.get(img["src"]).content
img_b64 = base64.b64encode(img_data).decode()
submit = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY, "method": "base64", "body": img_b64
})
task_id = submit.text.split("|")[1]
for _ in range(30):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
solution = result.text.split("|")[1]
break
# Submit solution
form = soup.find("form")
form_data = {inp.get("name"): inp.get("value", "")
for inp in form.find_all("input") if inp.get("name")}
form_data["field-keywords"] = solution
action = form.get("action", url)
resp = session.post(action, data=form_data)
# Parse product data
soup = BeautifulSoup(resp.text, "html.parser")
title = soup.find("span", {"id": "productTitle"})
price = soup.find("span", class_="a-price-whole")
return {
"title": title.text.strip() if title else None,
"price": price.text.strip() if price else None
}
product = scrape_amazon_product("https://www.amazon.com/dp/B0EXAMPLE")
print(product)
Réduire la fréquence des défis CAPTCHA
Le défi le moins coûteux reste celui qui n'apparaît pas. Cinq réglages font l'essentiel :
- Proxys résidentiels – les plages datacenter sont repérées en premier.
- Rotation des User-Agent – un pool de chaînes réalistes, cohérentes avec vos en-têtes.
- Sessions persistantes – gardez les cookies d'un même worker au lieu d'ouvrir une session par page.
- Délais variables – 3 à 10 s entre deux requêtes, avec un peu d'aléa.
Accept-Languageexplicite – une locale absente est un signal facile à repérer.
Sixième règle, non négociable : restez sur les pages produit publiques. Rien qui exige une connexion n'entre dans le périmètre de ce guide.
Coût : combien de threads pour un crawl e-commerce
La facturation CaptchaAI se fait au thread simultané, avec un nombre de résolutions illimité par thread sur le mois. Ce qui compte n'est pas votre volume mensuel, mais le nombre de défis à résoudre en même temps au pic.
| Profil de crawl | Plan indicatif |
|---|---|
| Un ou deux workers, quelques centaines de pages/jour | BASIC ($15/mois, 5 threads) |
| Plusieurs enseignes en parallèle | STANDARD ($30/mois, 15 threads) |
| Veille tarifaire à grande échelle | ADVANCE ($90/mois, 50 threads) |
Au-dessus, PREMIUM ($170/mois, 100 threads), CORPORATE ($240/mois, 150 threads), ENTERPRISE ($300/mois, 200 threads), VIP-1 ($1,500/mois), VIP-2 ($4,500/mois) et VIP-3 ($7,500/mois) suivent la même logique : plus de threads, jamais de facturation à la résolution, toujours en dollars US.
Dépannage
| Problème | Cause probable | Correctif |
|---|---|---|
| Un défi à chaque requête | Réputation IP ou cadence trop élevée | Proxys résidentiels, et un peu d'aléa entre les requêtes |
| Chaîne OCR refusée | Image téléchargée hors session, champs cachés perdus | Téléchargez l'image avec la session courante, reconstruisez les input |
| Boucles de redirection | Cookies non conservés | Un seul objet Session, allow_redirects=True |
| Données produit vides, sans défi visible | Variante de mise en page servie par le site | Vérifiez vos sélecteurs sur le HTML réellement reçu |
CAPCHA_NOT_READY jusqu'au timeout |
Polling trop rapproché ou image illisible | Espacez l'interrogation, vérifiez le base64 envoyé |
FAQ
Combien de threads faut-il pour un crawl de catalogue quotidien ?
Comptez en simultané, pas en volume mensuel. Un pipeline séquentiel de quelques centaines de pages tient sur BASIC ($15/mois, 5 threads) : seul le parallélisme au pic justifie de monter en gamme.
Pourquoi le formulaire refuse-t-il ma chaîne résolue ?
Presque toujours parce que le contexte a été perdu : image téléchargée hors session, champs cachés non repris, ou action postée en chemin relatif. Rejouez l'étape 3 avant de mettre en cause la résolution.
CaptchaAI prend-il en charge hCaptcha si un site marchand en affiche un ?
Non — hCaptcha n'est pas pris en charge, pas plus que FunCaptcha (Arkose Labs) ; GeeTest v4 est annoncé comme à venir. Sont couverts : CAPTCHA image/OCR, grilles d'images, reCAPTCHA v2 et v3, Cloudflare Turnstile, Cloudflare Challenge et GeeTest v3, plus CaptchaFox (bêta), Friendly Captcha (bêta) et Lemin (bêta).
Que faut-il vérifier côté RGPD sur une veille tarifaire ?
Restez sur des données produit publiques, sans contenus nominatifs (avis signés compris). Journalisez les URL et les horodatages, pas les identités, et validez vos obligations avec votre référent conformité.