Développement Web

Scraping responsable : robots.txt, ethique et limites

12 دقائق للقراءة

📍 Guide principal : Web scraping en Python : le guide complet.

Dernière étape de la série. Elle ne parle pas d’un nouvel outil mais de la chose qui sépare un amateur d’un professionnel : savoir scraper sans nuire, sans s’exposer, et sans se faire bloquer.

Aminata maîtrise désormais ses trois outils. Mais une question la taraude : a-t-elle le droit de faire tout ça ? Et comment être sûre de ne pas causer de tort au serveur du fournisseur, ni de finir bannie ? Ce sont les bonnes questions, et trop de tutoriels les esquivent. Un scraper irresponsable, c’est un projet qui marche une semaine puis se fait bloquer, ou pire, qui expose son auteur à un litige. Cette leçon transforme votre collecteur en outil durable et défendable.

On va distinguer deux plans qu’on confond souvent : la politesse technique (ne pas abîmer le serveur) et le cadre légal et éthique (avoir le droit de collecter ces données). Les deux comptent, et aucun outil ne les remplace — ils relèvent de votre jugement.

🎯 Ce que vous allez apprendre

  • Lire et respecter le fichier robots.txt d’un site avec la bibliothèque standard de Python.
  • Vous identifier honnêtement et limiter votre cadence pour ne pas surcharger un serveur.
  • Réagir correctement à un blocage (code 429) et mettre en cache pour ne pas re-télécharger inutilement.
  • Situer les limites légales et éthiques : conditions d’utilisation, données personnelles, droit d’auteur, et quand préférer une API.

🛠️ Ce que vous allez construire

Une fonction requete_responsable qui rassemble les bonnes pratiques : elle vérifie d’abord le robots.txt, s’identifie clairement, respecte un délai, et gère proprement un éventuel 429. C’est le garde-fou que vous placerez devant n’importe lequel de vos scrapers, qu’il repose sur requests, Scrapy ou Playwright.

Prérequis

  • Avoir suivi au moins le premier tutoriel (requests).
  • urllib.robotparser et time sont dans la bibliothèque standard : rien à installer.
  • ⏱️ Temps estimé : ~40 minutes.

Étape 1 — Lire et respecter robots.txt

Presque tous les sites publient à leur racine un fichier robots.txt qui indique aux robots ce qu’ils peuvent visiter ou non. Depuis 2022, ce protocole est même un standard officiel de l’IETF (RFC 9309). Le consulter avant de scraper est le premier geste de courtoisie — et Python le fait pour vous, sans rien installer, grâce à urllib.robotparser.

from urllib.robotparser import RobotFileParser

AGENT = "VeilleManuels/1.0"
rp = RobotFileParser()
rp.set_url("https://books.toscrape.com/robots.txt")
rp.read()

url = "https://books.toscrape.com/catalogue/page-1.html"
print("Autorisé :", rp.can_fetch(AGENT, url))
print("Délai conseillé :", rp.crawl_delay(AGENT))

can_fetch répond par vrai ou faux : ai-je le droit, selon le site, de récupérer cette URL ? crawl_delay renvoie le délai entre requêtes que le site recommande (ou None s’il n’en précise aucun). Quand un site n’a pas de robots.txt, le fichier est considéré comme « tout autorisé » — mais cela ne vous dispense ni de politesse ni du respect de ses conditions d’utilisation. Respecter robots.txt n’est pas qu’une formalité : c’est la volonté explicite du propriétaire du site, et l’ignorer est le meilleur moyen de transformer une collecte discrète en incident.

Point d’étape — Vous savez interroger un robots.txt par programme. Réflexe à adopter : appeler can_fetch avant chaque URL, et n’y aller que si la réponse est vraie.

Étape 2 — S’identifier honnêtement

Chaque requête HTTP porte un en-tête User-Agent qui dit « qui » frappe à la porte. Par défaut, les bibliothèques mettent un nom générique, parfois trompeur. Le bon réflexe est l’inverse de la discrétion : annoncer clairement votre robot et un moyen de vous contacter. Un administrateur qui voit un agent identifié avec une adresse de contact comprend votre démarche ; face à un agent anonyme, il bloque par précaution.

EN_TETES = {
    "User-Agent": "VeilleManuels/1.0 (+mailto:aminata@librairie-exemple.sn)"
}

Ce simple en-tête fait deux choses : il vous rend traçable (donc crédible) et il permet au site de vous joindre plutôt que de vous bannir si quelque chose le dérange. Se déguiser en navigateur humain pour contourner un blocage est exactement la mauvaise direction : c’est plus fragile techniquement, et cela vous place du mauvais côté sur le plan éthique. La transparence est à la fois plus honnête et plus durable.

Étape 3 — Limiter sa cadence

Un serveur, surtout celui d’une petite structure, a des ressources limitées. Lui envoyer des centaines de requêtes par seconde, c’est risquer de le ralentir pour ses vrais utilisateurs — une forme de nuisance, même involontaire. La règle est simple : espacer ses requêtes, et caler ce délai sur ce que le site recommande via crawl_delay quand il le précise.

import time

delai = rp.crawl_delay(AGENT) or 1.0   # le délai du site, sinon 1 seconde
# ... dans votre boucle de collecte :
time.sleep(delai)

Une seconde entre deux requêtes paraît lent à l’échelle d’une machine, mais c’est une cadence respectueuse qui passe inaperçue côté serveur. Et rappelez-vous : la vitesse n’est presque jamais l’objectif d’un scraper de veille. Mieux vaut une collecte tranquille qui tourne chaque nuit qu’une rafale qui se fait couper au bout de dix minutes.

Étape 4 — Réagir correctement à un blocage (429)

Si malgré tout vous allez trop vite, un serveur peut répondre par le code 429 Too Many Requests (défini par la RFC 6585). C’est un avertissement poli : « ralentissez ». Bien souvent, la réponse inclut un en-tête Retry-After qui indique combien de secondes attendre. Un scraper responsable le lit et obéit, au lieu d’insister.

import requests

def requete_responsable(session, url, agent=AGENT):
    if not rp.can_fetch(agent, url):
        raise PermissionError(f"Interdit par robots.txt : {url}")
    reponse = session.get(url, timeout=10)
    if reponse.status_code == 429:
        attente = int(reponse.headers.get("Retry-After", 5))
        print(f"429 reçu — pause de {attente} s")
        time.sleep(attente)
        reponse = session.get(url, timeout=10)   # une seule nouvelle tentative
    reponse.raise_for_status()
    return reponse

La fonction réunit tout : elle refuse d’aller où robots.txt l’interdit, puis, si le serveur signale une surcharge, elle respecte le délai demandé avant de réessayer une fois. C’est un comportement civilisé, exactement celui qu’un site attend d’un robot bien conçu. Insister après un 429 sans attendre, en revanche, est le plus sûr moyen de passer d’un ralentissement temporaire à un blocage définitif de votre adresse.

Point d’étape — Votre requete_responsable vérifie robots.txt, gère le 429 et s’identifie. Branchez-la devant toute collecte : c’est votre garde-fou réutilisable.

Étape 5 — Mettre en cache pour ne pas re-télécharger

La requête la plus polie est celle qu’on n’envoie pas. Pendant la mise au point, on relance souvent le même script : inutile de redemander cent fois la même page au serveur. Un cache local stocke la réponse au premier appel et la rejoue ensuite, ce qui épargne le serveur, accélère votre travail et économise votre forfait de données.

import requests_cache   # pip install requests-cache

# Toutes les requêtes sont mises en cache 1 heure dans un fichier local
session = requests_cache.CachedSession("cache_veille", expire_after=3600)
reponse = session.get("https://books.toscrape.com/catalogue/page-1.html")
print("Depuis le cache :", reponse.from_cache)

requests_cache remplace votre Session habituelle par une version qui mémorise les réponses. Le premier appel touche le réseau ; les suivants, dans l’heure, sont servis depuis le fichier local — reponse.from_cache vaut alors True. C’est un réflexe gagnant-gagnant : moins de charge pour le site, plus de vitesse pour vous, et une facture data réduite d’autant.

Étape 6 — Connaître les limites légales et éthiques

La technique étant réglée, reste le plus important : a-t-on le droit ? Il n’existe pas de réponse universelle — cela dépend du site, des données et du pays — mais quelques principes solides guident la décision. D’abord, robots.txt est une norme technique, pas une loi ; mais les conditions d’utilisation d’un site, elles, ont une valeur contractuelle, et certaines interdisent explicitement l’extraction automatisée. Les lire avant de scraper un site commercial est un minimum.

Ensuite, et c’est le point le plus sensible : les données personnelles. Collecter des noms, e-mails, numéros ou profils relève de lois strictes. Au Sénégal, la loi n° 2008‑12 du 25 janvier 2008 a créé la Commission de protection des données personnelles (CDP), qui encadre tout traitement de telles données ; en Europe, c’est le RGPD. La règle prudente est simple : on ne scrape pas de données personnelles sans base légale claire. Pour un projet de veille tarifaire comme celui d’Aminata, qui ne collecte que des prix et des titres de produits, la question ne se pose pas — et c’est précisément pour cela que ce fil rouge a été choisi.

Troisième garde-fou, le droit d’auteur : le contenu d’un site (textes, images, bases de données) peut être protégé ; en extraire des faits bruts (un prix, une disponibilité) est une chose, en republier le contenu créatif en est une autre. Enfin, le réflexe qui résout la plupart des cas : chercher d’abord une API officielle. Beaucoup de sites en proposent une, pensée pour un accès programmatique propre et autorisé. Quand elle existe, elle est presque toujours préférable au scraping — plus stable, plus rapide, et sans zone grise.

🐞 Pièges fréquents

Symptôme / erreur Cause probable Correctif
can_fetch renvoie toujours True read() oublié, ou robots.txt absent Appeler rp.read() après set_url ; un site sans robots.txt autorise tout
Blocages répétés malgré un délai Cadence encore trop élevée ou IP signalée Augmenter le délai, respecter Retry-After, espacer dans le temps
Retry-After illisible Valeur en date plutôt qu’en secondes Gérer les deux formats ou prévoir une valeur de repli
Le cache renvoie des données périmées expire_after trop long Ajuster la durée selon la fraîcheur voulue des données

🌍 Adaptation au contexte ouest-africain

Le cadre juridique se construit activement dans la région : outre la CDP sénégalaise, plusieurs pays se sont dotés d’autorités et de lois sur les données personnelles, et la Convention de l’Union africaine sur la cybersécurité (dite de Malabo) pose un cadre continental. Le réflexe à garder : avant de collecter quoi que ce soit qui touche des personnes, se renseigner sur la loi locale applicable. Côté pratique, le cache (requests-cache) prend ici tout son sens : sur une connexion facturée au mégaoctet, ne pas re-télécharger les mêmes pages représente une économie réelle, mois après mois. Scraping responsable et bon sens économique vont, ici plus qu’ailleurs, dans le même sens.

✅ Récapitulatif

Vous savez maintenant scraper en professionnel : consulter robots.txt avec urllib.robotparser, vous identifier honnêtement, limiter votre cadence, obéir à un 429 et à son Retry-After, et mettre en cache pour épargner le serveur. Surtout, vous distinguez la politesse technique du cadre légal — conditions d’utilisation, données personnelles, droit d’auteur — et vous connaissez le meilleur réflexe : préférer une API officielle quand elle existe. C’est la dernière brique de « VeilleManuels », et la plus importante pour durer. Avec elle, vous avez bouclé le parcours : trois outils, et le jugement pour les employer correctement.

🧾 Aide-mémoire

Élément Rôle
RobotFileParser() Lire un robots.txt
rp.can_fetch(agent, url) URL autorisée ?
rp.crawl_delay(agent) Délai recommandé par le site
User-Agent identifiant + contact Transparence
time.sleep(delai) Espacer les requêtes
Code 429 + Retry-After Signal de ralentissement à respecter
requests_cache.CachedSession Éviter les re-téléchargements

💪 À vous de jouer

Améliorez requete_responsable pour qu’elle respecte automatiquement le crawl_delay du site (au lieu d’un délai fixe) entre deux appels successifs.

Voir une solution
import time

_dernier_appel = 0.0

def requete_rythmee(session, url, agent=AGENT):
    global _dernier_appel
    delai = rp.crawl_delay(agent) or 1.0
    ecoule = time.monotonic() - _dernier_appel
    if ecoule < delai:
        time.sleep(delai - ecoule)
    _dernier_appel = time.monotonic()
    return requete_responsable(session, url, agent)

On mémorise l'instant du dernier appel et on ne repart que lorsque le délai du site s'est écoulé. time.monotonic() est préférable à time.time() pour mesurer une durée : il ne recule jamais, même si l'horloge système est ajustée.

Tutoriels frères

Pour aller plus loin

FAQ

Scraper un site public, est-ce légal ?
Il n'y a pas de réponse unique. Extraire des faits bruts d'une page accessible à tous, à cadence raisonnable et sans données personnelles, est généralement défendable ; mais les conditions d'utilisation du site et la nature des données peuvent tout changer. En cas de doute sur un usage commercial, demandez un avis juridique.

Dois-je obéir à robots.txt si rien ne m'y oblige légalement ?
Oui, par principe. C'est la volonté exprimée du propriétaire du site, et la respecter vous évite blocages et conflits. Le contourner pour quelques pages de plus n'en vaut presque jamais la peine.

Comment savoir si un site propose une API ?
Cherchez « [nom du site] API » ou une section « développeurs » dans son pied de page. Une API officielle vous donne un accès propre, autorisé et stable — toujours préférable au scraping quand elle existe.

Que faire si un site bloque même un robot poli ?
C'est son droit le plus strict : aucun site n'a l'obligation de se laisser scraper. Insister avec des techniques de contournement vous fait basculer dans la zone grise, techniquement comme éthiquement. La bonne réponse est de chercher une API, de contacter le site pour demander l'accès, ou de renoncer — jamais de forcer le passage.

مشاركة