Développement Web

Extraire des donnees web avec requests et BeautifulSoup

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

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

Ce tutoriel ouvre la série consacrée au web scraping en Python. Pour la vue d’ensemble (quel outil pour quel besoin, le cadre légal), lisez d’abord le guide principal.

Aminata tient une librairie-papeterie à Sicap Liberté, à Dakar. Chaque rentrée scolaire, elle court après les prix : son grossiste publie son catalogue de manuels en ligne, les tarifs bougent d’une semaine à l’autre, et elle découvre les ruptures de stock trop tard. Recopier 400 références à la main dans un cahier n’est pas une option. Ce qu’il lui faut, c’est un petit programme qui ouvre la page du catalogue, lit le titre, le prix et la disponibilité de chaque article, et lui rend un tableau propre. C’est exactement ce que vous allez construire ici.

Pour s’entraîner sans déranger le vrai grossiste, on travaille sur books.toscrape.com, un site public mis en ligne exprès pour apprendre le scraping : un catalogue de 1000 ouvrages, paginé, avec titre, prix et stock — la copie conforme d’un catalogue fournisseur. À la fin de cette leçon, vous saurez transformer une page web en données exploitables.

🎯 Ce que vous allez apprendre

  • Télécharger le code HTML d’une page avec la bibliothèque requests, en vérifiant que la réponse est valide.
  • Transformer ce HTML en un arbre interrogeable avec BeautifulSoup.
  • Cibler un élément précis (un prix, un titre) avec un sélecteur CSS, sans tâtonner.
  • Extraire titre, prix et disponibilité des 20 articles d’une page et les ranger dans une liste de dictionnaires.

🛠️ Ce que vous allez construire

Un script d’une trentaine de lignes, veille.py, qui interroge la première page du catalogue et affiche dans le terminal les 20 références avec leur prix et leur état de stock. C’est la première brique de « VeilleManuels », l’outil de suivi de catalogue d’Aminata. Les tutoriels suivants y ajouteront la pagination, le nettoyage, puis le passage à l’échelle.

Prérequis

  • Python 3.13 installé (vérifiez avec python --version). Toute la série repose sur cette version, qui reste compatible avec les trois outils du parcours.
  • Savoir lancer un script dans un terminal et installer un paquet avec pip. Si ces deux gestes ne vous parlent pas, parcourez d’abord notre tour d’horizon de l’écosystème Python.
  • Aucune connaissance préalable du HTML n’est exigée : on introduit le strict nécessaire au fil de l’eau.
  • ⏱️ Temps estimé : ~35 minutes.

Étape 1 — Préparer un environnement isolé

Avant d’installer quoi que ce soit, on crée un environnement virtuel. C’est un dossier qui contient une copie privée de Python et de ses bibliothèques, propre à ce projet. Sans lui, les paquets s’installent à l’échelle de toute la machine et finissent par se marcher dessus entre projets — un piège classique qui coûte des heures de débogage plus tard.

mkdir veille-manuels && cd veille-manuels
python -m venv .venv
# Activer l'environnement :
source .venv/bin/activate        # Linux / macOS
.venv\Scripts\Activate.ps1        # Windows (PowerShell)

pip install requests beautifulsoup4 lxml

Après activation, votre invite de commande affiche (.venv) au début de la ligne : c’est le signal que vous travaillez bien dans l’environnement isolé. On installe trois paquets : requests pour télécharger les pages, beautifulsoup4 pour lire le HTML, et lxml, un analyseur rapide que BeautifulSoup utilisera. Si l’installation de lxml échoue sur votre machine, ce n’est pas grave : BeautifulSoup sait aussi fonctionner avec l’analyseur html.parser intégré à Python, sans rien installer de plus.

Point d’étape — Lancez pip list : vous devez voir requests, beautifulsoup4, lxml et soupsieve (une dépendance de BeautifulSoup) dans la liste. Si la commande pip est introuvable, c’est que l’environnement n’est pas activé : reprenez la ligne d’activation.

Étape 2 — Télécharger une page proprement

Une page web, vue par un programme, n’est qu’un long texte : le code HTML que votre navigateur, lui, met en forme. La première tâche est donc de récupérer ce texte. La bibliothèque requests fait ça en une ligne, mais une requête « propre » demande deux précautions : annoncer qui l’on est, et vérifier que le serveur a bien répondu.

import requests

URL = "https://books.toscrape.com/catalogue/page-1.html"
EN_TETES = {
    "User-Agent": "VeilleManuels/1.0 (librairie Aminata; contact@exemple.sn)"
}

reponse = requests.get(URL, headers=EN_TETES, timeout=10)
reponse.raise_for_status()          # lève une erreur si le statut n'est pas 200
print("Statut :", reponse.status_code)
print("Taille reçue :", len(reponse.text), "caractères")

Trois détails comptent ici. Le User-Agent indique au serveur quel programme le contacte ; mettre un nom et un contact honnêtes est une marque de respect (et la base d’un scraping responsable, qu’on creusera plus loin). Le timeout=10 évite que le script reste bloqué indéfiniment si le serveur ne répond pas. Enfin, raise_for_status() transforme un statut d’erreur (404, 503…) en exception Python claire, plutôt que de continuer avec une page vide. Lancez le script : vous devez lire Statut : 200 et une taille de plusieurs dizaines de milliers de caractères.

Point d’étape — Un Statut : 200 confirme que la page est arrivée entière. Si vous obtenez 403 ou 429, le serveur refuse ou vous freine : ralentissez et vérifiez votre User-Agent.

Étape 3 — Transformer le HTML en arbre interrogeable

Le texte brut renvoyé par requests est illisible pour un humain et pénible à découper « à la main ». BeautifulSoup le transforme en une structure arborescente — l’arbre du document — où chaque balise devient un objet qu’on peut parcourir et interroger. C’est le cœur de la bibliothèque.

from bs4 import BeautifulSoup

soup = BeautifulSoup(reponse.text, "lxml")   # ou "html.parser" sans installation
print(soup.title.get_text(strip=True))        # le titre de l'onglet du navigateur

La variable soup représente désormais toute la page. soup.title attrape la première balise <title>, et get_text(strip=True) en extrait le texte sans les espaces superflus. Si cette ligne affiche le nom du site, l’analyse fonctionne et vous tenez le bon bout : tout le reste consiste à demander à soup les morceaux qui vous intéressent.

Étape 4 — Repérer les bons éléments avec un sélecteur CSS

Pour extraire un prix, il faut d’abord savoir il se trouve dans le HTML. C’est le travail de reconnaissance, et on le fait dans le navigateur, pas à l’aveugle. Ouvrez la page du catalogue, clic droit sur le titre d’un livre, « Inspecter ». Vous découvrez que chaque article est enveloppé dans une balise <article class="product_pod">, que son titre vit dans un <h3><a title="...">, son prix dans un <p class="price_color"> et son stock dans un <p class="instock availability">.

Ces noms de classes sont nos points de repère. Un sélecteur CSS les vise directement : article.product_pod signifie « toute balise article portant la classe product_pod ». La méthode select() de BeautifulSoup renvoie la liste de tous les éléments qui correspondent.

livres = soup.select("article.product_pod")
print("Nombre d'articles sur la page :", len(livres))

Vous devez obtenir 20 : c’est le nombre de livres affichés par page sur ce catalogue. Ce simple décompte est une vérification précieuse — s’il renvoie 0, votre sélecteur ne correspond à rien (faute de frappe, ou structure différente de celle attendue), et inutile d’aller plus loin tant qu’il ne renvoie pas 20.

Étape 5 — Extraire titre, prix et disponibilité d’un article

On tient la liste des 20 articles. Concentrons-nous sur un seul pour mettre au point l’extraction, avant de généraliser. Pour chaque champ, on réutilise select_one() (qui renvoie le premier élément correspondant, pas une liste) à l’intérieur de l’article.

premier = livres[0]

# Le titre complet est dans l'attribut "title" du lien, pas dans le texte tronqué
titre = premier.select_one("h3 a")["title"]

# Le prix ressemble à "£51.77" : on retire le symbole pour garder un nombre
prix_brut = premier.select_one("p.price_color").get_text(strip=True)
prix = float(prix_brut.replace("£", "").replace("Â", "").strip())

# La disponibilité : on normalise en vrai/faux
dispo_texte = premier.select_one("p.instock.availability").get_text(strip=True)
en_stock = "in stock" in dispo_texte.lower()

print(titre, "|", prix, "|", "dispo" if en_stock else "rupture")

Deux subtilités méritent qu’on s’y arrête. Le titre affiché à l’écran est souvent coupé par des points de suspension ; le titre complet, lui, est rangé dans l’attribut title du lien — d’où ["title"] plutôt que get_text(). Quant au prix, il arrive sous forme de texte avec un symbole monétaire ; on le nettoie pour obtenir un float sur lequel on pourra calculer. Le replace("Â", "") gère un artefact d’encodage fréquent sur ce site précis — un bon rappel que les données réelles sont rarement parfaites.

Point d’étape — La ligne affichée doit ressembler à A Light in the Attic | 51.77 | dispo. Si le prix déclenche une ValueError, affichez prix_brut tel quel : vous verrez le caractère parasite à retirer.

Étape 6 — Boucler sur toute la page et vérifier

L’extraction d’un article étant au point, on l’applique aux vingt par une boucle, en rangeant chaque résultat dans un dictionnaire. La liste de dictionnaires est le format de travail idéal : lisible, et directement convertible en tableau ou en CSV à l’étape suivante de la série.

def extraire_livres(soup):
    resultats = []
    for livre in soup.select("article.product_pod"):
        prix_brut = livre.select_one("p.price_color").get_text(strip=True)
        dispo = livre.select_one("p.instock.availability").get_text(strip=True)
        resultats.append({
            "titre": livre.select_one("h3 a")["title"],
            "prix": float(prix_brut.replace("£", "").replace("Â", "").strip()),
            "en_stock": "in stock" in dispo.lower(),
        })
    return resultats

livres = extraire_livres(soup)
print(f"{len(livres)} livres extraits")
for item in livres[:3]:
    print(item)

En séparant l’extraction dans une fonction, vous obtenez un bloc réutilisable : dans le prochain tutoriel, vous l’appellerez pour chacune des 50 pages sans réécrire une ligne. Lancez le script : il doit annoncer 20 livres extraits puis afficher les trois premiers dictionnaires. Voilà : une page web est devenue de la donnée structurée, exactement ce qu’Aminata pourra comparer d’une semaine à l’autre.

🐞 Pièges fréquents

Symptôme / erreur Cause probable Correctif
select() renvoie une liste vide Sélecteur erroné ou structure de page différente Recopier la classe exacte depuis l’inspecteur du navigateur ; attention aux classes multiples (instock availabilityp.instock.availability)
TypeError: 'NoneType' object is not subscriptable select_one() n’a rien trouvé et renvoie None Vérifier le sélecteur ; protéger avec un test if element is not None avant d’accéder au contenu
Accents et symboles cassés (é, £) Encodage mal deviné Forcer reponse.encoding = "utf-8" avant de lire reponse.text
Statut 403 ou 429 Pas de User-Agent, ou requêtes trop rapprochées Définir un User-Agent honnête et espacer les requêtes (voir le tutoriel sur le scraping responsable)

🌍 Adaptation au contexte ouest-africain

Avec une connexion mobile facturée au mégaoctet, télécharger cent fois la même page pendant la mise au point coûte cher — en data comme en patience. Le réflexe qui change tout : enregistrer la page une fois en local (open("page.html", "w", encoding="utf-8").write(reponse.text)) puis travailler vos sélecteurs sur ce fichier, sans relancer la requête réseau à chaque essai. Vous itérez instantanément et hors ligne, ce qui est précieux quand la connexion à Dakar, Bamako ou Niamey faiblit en milieu de journée. Pensez aussi à un timeout généreux : sur un réseau capricieux, dix secondes ne sont pas de trop.

✅ Récapitulatif

Vous êtes parti d’une page web et vous en avez tiré une liste de dictionnaires propres — titre, prix numérique, disponibilité — pour les vingt articles d’une page. En chemin, vous avez vu les deux temps de tout scraping : récupérer le HTML avec requests (en vérifiant le statut et en s’annonçant), puis extraire avec BeautifulSoup en visant les éléments par sélecteur CSS. La fonction extraire_livres que vous avez isolée est la pièce que la suite de la série va réutiliser et faire grandir.

🧾 Aide-mémoire

Élément Rôle
requests.get(url, headers=, timeout=) Télécharger une page
reponse.raise_for_status() Échouer franchement si le statut n’est pas un succès
BeautifulSoup(html, "lxml") Construire l’arbre du document
soup.select("css") Tous les éléments correspondant au sélecteur (liste)
soup.select_one("css") Le premier élément correspondant (ou None)
element.get_text(strip=True) Texte nettoyé d’un élément
element["title"] Valeur d’un attribut HTML

💪 À vous de jouer

Le catalogue affiche aussi une note en étoiles, codée dans une classe comme <p class="star-rating Three">. Ajoutez un champ note à chaque dictionnaire, en récupérant le mot anglais (One, Two, Three…) qui suit star-rating dans la liste des classes.

Voir une solution
MOTS = {"One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5}

p_note = livre.select_one("p.star-rating")
classes = p_note.get("class", [])          # ex. ["star-rating", "Three"]
mot = next((c for c in classes if c in MOTS), None)
note = MOTS.get(mot)                          # -> 3

On lit la liste des classes de l’élément, on garde celle qui correspond à un mot connu, et on la traduit en chiffre. C’est le motif type pour extraire une donnée encodée dans un nom de classe.

Tutoriels frères

Pour aller plus loin

FAQ

Faut-il connaître le HTML pour scraper ?
Pas en profondeur. Il suffit de comprendre les balises et les classes, et de savoir les repérer dans l’inspecteur du navigateur. Le reste s’apprend en pratiquant.

Pourquoi lxml plutôt que html.parser ?
lxml est nettement plus rapide sur de gros volumes et plus tolérant aux HTML mal formés. Mais html.parser, intégré à Python, suffit pour débuter et n’exige aucune installation.

requests peut-il scraper n’importe quel site ?
Non. Il ne récupère que le HTML envoyé par le serveur. Si la page construit son contenu en JavaScript après chargement (catalogues qui se remplissent au défilement, par exemple), requests ne verra rien : il faudra alors Playwright.

Est-ce légal de scraper ce site ?
books.toscrape.com est publié explicitement pour l’entraînement, donc oui. Pour un vrai site, la réponse dépend de ses conditions d’utilisation et des données visées : ce point est traité dans le tutoriel sur le scraping responsable.

مشاركة