Notre position : nous sommes Geonode et nous vendons des proxys, qui servent à l'exploration de sites et non à la gestion de listes. En toute honnêteté, une liste de crawling mal gérée coûte bien plus cher qu’un forfait de proxys mal choisi. Un robot d’indexation sans normalisation des URL visite la même page des dizaines de fois avec des ordres de paramètres de requête différents, et avec une tarification au gigaoctet, vous payez pour chacune de ces visites. Un robot d’exploration sans budget suit un calendrier à l’infini et vous facture en conséquence. Corriger la liste est gratuit ; la corriger après la facturation ne l’est pas. Cette section se trouve ci-dessous, et c’est celle qu’il vaut la peine de lire en premier.
Qu'est-ce qu'une liste d'exploration, au juste ?
Trois éléments, souvent confondus.
La liste de départ — le point de départ. Une poignée d'URL ou un plan du site complet.
La frontière — les URL découvertes et mises en file d’attente, mais pas encore récupérées. Il s’agit de la structure de données active, celle qui intègre toutes les décisions de conception.
L’ensemble des pages visitées — les URL déjà récupérées, conservées pour éviter de les récupérer à nouveau.
Le cycle de vie est une boucle : on prend une URL dans la « frontière », on la récupère, on en extrait les liens, on les normalise, on élimine celles qui figurent déjà dans l’ensemble des URL visitées ou qui sont déjà en file d’attente, on ajoute le reste à la « frontière », puis on marque l’URL comme visitée. On répète l’opération jusqu’à ce que la « frontière » soit vide ou que le budget soit épuisé.
Simple en apparence. Chaque étape comporte un détail qui vous coûtera une journée si vous vous trompez.
Initialisation : d’où proviennent les premières URL ?
Classées par ordre décroissant de travail requis.
Le plan du site. La meilleure source d’initialisation disponible, car il s’agit de l’inventaire propre au site et qu’il contient des horodatages lastmod indiquant les modifications apportées. Retrouvez-le via la directive Sitemap: dans robots.txt, puis suivez les fichiers d’index du plan du site de manière récursive.
Un flux ou une API partenaire. S’il en existe un, vous n’aurez peut-être pas besoin de procéder à un crawl.
Les archives Web. L’API CDX d’Internet Archive renvoie les URL historiques d’un domaine, y compris les pages qui ne sont plus liées depuis nulle part. Cela ne coûte rien au site cible et permet de trouver des pages orphelines que le crawl ne peut pas détecter.
Les opérateurs de recherche. Les requêtes du type « site: » font apparaître les pages indexées et, ce qui est encore plus utile, les sous-domaines dont vous ignoriez l’existence.
Les pages de catégories et d’index. Pour un exploration ciblée, partir des pages de listes spécifiques qui vous intéressent est bien plus efficace que de commencer par la page d’accueil en espérant trouver ce que vous cherchez.
La page d’accueil. Le dernier recours, et la solution par défaut vers laquelle on se tourne en premier. Partir d’une seule URL et tout découvrir en parcourant les liens est la méthode la plus lente pour obtenir la pire couverture.
Nous avons passé en revue l’intégralité de la hiérarchie des sources dans comment trouver toutes les pages d’un site web. En résumé : une bonne liste de départ transforme un exploration en une simple récupération.
Normalisation des URL : l’étape que tout le monde néglige
C’est l’élément technique le plus important d’un robot d’indexation, et pourtant celui qui est le plus souvent omis.
Sans cela, il y a cinq URL différentes vers votre ensemble de pages visitées et une seule page pour le serveur :
http://Example.com/Products
http://example.com/products
http://example.com/products/
http://example.com:80/products
http://example.com/products?utm_source=email
La RFC 3986 définit les normalisations qui sont toujours sûres.
Normalisation de la casse. « Le schéma et l’hôte ne sont pas sensibles à la casse et doivent donc être normalisés en minuscules. Par exemple, l’URI <HTTP://www.EXAMPLE.com/>
est équivalent à <http://www.example.com/>."
. Notez la restriction : « Les autres composants syntaxiques génériques sont supposés être sensibles à la casse, sauf indication contraire spécifique du schéma. » Les chemins sont sensibles à la casse — ne les mettez pas en minuscules.
Par ailleurs : « les chiffres hexadécimaux au sein d’un triplet de codage en pourcentage (par exemple, %3a
par opposition à %3A
) ne sont pas sensibles à la casse et doivent donc être normalisés pour utiliser des lettres majuscules ».
Normalisation du codage en pourcentage. La RFC qualifie cela de « source fréquente de variance entre des URI par ailleurs identiques », car « certains créateurs d’URI recourent au codage en pourcentage pour des octets qui ne le nécessitent pas ». Ceux-ci « doivent être normalisés en décodant tout octet codé en pourcentage qui correspond à un caractère non réservé ».
Normalisation des segments de chemin. Supprimer les segments .
et ..
en appliquant l’algorithme remove_dot_segments
, car « certaines implémentations déployées partent à tort du principe que la résolution de la référence n’est pas nécessaire lorsque celle-ci est déjà un URI ».
Normalisation basée sur le schéma. La RFC donne l’exemple canonique — ces quatre éléments sont équivalents :
http://example.com
http://example.com/
http://example.com:/
http://example.com:80/
Ainsi, un chemin vide « doit être normalisé en un chemin de type /
», et un port par défaut ou vide « doit être supprimé par normalisation basée sur le schéma ».
Au-delà de la spécification, trois normalisations sont pragmatiques plutôt que strictement sûres, et méritent d’être appliquées avec prudence :
Supprimer les paramètres de suivi. utm_*
, fbclid
, gclid
, identifiants de session. Ceux-ci ne modifient presque jamais le contenu et multiplient considérablement le nombre d’URL. Tenez une liste plutôt que de deviner.
Trier les paramètres de requête restants. ?a=1&b=2
et ?b=2&a=1
renvoient généralement à la même page. Généralement — certaines applications sont sensibles à l’ordre, il convient donc de tester sur un échantillon.
Supprimer les fragments. #section
est un élément côté client qui n’atteint jamais le serveur. Vous pouvez toujours le supprimer sans risque lors de l’exploration.
**Et respectez les balises rel="canonical"
.** Si une page déclare une URL canonique, cela signifie que le site vous indique laquelle, parmi plusieurs adresses, est la bonne. La respecter permet une déduplication gratuite, soutenue par l’autorité du site lui-même.
The Frontier : Conception des files d’attente
Trois propriétés déterminent si votre robot d’indexation est évolutif.
La déduplication doit être peu coûteuse. Avant d’ajouter une URL, vous vérifiez si elle est déjà connue. Avec un million d’URL, un balayage linéaire est inutilisable. Un ensemble de hachage fonctionne jusqu'à un certain point ; au-delà, un filtre de Bloom vous offre une vérification d'appartenance en temps constant pour une fraction de la mémoire, avec un faible taux de faux positifs — ce qui signifie que vous passez parfois à côté d'une URL que vous n'avez pas encore vue. Pour la plupart des explorations, ce compromis est acceptable ; lorsque l'exhaustivité est primordiale, complétez le filtre par un stockage exact.
L'ordre doit être contrôlable. Une simple file d'attente FIFO (premier entré, premier sorti) permet un parcours en largeur, ce qui correspond généralement à ce que vous recherchez : elle atteint rapidement une large couverture tout en restant peu profonde. Une pile LIFO (dernier entré, premier sorti) permet un parcours en profondeur, qui explore en profondeur une seule branche et s'avère rarement utile pour l'exploration d'un site. Une file d’attente prioritaire vous permet de classer les éléments selon vos critères, ce qui sera abordé ensuite.
L’état de chaque hôte doit être suivi. En pratique, la « frontière » ne se résume pas à une seule file d’attente ; il s’agit d’une file d’attente par hôte, de sorte que les limites de courtoisie s’appliquent à chacune indépendamment. Une file d’attente globale unique avec une limite de débit globale signifie qu’un grand site prive les autres de ressources.
La structure évolutive consiste en un ensemble de files d’attente par hôte, associé à un planificateur qui sélectionne le prochain hôte éligible à l’extraction — « éligible » signifiant qu’un délai suffisant s’est écoulé depuis la dernière requête qui lui a été adressée.
Hiérarchisation
Quelle URL récupérer ensuite, lorsque vous ne pouvez pas toutes les récupérer.
Par profondeur. Les pages situées à un niveau moins profond sont généralement plus importantes. Un paramètre par défaut simple et efficace.
Par structure de chemin d’accès. Si vous recherchez des pages de produits, donnez la priorité aux URL correspondant à /product/. Il s’agit de l’heuristique la plus rentable pour les explorations ciblées, et elle est très peu coûteuse.
Par lastmod. À partir du plan du site. Récupérez ce qui a changé.
Par historique des modifications. Pour les explorations récurrentes, les pages qui ont souvent changé par le passé changeront à nouveau fréquemment.
Par nombre de liens entrants. Les pages vers lesquelles de nombreux liens renvoient sont généralement plus importantes. Ce calcul est coûteux à effectuer pendant une exploration, mais s’avère rentable pour les tâches de grande envergure.
Par valeur estimée. Quel que soit votre objectif réel. Si vous recherchez des prix, donnez la priorité aux pages susceptibles d’en contenir.
Concrètement, il s’agit d’un petit score entier calculé au moment de la mise en file d’attente à partir de quelques-uns de ces signaux, utilisé comme clé dans une file d’attente prioritaire. Les schémas élaborés justifient rarement leur complexité ; la profondeur, associée à un bonus lié au motif du chemin d’accès, couvre la plupart des besoins.
Limites : budgets et pièges
Sans limites, certains explorateurs ne s'arrêtent jamais. Ce n'est pas un cas marginal.
Profondeur maximale. Des liens menant à d’autres liens menant à d’autres liens. Une limite de profondeur de cinq ou six couvre pratiquement toutes les structures de sites réelles.
Nombre maximal de pages par hôte. Un chiffre fixe. Lorsqu’il est atteint, il faut s’arrêter et générer un rapport plutôt que de continuer.
Nombre maximal total de pages. Pour l’ensemble de la tâche.
Bande passante maximale. Surtout sur le trafic proxy facturé au volume, où un exploration illimitée se traduit par une facture illimitée.
Exclusions par modèle. Les calendriers constituent l’exemple classique d’espace infini : un lien « next month
» génère des URL à l’infini. La navigation à facettes sur un vaste catalogue produit des explosions combinatoires. Excluez-les par modèle :
/calendar/
/?filter=
/*?sort=
Détection de contenu dupliqué. Calculez le hachage du corps du contenu. Si une centaine d’URL renvoient un contenu identique, vous avez trouvé un espace généré plutôt qu’une centaine de pages.
Et surveillez le taux de découverte. L’alerte la plus utile : suivez le nombre d’URL nouvellement découvertes par page récupérée. Sur un site fini, ce taux diminue régulièrement jusqu’à zéro. S’il reste stable ou augmente, cela signifie que quelque chose génère des URL plus vite que vous ne pouvez les traiter — un piège, un calendrier ou une explosion de facettes. Nous avons abordé les versions délibérées dans les pièges honeypot.
Planification des nouvelles explorations
Pour tout ce qui est mis à jour plus d’une fois, la liste devient un calendrier.
Classez par volatilité. Les pages qui changent toutes les heures nécessitent des vérifications toutes les heures ; celles qui ne changent qu’une fois par an n’en ont pas besoin. Explorer l’ensemble du contenu à la fréquence imposée par l’élément le plus volatile est la principale cause de surcoût.
Utilisez des requêtes conditionnelles. Les requêtes If-Modified-Since et If-None-Match transforment une nouvelle exploration en une série de réponses « 304 Not Modified » ne coûtant que quelques centaines d’octets chacune. Lors d’une nouvelle exploration où la plupart des pages sont inchangées, cela réduit à la fois votre facture et la charge du serveur cible d’un ordre de grandeur.
Adaptez-vous en fonction de vos observations. Si une page n’a pas changé au cours des dix dernières vérifications, vérifiez-la moins souvent. Si elle a changé au cours des trois dernières, vérifiez-la plus souvent. Un simple recul multiplicatif suffit.
Détectez explicitement les suppressions. Les pages disparaissent, et les sites l’annoncent rarement. Surveillez les codes 404 ou appliquez une règle : une URL non détectée lors de trois explorations consécutives est marquée comme inactive. Sans cela, votre ensemble de données se remplit d’entrées qui n’existent plus, ce qui sape la confiance plus rapidement que les entrées manquantes.
Stockage et évolutivité
Remarque sur les cas où l'approche en mémoire cesse de fonctionner.
Jusqu'à environ cent mille URL, les sets et les listes Python suffisent. Il ne faut pas sur-concevoir.
Jusqu’à quelques millions, une base de données locale — SQLite fonctionne bien — avec des index sur le hachage de l’URL et le statut de récupération. La persistance permet également de reprendre un crawl interrompu plutôt que de le redémarrer, ce qui est plus important que les performances.
Au-delà de ce seuil, il faut une file d’attente adaptée et un magasin clé-valeur, avec la frontière partitionnée par hôte afin que les travailleurs puissent se voir attribuer des hôtes entiers et que la « politesse » reste correcte sans coordination.
Deux choix de conception qui s’avèrent payants à toutes les échelles :
Stockez le hachage de l’URL, pas seulement l’URL. Les comparaisons et les indexations sur un hachage de longueur fixe sont moins coûteuses et l’espace de stockage nécessaire est réduit.
Stocker la réponse brute, et pas seulement le résultat analysé. Lorsqu’un analyseur syntaxique tombe en panne — ce qui arrivera inévitablement —, réanalyser ce dont vous disposez ne coûte rien, tandis que la récupération des données coûte de la bande passante et de la bonne volonté.
Une implémentation minimale
Le tout en une quarantaine de lignes, pour concrétiser les mécanismes en jeu.
import time
from collections import deque, defaultdict
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
TRACKING = {"utm_source", "utm_medium", "utm_campaign", "fbclid", "gclid"}
def normalise(url):
p = urlsplit(url)
host = p.hostname or ""
port = "" if p.port in (None, 80, 443) else f":{p.port}"
query = urlencode(sorted(
(k, v) for k, v in parse_qsl(p.query, keep_blank_values=True)
if k.lower() not in TRACKING
))
return urlunsplit((p.scheme.lower(), host + port, p.path or "/", query, ""))
class Frontier:
def __init__(self, delay=1.5, max_per_host=5000):
self.queues = defaultdict(deque)
self.seen = set()
self.next_ok = defaultdict(float)
self.counts = defaultdict(int)
self.delay, self.max_per_host = delay, max_per_host
def add(self, url, depth=0):
url = normalise(url)
if url in self.seen or depth > 5:
return False
host = urlsplit(url).hostname
if self.counts[host] >= self.max_per_host:
return False
self.seen.add(url)
self.counts[host] += 1
self.queues[host].append((url, depth))
return True
def next(self):
now = time.monotonic()
for host, q in self.queues.items():
if q and self.next_ok[host] <= now:
self.next_ok[host] = now + self.delay
return q.popleft()
return None
On y distingue cinq choix de conception, qui correspondent chacun à une section ci-dessus.
La normalisation s'effectue lors de l'add(), et non au moment de la récupération. La déduplication n'est correcte que si la forme canonique est celle qui entre dans l'ensemble des pages visitées ; par conséquent, normaliser plus tard signifie que vous avez déjà stocké des doublons.
L'ensemble des pages visitées est rempli lors de la mise en file d'attente, et non à la fin du processus. Sinon, une URL découverte sur vingt pages serait mise en file d’attente vingt fois avant même que la première récupération ne soit terminée.
Les files d’attente sont propres à chaque hôte, tout comme la « politesse ». next_ok enregistre quand chaque hôte peut être contacté à nouveau, ce qui empêche un site volumineux de monopoliser les ressources au détriment des autres et garantit que le délai s’applique là où il le faut.
Les budgets sont appliqués dès l’entrée. Les limites de profondeur et par hôte rejettent les URL avant qu’elles ne consomment de la mémoire, ce qui fait la différence entre un exploration limitée et une exploration qui découvre ses limites en épuisant la RAM.
next() renvoie None plutôt que de bloquer. Cela laisse l’appelant libre de décider s’il souhaite attendre, effectuer d’autres tâches ou terminer — un planificateur qui se met en veille au sein de la structure de données est un planificateur que l’on ne peut pas instrumenter.
Ce qui manque délibérément ici, c’est la persistance, et c’est la première chose à ajouter pour toute application concrète. Un crawl qui plante et doit redémarrer à partir de la liste de départ a perdu plus que du temps ; il a récupéré toutes les données à nouveau, ce qui coûte de la bande passante et de la bonne volonté.
Mise en place des indicateurs
Que faut-il mesurer ? En effet, un crawl qui ne rend compte que du nombre de « pages récupérées » ne vous apprend pratiquement rien.
Taille de la frontière au fil du temps. Elle devrait tendre vers zéro. Une augmentation signifie une découverte illimitée.
Taux de découverte. Nombre de nouvelles URL par page récupérée, comme ci-dessus.
Résultats de récupération par statut, par hôte. Les chiffres agrégés masquent l’échec total d’un hôte.
Taux de doublons. Combien d’URL découvertes étaient déjà connues. Un taux élevé après normalisation signifie que votre normalisation passe à côté de quelque chose.
Octets par page utile. Le chiffre qui relie l’exploration à la facture, et celui qui révèle qu’un navigateur sans interface utilisateur récupère des mégaoctets d’images dont vous n’aviez pas besoin.
Vérifications de contenu. Les pages contiennent-elles les marqueurs attendus ? Un robot d’exploration signalant un taux de réussite de 100 % tout en renvoyant des pages bloquées de manière implicite constitue un échec coûteux, et seules les vérifications de contenu permettent de le détecter.
Questions fréquentes
Qu'est-ce qu'une liste de crawl ?
Ensemble d'URL qu'un robot d'indexation prévoit de visiter, comprenant généralement une liste de départ, une zone de frontière constituée d'URL découvertes mais non encore récupérées, et un ensemble d'URL déjà visitées. La gestion de cette zone de frontière (ordre, déduplication et budgets) est le facteur qui détermine en grande partie si une exploration aboutit.
Comment normaliser les URL pour l'exploration ?
Mettre en minuscules le schéma et l’hôte, mais pas le chemin d’accès ; mettre en majuscules les chiffres hexadécimaux codés en pourcentage ; décoder les encodages en pourcentage inutiles ; supprimer les segments de points ; omettre les ports par défaut ; normaliser un chemin d’accès vide en / ; supprimer les fragments ; et éliminer les paramètres de suivi connus. La norme RFC 3986 définit toutes ces opérations sauf les deux dernières.
Qu’est-ce qu’une « frontière d’exploration » ?
Il s’agit de la file d’attente des URL découvertes mais non encore récupérées. En pratique, il s’agit d’un ensemble de files d’attente par hôte, associé à un planificateur qui sélectionne le prochain hôte éligible dans le respect des limites de courtoisie, car une file d’attente globale unique permettrait à un seul grand site de priver tous les autres de ressources.
Comment empêcher mon robot d’exploration de tourner indéfiniment ?
Définissez des limites strictes : profondeur maximale, nombre maximal de pages par hôte et au total, ainsi qu’un plafond de bande passante. Ajoutez des exclusions de modèles pour les calendriers et la navigation à facettes, et déclenchez une alerte lorsque le rapport entre les URL nouvellement découvertes et les pages récupérées cesse de diminuer.
Comment éviter d’explorer deux fois la même page ?
Normalisez les URL avant la déduplication, car une même page peut avoir plusieurs adresses valides. Vérifiez ensuite si la page fait partie d’un ensemble de pages déjà visitées — un ensemble de hachage à petite échelle, un filtre de Bloom ou une base de données à grande échelle. Respectez les balises rel="canonical" lorsque les pages les déclarent.
À quelle fréquence dois-je effectuer un nouvel exploration ?
Aussi rarement que vos besoins le permettent, en fonction de la fréquence à laquelle chaque page change réellement. Utilisez l’lastmode issue du plan du site et des requêtes conditionnelles afin que les pages inchangées ne nécessitent qu’une requête « 304 » plutôt qu’une récupération complète. Explorer l’ensemble du site à la fréquence des pages volatiles est la source de gaspillage la plus courante.
Qu’est-ce qu’un filtre de Bloom et en ai-je besoin ?
Il s’agit d’un ensemble probabiliste qui détermine l’appartenance en temps constant en utilisant très peu de mémoire, avec un faible risque de faux positifs — ce qui signifie que vous pouvez parfois omettre une URL que vous n’avez pas réellement vue. Cela vaut la peine au-delà de quelques millions d’URL ; cela n’est pas nécessaire en dessous de ce seuil, où un simple ensemble est plus simple et plus précis.
Comment détecter que des pages ont été supprimées ?
Surveillez les codes 404 et appliquez une politique pour les pages qui cessent tout simplement d’apparaître — par exemple, marquez une URL comme inactive si elle n’a pas été détectée lors de trois explorations consécutives. Sans cela, un ensemble de données accumule des entrées qui n’existent plus, ce qui nuit à la fiabilité plus rapidement que les lacunes.
Conclusion
L'exploration de sites web relève principalement de la gestion administrative. La récupération des données est un problème résolu grâce aux bibliothèques ; le véritable défi technique réside dans le choix des données à récupérer, la reconnaissance des données déjà récupérées et la détermination du moment où il faut s'arrêter.
Trois aspects méritent une attention disproportionnée par rapport à leur difficulté. La normalisation, car sans elle, vous visitez la même page sous une douzaine d’adresses et payez pour chacune d’entre elles — et la RFC 3986 vous indique précisément quelles transformations sont sûres. Les budgets, car certains espaces d’URL sont véritablement infinis et un robot d’exploration sans limites strictes finira par en trouver un. Et le taux de découverte, car c’est un chiffre unique qui révèle un piège, un calendrier ou une explosion de facettes bien avant que la facture ne le fasse.
Ensuite, semez judicieusement. Un plan du site transforme un crawl en une récupération des modifications, une requête d’archives fait remonter à la surface des pages que le parcours des liens ne pourra jamais atteindre, et ces deux opérations ne coûtent rien à la cible. Commencer par la page d’accueil et espérer que ça marche est la voie la plus lente vers le résultat le moins complet, et c’est pourtant toujours la méthode par défaut.
