Comment scraper les résultats de recherche malgré un captcha (guide Python, 2026)
Les pages de résultats de recherche sont l'un des premiers endroits où un scraper rencontre un captcha. Le site voit une rafale de requêtes depuis une même adresse, présente un défi, et ne renvoie aucun résultat tant que le défi n'est pas réussi. Pour scraper les résultats de recherche malgré un captcha, votre script a besoin de trois étapes : détecter le défi avant l'analyse, le résoudre en un seul appel API, puis soumettre le jeton pour que le site renvoie la vraie page de résultats.
Ce guide montre ce flux en Python avec requests et BeautifulSoup. Il couvre les pages qui affichent un widget PopularCaptcha, qui est le type résolu aujourd'hui par Scarnox (sans proxy). Commencez avec un crédit d'essai gratuit, sans carte bancaire et exécutez d'abord le code sur une page qui vous appartient.
À quoi ressemble le défi
Les pages de recherche placent généralement le widget à l'intérieur d'un formulaire. Le conteneur du widget porte un attribut data-sitekey, et le formulaire renvoie les données vers le site :
<form action="/search/verify" method="post">
<input type="hidden" name="q" value="web scraping">
<input type="hidden" name="lang" value="en">
<div class="h-captcha" data-sitekey="YOUR-SITEKEY-UUID"></div>
<button type="submit">Continue</button>
</form>
Votre code a besoin de trois éléments de ce balisage : la sitekey, l'URL d'action du formulaire, et les champs cachés à renvoyer avec le jeton. La fonction ci-dessous lit les trois, donc elle fonctionne sans les coder en dur.
Si le widget de votre cible est un autre type de captcha, consultez les types de captcha. La prise en charge de reCAPTCHA et de Cloudflare Turnstile est bientôt disponible, donc le code ici ne couvre que PopularCaptcha.
L'appel au solveur
La requête de résolution part vers l'API Scarnox, avec votre clé dans un en-tête Bearer. L'appel bloque jusqu'à ce que le jeton soit prêt, il n'y a donc aucune boucle d'interrogation :
import time
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup
API_KEY = "scarnox_YOURAPIKEY"
SEARCH_URL = "https://search.example.com/search"
session = requests.Session()
session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"
)
def solve(sitekey: str, page_url: str) -> str:
r = requests.post(
"https://api.scarnox.com/api/tasks/create",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
},
timeout=185, # l'appel revient lorsque le jeton est prêt
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
Une résolution échouée renvoie un errorId différent de 0, et elle ne vous est pas facturée. Les nouvelles tentatives sont traitées dans gestion des erreurs de résolution de captcha.
Charger la page et passer le captcha
La fonction de récupération vérifie d'abord la présence du widget. S'il n'y en a pas, elle renvoie le HTML tel quel. S'il y en a un, elle résout le défi, reconstruit le formulaire avec les mêmes champs cachés, et l'envoie :
def fetch_results(query: str, page: int = 1) -> str:
resp = session.get(SEARCH_URL, params={"q": query, "page": page}, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
widget = soup.select_one("[data-sitekey]")
if widget is None:
return resp.text # pas de défi sur cette page
form = widget.find_parent("form")
if form is None:
raise RuntimeError("Captcha widget is not inside a form")
token = solve(widget["data-sitekey"], resp.url)
payload = {
field["name"]: field.get("value", "")
for field in form.select("input[name]")
}
payload["h-captcha-response"] = token
payload["g-recaptcha-response"] = token
action = urljoin(resp.url, form.get("action") or resp.url)
verified = session.post(action, data=payload, timeout=30)
if "data-sitekey" in verified.text:
raise RuntimeError("Challenge still shown after submit; token may be rejected")
return verified.text
Deux détails comptent ici. La site_url est l'URL sur laquelle le captcha est réellement servi (resp.url, qui tient compte des redirections), et non le simple point d'accès de recherche. Et la session conserve les cookies entre le GET et le POST, ce dont beaucoup de sites ont besoin pour accepter le jeton.
Les jetons expirent au bout d'environ deux minutes, c'est pourquoi la résolution se fait juste après la lecture du formulaire, et non avant. Pour en savoir plus sur cette fenêtre, voir expiration du jeton de captcha.
Analyser les résultats
Une fois la vraie page de résultats renvoyée, l'analyse est un travail HTML ordinaire. Les sélecteurs ci-dessous sont des exemples ; ouvrez la page dans les outils de développement et remplacez-les par les vrais :
def parse_results(html: str) -> list[dict]:
soup = BeautifulSoup(html, "html.parser")
rows = []
for item in soup.select("div.result"):
link = item.select_one("a[href]")
heading = item.select_one("h3")
if link and heading:
rows.append({
"title": heading.get_text(strip=True),
"url": link["href"],
})
return rows
if __name__ == "__main__":
for page in range(1, 4):
html = fetch_results("web scraping", page)
for row in parse_results(html):
print(row)
time.sleep(5) # espacer les requêtes entre les pages
Conservez la sortie de chaque page séparément. Si une page renvoie zéro résultat, vérifiez la présence du widget avant de supposer que la requête n'a aucun résultat.
Scraper de manière responsable
Un captcha est un signal, pas une faille. Avant d'exécuter ceci sur un site de recherche :
- Lisez le robots.txt et les conditions d'utilisation. De nombreux sites de recherche interdisent les requêtes automatisées. Si les conditions l'interdisent, n'exécutez pas le script.
- Limitez le débit. Le
time.sleep(5)ci-dessus est un plancher, pas un objectif. Ralentissez davantage si les défis deviennent plus fréquents. - Limitez ce que vous conservez. Les résultats de recherche peuvent contenir des noms, des adresses e-mail et d'autres données personnelles. Ne stockez que les champs dont vous avez besoin, et supprimez le reste.
- Privilégiez une API officielle lorsque le site en propose une. Elle est plus fiable et généralement moins coûteuse que le scraping HTML.
Pour le volet juridique, voir le web scraping est-il légal. Pour éviter les blocages en amont, voir scraper sans se faire bloquer.
Tarifs
Scarnox facture à chaque résolution réussie, et les résolutions échouées sont gratuites. Consultez les tarifs actuels pour les montants. Commencez avec un crédit d'essai gratuit, sans carte bancaire.