Scraper quelques centaines de pages, c'est un script. Scraper des millions, c'est un système. Dès que le nombre de cibles passe de « tourne sur mon portable une nuit » à « doit se terminer cette semaine sans tout faire fondre », la partie difficile n'est plus le parsing mais tout ce qui l'entoure : comment mettre le travail en file, le distribuer sur des workers, éviter les blocages, réessayer les échecs, stocker le résultat et le vérifier. Voici le guide de référence du scraping web à grande échelle vu comme une architecture, pas comme un extrait de code.
Il se concentre sur les données publiques collectées en volume : listes de produits, prix, résultats de recherche, profils publics et similaires. La forme du travail est la même quelle que soit la source, donc l'accent est mis sur le pipeline et les compromis à chaque étape, en signalant au passage les parties que vous ne devriez pas construire vous-même.
Ce que signifie réellement le scraping web à grande échelle
Le scraping web à grande échelle est la pratique consistant à extraire des données de millions de pages, sur un seul site énorme ou sur des milliers de sites plus petits à la fois. Le saut depuis le scraping ordinaire n'est pas qu'un nombre plus grand, et un chiffre le rend concret. Imaginez une catégorie avec 20 000 pages de listes, 20 éléments chacune, soit 400 000 pages à récupérer. À un rythme réaliste de 2,5 secondes par page, une exécution strictement séquentielle représente environ 1 000 000 secondes, soit environ 11,5 jours d'attente sur les chargements de pages avant de parser un seul champ. À partir de là, les chiffres sont illustratifs, mais ils reflètent le bon ordre de grandeur. Ce chiffre est la raison d'être de cet article : à grande échelle, le temps est la contrainte, et la concurrence est ce qui permet de le récupérer. Traitez 200 pages en parallèle et ces 11,5 jours s'effondrent vers une heure de temps d'horloge murale.
L'architecture en un coup d'œil
Un scraper qui survit à des millions de pages est un petit système distribué avec une poignée de composants nommés. Chacun résout un problème qui n'apparaît qu'au volume.
- Une file d'attente contient les URLs encore à récupérer et découple la découverte du travail, pour que producteurs et consommateurs avancent à leur propre rythme.
- Des workers asynchrones ou distribués tirent de la file et font le travail de récupération en parallèle. C'est là que viennent les économies de temps d'horloge murale.
- Un layer de proxy et anti-bot fait tourner les IPs et présente un trafic que les cibles lisent comme un vrai navigateur, pour qu'aucune adresse ne déclenche de limite de débit.
- Le rendu, uniquement quand nécessaire, exécute un navigateur headless pour les pages lourdes en JavaScript et l'ignore pour les pages statiques, car le rendu est l'opération la plus coûteuse possible.
- Les tentatives avec backoff capturent les échecs transitoires qui sont garantis à ce volume.
- La déduplication vous empêche de récupérer ou de stocker deux fois la même URL.
- Le stockage prend les lignes parsées et les place quelque part de requêtable.
- Le monitoring et les contrôles de qualité des données vous indiquent que l'exécution est saine et que la sortie est fiable.
Les sections ci-dessous les abordent dans l'ordre. Le fil conducteur de toutes est un compromis entre contrôle et charge opérationnelle : construire chaque couche soi-même, ou confier les plus difficiles (proxies, anti-bot, rendu, tentatives) à une couche gérée et consacrer son temps aux données.
La file d'abord : découpler la découverte de la récupération
La décision structurelle la plus importante est de placer une file entre « quoi scraper » et « faire le scraping ». Un producteur énumère les URLs (depuis un sitemap, un crawl de résultats de recherche, ou une base de données d'identifiants) et les pousse dans la file ; un pool de workers en tire. Ni l'un ni l'autre n'a besoin de savoir à quelle vitesse l'autre avance, et vous pouvez ajouter des workers sans toucher au producteur.
En Python c'est couramment Celery ou RQ sur Redis ; en Node une file Bull ou BullMQ sur Redis ; à plus grande échelle un vrai broker comme RabbitMQ ou Kafka. Un sketch minimal de worker rend le schéma concret.
import asyncio import aiohttp CONCURRENCY = 50 queue = asyncio.Queue() async def worker(name, session): while True: url = await queue.get() try: async with session.get(url) as resp: html = await resp.text() parse_and_store(url, html) except Exception as err: print(f'failed {url}: {err}') finally: queue.task_done() async def run(urls): for u in urls: queue.put_nowait(u) async with aiohttp.ClientSession() as session: workers = [asyncio.create_task(worker(i, session)) for i in range(CONCURRENCY)] await queue.join() for w in workers: w.cancel()
C'est toute l'idée en un seul fichier : un pool borné de workers concurrents qui vide une file partagée. Le paramètre qui compte est CONCURRENCY. Trop bas, on gaspille le parallélisme qui rend l'échelle possible ; trop haut, on surcharge à la fois la cible et sa propre bande passante sortante. On trouve la bonne valeur en observant la montée des taux d'erreur, ce qui explique exactement pourquoi le monitoring est une partie de premier ordre du système.
La concurrence asynchrone (une machine, de nombreuses requêtes en vol) et les workers distribués (plusieurs machines) résolvent des plafonds différents. L'asynchrone vous tire bon marché du sol d'une requête à la fois. Les workers distribués vous font dépasser les limites d'une seule machine : CPU pour le rendu, mémoire pour le parsing, et bande passante sortante. La plupart des grands jobs utilisent les deux : asynchrone au sein de chaque worker, plusieurs workers sur plusieurs machines.
Rotation de proxy et anti-bot : la partie qui cède en premier
À faible volume, on remarque à peine les défenses anti-bot ; à grande échelle, elles font céder l'exécution en premier. Envoyez quelques centaines de milliers de requêtes depuis une seule IP et vous êtes soumis à des limites de débit, puis à des challenges, puis bloqué. La solution est la rotation : répartir les requêtes sur de nombreuses adresses pour qu'aucune ne semble abusive.
Le type de proxy compte. Les IPs de datacenter sont bon marché et rapides mais faciles à identifier et à bloquer en masse. Les proxies résidentiels passent par de vraies connexions grand public et apparaissent comme des utilisateurs ordinaires, ce que les cibles commerciales difficiles attendent. Pour la plupart des grands jobs, le bon choix par défaut est un pool de proxies résidentiels rotatifs, où chaque requête ou courte session part d'une nouvelle IP d'utilisateur réel. Si vous montez cela vous-même, bien implémenter la logique de rotation (sessions persistantes quand le site en a besoin, IPs fraîches sinon) représente l'essentiel du travail ; voir comment utiliser les proxies rotatifs.
La rotation est nécessaire mais pas suffisante. Les défenses modernes lisent aussi les empreintes TLS, l'ordre des en-têtes et le comportement du navigateur. Une couche gérée comme le Smart AI Proxy de Crawlbase combine rotation et gestion des empreintes en un seul endpoint : vous pointez votre client HTTP existant sur une URL de proxy et il gère le pool, les en-têtes et les tentatives sur blocages en arrière-plan. Pour le guide défensif complet, voir comment scraper des sites web sans se faire bloquer.
Ne rendez que quand vous y êtes obligé
Rendre une page dans un navigateur headless est l'opération la plus coûteuse du pipeline : elle coûte du CPU, de la mémoire et des secondes par page, et sur un million de pages ces secondes dominent tout. Donc ne rendez que quand les données le requièrent vraiment.
De nombreux sites envoient encore leurs données dans le HTML initial, ou les exposent via un endpoint JSON que la page appelle. Pour ceux-là, une simple requête HTTP plus un parser est un ordre de grandeur moins cher qu'un navigateur. Réservez le rendu aux pages qui construisent le contenu côté client, où une récupération brute renvoie une coquille vide. La discipline est simple : essayez d'abord le chemin bon marché, confirmez que les champs sont présents, et montez au rendu seulement pour les pages qui en ont besoin. Mélanger les deux dans un seul run (récupération statique pour les pages de catalogue, rendu pour la poignée de pages détaillées lourdes en JS) est normal et c'est là que se trouvent les économies.
La couche d'échelle gérée : Crawling API et le Crawler asynchrone
Les proxies, l'anti-bot et le rendu sont les trois couches les plus difficiles à construire et à maintenir en bonne santé, et c'est exactement ce que Crawlbase gère pour vous. La Crawling API est un appel unique qui récupère une URL derrière des IPs résidentielles rotatives, gère le challenge anti-bot, rend optionnellement la page dans un vrai navigateur, et retourne du HTML terminé. Vous décidez par requête si vous souhaitez le rendu en ajoutant un token JavaScript ; les pages statiques restent bon marché et les pages lourdes en JS bénéficient d'un navigateur.
from crawlbase import CrawlingAPI api = CrawlingAPI({ 'token': 'YOUR_CRAWLBASE_TOKEN' }) options = { 'ajax_wait': 'true', 'page_wait': 3000, 'country': 'US', } resp = api.get('https://www.example.com/products?page=42', options) if resp['status_code'] == 200: parse_and_store(resp['body'])
Les appels synchrones s'intègrent parfaitement dans le pool de workers ci-dessus : chaque worker appelle api.get et l'API absorbe les préoccupations de proxy, d'anti-bot et de rendu. Mais pour les très grands jobs, il existe un meilleur schéma. Le Crawler asynchrone inverse le flux : au lieu de maintenir une connexion ouverte le temps que chaque page soit récupérée, vous poussez des URLs vers lui et il les crawle selon son propre planning, puis POSTe chaque page terminée vers un endpoint webhook que vous contrôlez. Vous ajoutez deux paramètres à l'appel de la Crawling API, &callback=true&crawler=YourCrawlerName, et le Crawler prend en charge la mise en file, l'ordonnancement et les tentatives.
from crawlbase import CrawlingAPI api = CrawlingAPI({ 'token': 'YOUR_CRAWLBASE_TOKEN' }) # Push as many URLs as you like; the Crawler queues and crawls them async, # then POSTs each finished page to the webhook on your registered crawler. for url in urls_to_crawl: api.get(url, { 'callback': 'true', 'crawler': 'my-products-crawler', })
Le modèle asynchrone est le bon choix pour des millions de pages car il supprime la partie du système que vous auriez sinon à surveiller en permanence. Vous ne maintenez pas de connexions ouvertes, ne gérez pas une flotte de rendu, ni une file de tentatives ; vous poussez et vous recevez. Le Crawler surveille même votre webhook : si votre endpoint tombe, il fait une pause, vous notifie, réessaie la livraison échouée et reprend automatiquement quand votre serveur est de retour. C'est la mise en file, l'ordonnancement, les tentatives et la fiabilité de livraison gérés comme une couche managée, ce qui représente l'essentiel de ce que le reste de cet article vous dit de construire.
L'échelle, c'est surtout les parties qui ne sont pas amusantes à construire : IPs résidentielles rotatives, anti-bot, rendu headless, files et tentatives. La Crawling API plie les trois premières en un seul appel, et le Crawler asynchrone prend vos URLs poussées, les crawle selon son propre planning et POSTe les pages terminées vers votre webhook avec des tentatives automatiques. Pointez-le sur une cible publique avec le niveau gratuit d'abord.
Tentatives et backoff : l'échec est l'état normal
Sur un million de requêtes, un taux d'échec transitoire de 1 % représente 10 000 pages échouées. L'échec n'est pas un cas limite à ce volume ; c'est l'état normal, et votre pipeline doit traiter une récupération échouée comme une routine plutôt que comme fatale. Le schéma est de réessayer avec un backoff exponentiel et un plafond : attendre un peu, puis plus, puis encore plus, et après quelques tentatives déplacer l'URL dans une file de lettres mortes au lieu de bloquer l'exécution.
La nuance est de lire pourquoi une requête a échoué, car tous les échecs ne méritent pas une nouvelle tentative. Un timeout ou un 503 vaut la peine d'être réessayé ; un 404 dur non. Avec du trafic proxifié, vous obtenez aussi des signaux de statut spécifiques au proxy qui vous indiquent s'il faut reculer, faire tourner, ou monter de niveau d'IP ; les traiter comme des signaux plutôt que comme du bruit garde une longue exécution saine. Voir comment résoudre les codes d'erreur de statut de proxy pour le mapping complet. Une couche gérée réessaie les blocages en interne, mais vous êtes toujours responsable des tentatives pour votre propre logique et stockage.
Déduplication : ne crawlez pas la même page deux fois
La découverte à grande échelle produit constamment des doublons : le même produit accessible depuis trois chemins de catégorie, des paramètres de tracking qui font apparaître une page comme dix, une pagination qui boucle. Sans déduplication, vous gaspillez du budget à récupérer des pages et corrompez votre dataset avec des lignes répétées.
Deux couches s'en chargent. D'abord, normalisez les URLs avant qu'elles entrent dans la file : supprimez les paramètres de tracking, mettez l'hôte en minuscules, triez les clés de requête, résolvez les liens relatifs en forme canonique. Ensuite, maintenez un ensemble des URLs vues (un ensemble Redis, ou un filtre de Bloom pour les très grandes exécutions) et ignorez toute URL déjà présente. Un filtre de Bloom échange un minuscule taux de faux positifs contre une économie de mémoire massive, le bon compromis quand les ensembles vus atteignent des centaines de millions. Déduplication aussi la sortie : identifiez les lignes sur un identifiant stable pour qu'une page récupérée deux fois ne devienne pas deux enregistrements.
Stockage : faites correspondre le store au pattern d'accès
L'endroit où les données atterrissent dépend de ce que vous en faites ensuite. Les fichiers plats (CSV, JSONL) ou le stockage objet conviennent à l'archivage en ajout intensif et au traitement en masse bon marché. Une base de données relationnelle convient quand vous devez requêter, joindre et mettre à jour des lignes. Un store de documents convient aux enregistrements semi-structurés dont la forme varie selon la source. L'erreur est de tout forcer dans l'un d'eux parce que c'était le premier sous la main.
Deux habitudes spécifiques à l'échelle comptent. Écrivez par lots, pas une ligne par requête, pour que le stockage ne soit pas votre goulot d'étranglement ; le worker doit bufferiser et purger. Et séparez le brut du parsé : gardez le HTML original (ou une référence à lui) pour pouvoir re-parser sans re-crawler quand les sélecteurs changent ou que vous trouvez un nouveau champ. Crawlbase peut livrer les pages directement vers le Cloud Storage ou votre webhook, supprimant complètement la plomberie d'ingestion de votre côté.
Monitoring et qualité des données
Une grande exécution est opaque sans instrumentation. Vous voulez des compteurs en direct pour les pages récupérées, le taux de succès, le taux d'erreur par type, la profondeur de file et le débit, pour pouvoir voir une tempête de blocage ou une file bloquée pendant qu'elle se produit plutôt que dans le dataset vide de demain. La profondeur de file qui monte pendant que le débit chute signifie que les workers sont bloqués ; un pic de challenges signifie qu'il est temps de reculer ou de faire tourner plus fort.
La qualité des données est la moitié du monitoring que les équipes ignorent, et la moitié qui détermine si les données sont utilisables. Une exécution peut rapporter 100 % de succès HTTP et produire quand même des ordures si la mise en page a changé et que vos sélecteurs ne correspondent plus à rien. Ajoutez des contrôles bon marché et continus : vérifiez que les champs obligatoires ne sont pas vides, que les prix se parsent en nombres dans une plage raisonnable, que le nombre de lignes par page est à peu près ce que vous attendez. Quand un contrôle échoue sur de nombreuses pages à la fois, le balisage a dérivé et votre parser a besoin d'attention. Mieux vaut le détecter à la page 5 000 qu'après avoir stocké cinq millions de lignes vides.
Où tracer la ligne construire vs acheter
Tout ce qui précède est constructible, donc la question honnête est de savoir quelles parties valent votre temps d'ingénierie. Le modèle de données, la logique de parsing, les contrôles de qualité et le schéma de stockage sont spécifiques à votre projet, et seul vous pouvez les construire bien. Le pool de proxies, la gestion anti-bot, la flotte de rendu headless et la file de tentatives et de livraison asynchrone sont une infrastructure générique coûteuse à construire et pénible à maintenir en bonne santé à mesure que les cibles évoluent. C'est là que se situe une couche d'échelle gérée : utilisez la Crawling API ou le Crawler asynchrone pour la récupération, le rendu et l'anti-bot ; le Smart AI Proxy pour garder votre propre client et permuter vers un endpoint rotatif géré ; ou la Crawling API pour du JSON parsé depuis les sites supportés afin de sauter entièrement les sélecteurs. Consacrez votre temps aux données ; louez les parties qui sont les mêmes pour tout le monde.
Points clés
- L'échelle, c'est la concurrence, pas une boucle plus grande. Une exécution séquentielle d'un million de pages prend des jours ; une file alimentant des workers asynchrones ou distribués réduit ça à des heures.
- Les proxies et l'anti-bot cèdent en premier. Faites tourner via des IPs résidentielles et présentez du trafic de navigateur réel, ou laissez une couche gérée s'occuper de la rotation et des empreintes pour vous.
- Ne rendez que quand vous devez. Le rendu headless est l'étape la plus coûteuse par page ; essayez d'abord une récupération statique et montez seulement pour les pages côté client.
- L'échec est l'état normal. Réessayez les erreurs transitoires avec backoff et une file de lettres mortes, dédupliquez les URLs et les lignes, et lisez les codes de statut de proxy comme des signaux.
- L'asynchrone bat le synchrone au sommet. Poussez des URLs vers le Crawler et recevez les résultats sur un webhook, pour que la mise en file, l'ordonnancement, les tentatives et la livraison soient gérés pour vous.
- Monitorez le succès et la qualité des données. 100 % de succès HTTP avec des champs vides est quand même une exécution échouée ; vérifiez les données, pas seulement le code de statut.
Foire aux questions
Qu'est-ce qui compte comme scraping web à grande échelle ?
En gros, tout job assez grand pour qu'un seul script séquentiel ne soit plus viable, ce qui signifie en pratique des centaines de milliers à des millions de pages, sur un grand site ou sur de nombreux plus petits. Le trait distinctif n'est pas le nombre mais le fait que vous ayez désormais besoin de concurrence, de rotation de proxy, de tentatives et de monitoring pour terminer dans un temps raisonnable sans être bloqué. En dessous de ce seuil, une simple boucle suffit ; au-dessus, vous gérez un petit système distribué.
Comment scraper des millions de pages sans se faire bloquer ?
Répartissez les requêtes sur de nombreuses IPs pour qu'aucune adresse ne semble abusive, préférez les proxies résidentiels rotatifs pour les cibles difficiles, présentez un trafic qui se lit comme un vrai navigateur, réglez le rythme de vos requêtes et reculez quand des challenges apparaissent. Construire tout ça soi-même est un travail considérable, donc la plupart des équipes routent via une couche gérée comme la Crawling API ou le Smart AI Proxy qui gère la rotation, les empreintes et la résolution de challenges derrière un seul endpoint.
Dois-je utiliser le scraping synchrone ou asynchrone à grande échelle ?
Asynchrone, pour tout ce qui est vraiment grand. La récupération synchrone maintient une connexion ouverte par page et bloque un worker jusqu'à ce que chaque requête se termine. Le modèle du Crawler asynchrone vous permet de pousser des URLs et de recevoir les pages terminées sur un callback webhook, pour que la mise en file, l'ordonnancement et les tentatives se produisent côté serveur et que votre application ne soit pas bloquée à attendre. Vous poussez et vous recevez, ce qui est bien plus facile à faire évoluer et à opérer.
Ai-je toujours besoin d'un navigateur headless pour le scraping à grande échelle ?
Non, et évitez-le où vous pouvez. Le rendu est l'étape la plus coûteuse par page, donc réservez-le aux sites qui construisent du contenu côté client et renvoient une coquille vide à une récupération simple. De nombreux sites envoient des données utilisables dans le HTML initial ou exposent un endpoint JSON, tous deux bien moins chers à récupérer. Mélanger un chemin statique bon marché avec le rendu seulement pour les pages qui en ont besoin est la valeur par défaut rentable.
Comment gérer les échecs et les doublons sur des millions de requêtes ?
Traitez les deux comme des éléments de routine. Réessayez les échecs transitoires (timeouts, 503) avec un backoff exponentiel et un plafond, puis envoyez les récalcitrants dans une file de lettres mortes au lieu de bloquer l'exécution ; ne réessayez pas les 404 durs. Pour les doublons, normalisez les URLs avant la mise en file, maintenez un ensemble des URLs vues ou un filtre de Bloom pour ignorer les URLs déjà récupérées, et identifiez les lignes stockées sur un identifiant stable pour qu'une page récupérée deux fois ne devienne pas deux enregistrements.
Où stocker les données d'un grand scrape ?
Faites correspondre le store à comment vous utiliserez les données : stockage objet ou JSONL pour l'archivage bon marché, une base de données relationnelle quand vous avez besoin de requêter et joindre, un store de documents pour les enregistrements à forme variable. Écrivez par lots plutôt qu'une ligne par requête pour que le stockage ne soit pas le goulot d'étranglement, et gardez le HTML brut aux côtés de la sortie parsée pour pouvoir re-parser sans re-crawler quand les sélecteurs changent ou que vous ajoutez un champ.
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.
