Scraper une page, c'est facile. Scraper des milliers de pages sur de nombreux sites différents en une seule exécution, c'est là que la plupart des configurations s'effondrent : une boucle simple se bloque sur chaque requête, les adresses IP sont bannies quelques centaines d'appels plus loin, les URL en double gaspillent des crédits, et une seule page lente bloque tout le travail. Le problème n'est pas l'analyse. C'est le débit, le blocage et la tenue des registres à grande échelle.
Ce guide vous montre comment scraper plusieurs sites web à la fois en Python comme un vrai travail en production. Vous utiliserez l'API Crawling pour récupérer et rendre chaque page derrière un vrai navigateur et une IP de confiance, et le Crawler asynchrone pour pousser des milliers d'URL sur une file d'attente gérée qui les crawle en concurrence et livre les résultats à un webhook. Nous couvrirons la mise en file de nombreuses URL, le contrôle de la concurrence, la déduplication du travail et la collecte des lignes en un seul endroit.
Pourquoi une seule boucle ne passe pas à l'échelle
Un premier scraper est presque toujours une boucle : lire une URL, la récupérer, l'analyser, la sauvegarder, répéter. Ça fonctionne pour dix pages et ça casse à dix mille. Chaque récupération bloque la suivante, donc le temps total est la somme de chaque requête. Envoyez ces requêtes depuis une seule IP et un site commence à renvoyer des 403 et des CAPTCHAs au bout de quelques centaines d'appels. Et rien dans la boucle ne remarque que vous avez déjà crawlé la moitié de la liste hier, donc vous payez pour récupérer à nouveau des pages que vous avez déjà.
Passer à l'échelle sur de nombreux sites à la fois signifie résoudre trois problèmes distincts. Vous avez besoin de concurrence, pour que les pages lentes ne bloquent pas les rapides. Vous avez besoin de déblocage, pour que la rotation d'IP et le rendu dans un vrai navigateur vous maintiennent hors de la liste de bannissement. Et vous avez besoin de tenue des registres, pour que les URL en double soient ignorées et que les résultats terminés atterrissent dans un seul dépôt. Le reste de ce guide associe chaque problème à un outil et montre le code.
Gardez la limite claire. L'API Crawling récupère et rend une page par appel : elle exécute le JavaScript, fait tourner l'IP et renvoie du HTML terminé. Le Crawler asynchrone est la file d'attente par-dessus : vous poussez de nombreuses URL, il les crawle en concurrence, réessaie les échecs et POST chaque résultat vers un webhook que vous hébergez. Utilisez l'API pour un lot borné que vous attendez, le Crawler pour un grand travail de type "lancer et collecter".
Ce que vous allez construire
Deux schémas exécutables sur une liste d'URL couvrant différents sites. Premièrement, un lot concurrent qui boucle un ensemble d'URL dédupliqué à travers l'API Crawling et écrit chaque résultat dans un fichier JSON, ce qui est la bonne forme pour des centaines à quelques milliers de pages que vous souhaitez avoir en main à la fin de l'exécution. Deuxièmement, un push asynchrone vers le Crawler pour les travaux qui atteignent les dizaines de milliers, où bloquer sur chaque récupération n'est plus une option. Les deux utilisent le client Python officiel crawlbase.
Configurer l'environnement
Vous avez besoin de Python 3.8 ou supérieur. Confirmez votre version, créez un environnement virtuel pour que les dépendances restent isolées, puis installez le client.
python --version python -m venv scrape_env source scrape_env/bin/activate pip install crawlbase
Sur Windows, activez l'environnement avec scrape_env\Scripts\activate au lieu de la ligne source. Le package crawlbase est le client officiel et enveloppe à la fois l'API Crawling et le Crawler asynchrone, vous n'assemblez donc pas des appels HTTP manuellement. Récupérez deux tokens depuis votre tableau de bord Crawlbase après inscription : un token normal pour les pages statiques et un token JavaScript (JS) pour les pages rendues côté client. Lisez-les depuis des variables d'environnement plutôt que de les coder en dur.
export CRAWLBASE_TOKEN=your_normal_token_here export CRAWLBASE_JS_TOKEN=your_js_token_here
Le token normal récupère le HTML statique et est moins cher et plus rapide. Le token JS rend la page dans un vrai navigateur d'abord, ce dont vous avez besoin pour tout site qui charge du contenu côté client. Quand vous scrapez sur de nombreux sites différents à la fois, vous rencontrerez les deux types, donc un schéma courant est d'utiliser le token JS par défaut et de passer au normal pour les cibles que vous savez être statiques.
Construire un ensemble d'URL dédupliqué
Avant toute récupération, nettoyez l'entrée. Une vraie liste de cibles, assemblée depuis des sitemaps, des pages de catégories et des exécutions précédentes, est pleine de doublons et d'entrées périmées. Dédupliquer en amont est la seule optimisation la moins chère que vous puissiez faire, car la requête que vous n'envoyez jamais ne coûte rien. Normalisez chaque URL et gardez un ensemble de ce que vous avez déjà crawlé.
import json import os from urllib.parse import urlparse, urlunparse def normalize(url): parts = urlparse(url.strip()) # Drop fragments and trailing slashes so near-duplicates collapse. path = parts.path.rstrip("/") or "/" return urlunparse((parts.scheme, parts.netloc, path, "", parts.query, "")) raw_urls = [ "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html", "https://books.toscrape.com/catalogue/tipping-the-velvet_999/index.html", "https://quotes.toscrape.com/page/1/", "https://quotes.toscrape.com/page/1/#top", # duplicate after normalizing ] targets = sorted({normalize(u) for u in raw_urls}) print(f"{len(targets)} unique URLs to crawl")
La compréhension d'ensemble réduit les URL exactes et quasi-dupliquées en une seule ligne. Pour une exécution qui reprend sur plusieurs jours, persistez l'ensemble crawlé sur disque et soustrayez-le de targets au début de chaque exécution pour ne jamais récupérer à nouveau une page que vous avez déjà. C'est la couche de tenue des registres qui fait son travail avant que vous ne dépensiez un seul crédit.
Scraper le lot en concurrence avec l'API Crawling
Récupérez maintenant l'ensemble. La version naïve est une boucle sérielle, mais le sériel est exactement ce qui ne passe pas à l'échelle, donc exécutez les requêtes via un pool de threads. Chaque appel à l'API Crawling est lié aux I/O, en attente du réseau, ce qui est le cas qu'un pool de threads gère bien. Un nombre modeste de workers maintient de nombreuses requêtes en vol sans trop marteler un seul site.
from concurrent.futures import ThreadPoolExecutor, as_completed from crawlbase import CrawlingAPI api = CrawlingAPI({"token": os.environ["CRAWLBASE_JS_TOKEN"]}) def fetch(url): options = {"ajax_wait": "true", "page_wait": 2000} response = api.get(url, options) status = response["headers"].get("cb_status") return { "url": url, "status": status, "html": response["body"].decode("utf-8", "ignore"), } results = [] with ThreadPoolExecutor(max_workers=10) as pool: futures = {pool.submit(fetch, u): u for u in targets} for future in as_completed(futures): url = futures[future] try: results.append(future.result()) except Exception as err: print(f"Failed {url}: {err}") print(f"Collected {len(results)} pages")
Deux détails rendent cela robuste. L'en-tête cb_status (legacy pc_status) porte le statut original renvoyé par la cible, vous pouvez donc distinguer un vrai 200 d'un échec doux et décider si vous gardez la ligne. Et envelopper future.result() dans un try/except signifie qu'une mauvaise URL se consigne et passe à la suivante au lieu de tuer tout le lot. L'API Crawling gère le rendu et la rotation d'IP par appel, donc la seule chose que votre code gère est la concurrence.
Un seul appel récupère et rend une page derrière un vrai navigateur et une IP résidentielle rotative, donc un lot sur de nombreux sites différents reste non bloqué sans que vous fassiez tourner une flotte headless ou un pool de proxies. Commencez avec une page publique sur le niveau gratuit, puis mettez à l'échelle la même boucle sur des milliers d'URL.
Analyser et sauvegarder les résultats collectés
Vous avez maintenant du HTML brut pour chaque page dans results. L'analyse varie par site, mais l'étape de collecte est la même : extrayez les champs que vous voulez et écrivez un enregistrement structuré par page. Gardez l'URL et un horodatage de capture sur chaque ligne pour que la sortie double comme journal d'audit de ce qui a été exécuté et quand.
from datetime import datetime, timezone from bs4 import BeautifulSoup def parse_title(html): soup = BeautifulSoup(html, "html.parser") title = soup.find("title") return title.text.strip() if title else None rows = [] for item in results: if item["status"] != "200": continue rows.append({ "url": item["url"], "title": parse_title(item["html"]), "captured_at": datetime.now(timezone.utc).isoformat(), }) with open("scraped.json", "w") as f: json.dump(rows, f, indent=2) print(f"Wrote {len(rows)} rows to scraped.json")
Cela installe beautifulsoup4 aux côtés du client (pip install beautifulsoup4). Ignorer les lignes qui n'ont pas renvoyé un 200 propre maintient les échecs doux, un corps vide ou une page de challenge, hors de votre jeu de données, ce qui est le genre de corruption silencieuse qui empoisonne tranquillement un grand crawl. Pour des cibles bien connues comme les grands détaillants ou les marketplaces, vous pouvez sauter complètement l'analyse manuelle et laisser l'Crawling API renvoyer du JSON pré-analysé à la place.
Passer à l'échelle au-delà du lot avec le Crawler asynchrone
Le lot à pool de threads est le bon outil jusqu'à quelques milliers d'URL que vous souhaitez attendre. Au-delà, bloquer votre processus pendant que des dizaines de milliers de pages crawlent n'est plus pratique, et c'est là que le Crawler asynchrone prend le relais. C'est une file d'attente gérée basée sur le push : vous soumettez des URL via le même client, chacune reçoit un ID de requête, le système les crawle en concurrence et réessaie les échecs pour vous, puis POST chaque page terminée vers un webhook sur votre serveur.
from crawlbase import CrawlingAPI crawler = CrawlingAPI({"token": os.environ["CRAWLBASE_JS_TOKEN"]}) # Push each URL to the async Crawler; results arrive at your webhook. for url in targets: response = crawler.post(url, { "callback": "https://your-app.example.com/webhook", "callback_headers": "X-Job-Id:bulk-run-01", }) body = json.loads(response["body"]) print(f"Queued {url} as request {body['rid']}")
Chaque post renvoie un ID de requête (rid) que vous pouvez journaliser pour suivre le travail. Le Crawler crawle la file d'attente en arrière-plan avec sa propre concurrence et sa logique de réessai, donc votre script se termine au moment où chaque URL est soumise au lieu d'attendre le crawl. Quand une page est terminée, le système POST le résultat vers votre URL de callback, et le champ callback_headers vous permet de taguer une exécution pour que le gestionnaire de réception sache à quel travail une livraison appartient.
Collecter les livraisons
Le modèle asynchrone inverse la collecte : au lieu de tirer des pages, vous les recevez. Votre webhook exécute la même logique d'analyse et de sauvegarde de la version par lot, seul le déclencheur change. Un gestionnaire minimal dans Flask ressemble à ceci.
from flask import Flask, request app = Flask(__name__) @app.route("/webhook", methods=["POST"]) def webhook(): rid = request.headers.get("rid") original_url = request.headers.get("original_url") html = request.get_data(as_text=True) row = { "url": original_url, "title": parse_title(html), "captured_at": datetime.now(timezone.utc).isoformat(), } with open("bulk.jsonl", "a") as f: f.write(json.dumps(row) + "\n") return "", 200
Ajouter à un fichier JSON Lines signifie que chaque livraison est une écriture autonome, donc les callbacks concurrents ne s'écrasent jamais mutuellement comme le ferait un tableau JSON re-sérialisé. Le Crawler livre l'URL originale et l'ID de requête dans les en-têtes de réponse, donc le même parse_title et la même forme de ligne de la version par lot s'appliquent directement. C'est ce qui permet au pipeline de passer de quelques milliers de pages à des centaines de milliers sans que votre processus ne reste jamais en attente.
En volume, vous ne pouvez pas surveiller un crawl à l'oeil nu, donc appuyez-vous sur la surveillance intégrée. Le tableau de bord Crawlbase suit le volume des requêtes, les taux de succès et d'échec, et les crédits utilisés, et le moniteur en direct affiche la profondeur de la file en temps réel. Une hausse progressive des échecs signifie généralement qu'une cible a commencé à challenger le trafic, et vous voulez le détecter en quelques minutes, pas après la fin d'une exécution avec la moitié des lignes manquantes.
Concurrence, limites de débit et rester non bloqué
Plus de workers n'est pas toujours plus rapide. Poussez la concurrence trop haut et vous épuisez soit le débit de requêtes de votre plan, soit vous martelez un seul domaine assez fort pour déclencher ses défenses, ce qui ralentit l'exécution avec des réessais. La solution est de contrôler la concurrence par domaine plutôt que globalement : dix requêtes en vol réparties sur dix sites est doux, tandis que dix contre un seul site est agressif. Regroupez votre ensemble d'URL par hôte et limitez le nombre que vous maintenez en vol contre l'un d'eux.
Comme l'API Crawling et le Crawler font tous deux tourner des IP résidentielles et rendent derrière un vrai navigateur côté serveur, la partie la plus difficile pour rester non bloqué est gérée pour vous. Si vous préférez router votre propre client via un pool rotatif, le Smart AI Proxy vous donne la même rotation d'IP résidentielle comme endpoint proxy plug-and-play. Dans tous les cas, cadencez les requêtes, variez les cibles et surveillez les codes de statut pour pouvoir reculer à l'instant où un site commence à repousser. Le guide complet se trouve dans comment scraper des sites web sans être bloqué.
Scraper de manière responsable
Scraper à grande échelle est une responsabilité, pas seulement une capacité. Restez sur les données publiquement disponibles ; ne scrapez pas de contenu derrière une connexion, des matériaux payants, ou quoi que ce soit de personnel ou protégé par le droit d'auteur sans un droit clair. Lisez le robots.txt et les conditions d'utilisation de chaque site, et honorez les règles d'accès qu'ils indiquent. Et limitez vous-même votre débit : espacer les requêtes et plafonner la concurrence par domaine vous maintient hors des listes de blocage et réduit votre charge sur les serveurs d'un site. La retenue n'est pas seulement le choix éthique, c'est l'opérationnel, car un travail qui respecte les limites reste en ligne bien plus longtemps qu'un qui ne le fait pas.
Points clés
- Divisez le problème. Scraper de nombreux sites à la fois représente trois problèmes, pas un : concurrence, déblocage et tenue des registres. Associez chacun à un outil au lieu de les entasser dans une boucle.
- Dédupliquez avant de récupérer. Normalisez les URL et ignorez celles que vous avez déjà crawlées, car la requête la moins chère est celle que vous n'envoyez jamais.
- Utilisez un pool de threads pour les lots bornés. L'appel à l'API Crawling est lié aux I/O, donc un pool modeste de workers collecte des centaines à quelques milliers de pages bien plus vite qu'une boucle sérielle.
- Poussez vers le Crawler asynchrone à grande échelle. Pour des dizaines de milliers d'URL, soumettez-les à la file d'attente et recevez les résultats à un webhook, pour que concurrence, réessais et surveillance viennent gratuitement.
- Contrôlez la concurrence par domaine. Répartissez la charge sur les hôtes et limitez les requêtes en vol par site pour rester non bloqué au lieu de déclencher des défenses.
- Scrapez de manière responsable. Données publiques uniquement, respectez robots.txt et les conditions d'utilisation, et limitez votre débit pour que le travail continue à fonctionner.
Foire aux questions
Comment scraper plusieurs sites web à la fois en Python ?
Construisez un ensemble dédupliqué d'URL, puis récupérez-les en concurrence plutôt qu'en boucle sérielle. Pour un lot borné, faites passer l'API Crawling via un ThreadPoolExecutor pour que les pages lentes ne bloquent pas les rapides, et collectez chaque résultat dans une liste que vous écrivez sur disque. Pour les très grands travaux, poussez plutôt les URL vers le Crawler asynchrone, qui les met en file d'attente et les crawle en arrière-plan et livre chaque page terminée à un webhook que vous hébergez.
Quelle est la différence entre l'API Crawling et le Crawler asynchrone ?
L'API Crawling est synchrone : vous envoyez une URL et attendez la page rendue dans la réponse, ce qui est idéal pour un seul scrape ou un petit lot concurrent. Le Crawler asynchrone est conçu pour la mise à l'échelle : vous poussez de nombreuses URL, il les crawle en arrière-plan avec sa propre concurrence et ses réessais, et il POST chaque résultat vers votre webhook. Les deux partagent le même backbone de rendu et d'anti-blocage, vous choisissez donc celui qui correspond à votre débit.
Comment éviter d'être bloqué en scrapant de nombreux sites ?
Faites tourner les IP et rendez les pages derrière un vrai navigateur, et cadencez vos requêtes pour ne pas surcharger un seul domaine. L'API Crawling et le Crawler gèrent la rotation d'IP et le rendu côté serveur, donc la plupart des blocages sont pris en charge. Si vous routez votre propre client, utilisez un endpoint rotatif comme le Smart AI Proxy, contrôlez la concurrence par domaine, et surveillez les codes de statut pour pouvoir reculer quand un site commence à challenger le trafic.
Comment gérer les URL en double sur des milliers de pages ?
Normalisez chaque URL en supprimant les fragments et les barres obliques finales, puis stockez-les dans un ensemble pour que les doublons exacts et quasi-exacts s'effacent automatiquement. Pour les exécutions qui reprennent dans le temps, persistez l'ensemble des URL déjà crawlées sur disque et soustrayez-le de votre liste de cibles au début de chaque exécution. Cette tenue des registres vous évite de payer pour récupérer à nouveau des pages que vous avez déjà.
Combien de requêtes concurrentes devrais-je lancer ?
Commencez modestement, environ dix workers, et ajustez en fonction du débit de requêtes de votre plan et de la façon dont les cibles répondent. Le nombre qui compte est la concurrence par domaine, pas le total global : dix requêtes réparties sur dix sites est doux, tandis que dix contre un seul site est agressif. Regroupez les URL par hôte et limitez le nombre que vous maintenez en vol contre un seul pour rester non bloqué.
Est-il légal de scraper des milliers de sites web ?
Scraper des données publiquement disponibles est généralement accepté, mais la légalité dépend des conditions d'utilisation de chaque site, du droit d'auteur et des lois sur la protection des données comme le RGPD et le CCPA. Restez sur les données publiques, évitez le contenu derrière des connexions ou des paywalls et tout ce qui est personnel ou protégé par le droit d'auteur, suivez robots.txt et limitez votre débit. En cas de doute sur une cible spécifique, vérifiez ses conditions et obtenez un avis juridique avant de lancer un grand travail contre elle.
Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.
Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.
