Cours de Python / Extraire et vérifier des données HTML avec Beautiful Soup

Pour extraire des données d’un document HTML, utilisez un analyseur qui sélectionne les éléments puis lit leurs textes ou leurs attributs. Vérifiez aussi le nombre d’éléments et les champs attendus : un sélecteur devenu incorrect ne doit pas être confondu avec une collecte vide réussie.

Commencer par un document statique maîtrisé

Ce programme analyse une chaîne HTML contenue dans le fichier Python. Il ne contacte aucun site et n’exécute aucun JavaScript. Cette séparation permet d’apprendre la sélection et la validation sans ajouter les erreurs de réseau ou les changements d’un site externe.

Beautiful Soup est une bibliothèque externe ; le nom du paquet à installer est beautifulsoup4, tandis que le nom importé est bs4. Dans l’environnement virtuel du projet, lancez :

python -m pip install beautifulsoup4

L’exemple utilise html.parser, l’analyseur HTML standard de Python, comme backend. Un autre analyseur peut construire un arbre différent face à un HTML mal formé ; choisir explicitement celui de l’exercice rend le contexte plus clair. La documentation officielle de Beautiful Soup décrit les sélections, la lecture des attributs et le choix de l’analyseur.

Pour une collecte externe, il faut en plus prévoir les droits d’accès, les conditions du service, la fréquence des requêtes et le traitement des données personnelles. Le cours sur les réponses HTTP et JSON traite la réception des données ; télécharger un document et analyser sa structure restent deux étapes distinctes.

Exécuter l’extraction complète et vérifier le total

Créez un dossier d’exercice, puis enregistrez en UTF-8 le programme complet parse_html.py :

from bs4 import BeautifulSoup

html = """<ul id="expenses">
<li data-amount="3500"><span class="name">coffee</span></li>
<li data-amount="8000"><span class="name">lunch</span></li>
</ul>"""
soup = BeautifulSoup(html, "html.parser")
rows = soup.select("#expenses > li")
if len(rows) != 2:
    raise ValueError("unexpected row count")
total = 0
for row in rows:
    name = row.select_one(".name")
    amount_text = row.get("data-amount")
    if name is None or not amount_text or not amount_text.isascii() or not amount_text.isdecimal():
        raise ValueError("missing or invalid field")
    amount = int(amount_text)
    print(name.get_text(" ", strip=True), amount)
    total += amount
print("total:", total)

Dans le terminal de ce dossier avec l’environnement sélectionné, lancez python parse_html.py. Sous macOS ou Ubuntu, utilisez python3 si c’est le nom de votre interpréteur hors environnement virtuel. Le résultat attendu est :

coffee 3500
lunch 8000
total: 11500

Le programme attend exactement deux éléments et additionne leurs attributs data-amount. Il impose des chiffres décimaux ASCII sans signe, espace ou point : 0 et 005 sont acceptés, mais pas -1, 3.0 ou les chiffres pleine chasse. La combinaison isascii() et isdecimal() rend cette règle plus stricte qu’une conversion numérique générale. Les méthodes de classification des chaînes Python précisent ces contrôles.

Il n’y a pas de plafond métier dans cette démonstration. Le contrôle du nom vérifie la présence d’un élément .name, pas que son texte est non vide. Pour une entrée réelle, définissez aussi les limites des montants, le nom obligatoire et les autres règles du domaine avant de déclarer les données entièrement valides.

Comprendre les sélecteurs, les attributs et les textes

Expression Rôle dans l’exemple
#expenses > li Sélectionner les enfants li directs de l’élément portant cet identifiant
row.select_one(".name") Prendre le premier élément de cette classe dans la ligne courante, ou None s’il est absent
row.get("data-amount") Lire la valeur de l’attribut, ou None s’il est absent

Le signe > indique un enfant direct. Un espace seul dans #expenses li autoriserait aussi des descendants plus profonds. Restreindre la recherche à la ligne courante évite de prendre le nom d’une autre dépense. Comme select_one peut renvoyer None, le programme le contrôle avant d’appeler une méthode de texte.

get_text(" ", strip=True) rassemble les fragments de texte avec un espace et retire les blancs aux extrémités de chaque fragment. Ainsi, un nom contenant coffee <strong>large</strong> est lu comme coffee large au lieu de coller les deux mots. Cela ne constitue pas une règle universelle de normalisation de tout l’espacement interne.

Beautiful Soup voit uniquement le HTML qui lui est fourni. Un élément créé plus tard par JavaScript dans un navigateur n’apparaîtra pas dans ce seul document initial. Avant de changer vos sélecteurs pour une page externe, comparez la réponse reçue et le contenu rendu afin de comprendre où les données sont produites. Les expressions régulières restent utiles pour certains motifs textuels, mais ne remplacent pas l’arbre HTML pour ces relations d’éléments.

Détecter les changements et traiter les échecs explicitement

  1. Changez la classe du second nom de name en label. La recherche doit échouer et lever ValueError: missing or invalid field.
  2. Rétablissez la classe, puis remplacez le second montant par -1 ou abc. Les deux valeurs doivent être refusées. Modifiez un seul point par essai pour savoir quelle règle vous examinez.
  3. Rétablissez le montant et ajoutez une balise imbriquée dans le premier nom, par exemple coffee <strong>large</strong>. Vérifiez le texte extrait, puis revenez au document initial pour retrouver le total de 11 500.

Si l’erreur concerne la deuxième ligne, le premier affichage peut déjà avoir eu lieu : ce programme imprime au fil de sa boucle. Une ligne sur la sortie standard ne prouve donc pas que l’extraction est complète. Vérifiez le code de sortie immédiatement avec $LASTEXITCODE sous PowerShell ou echo $? dans un shell macOS/Ubuntu, ainsi que la fin normale du traitement et le total.

Le nombre deux est un contrat pour ce document d’exercice, pas une règle à copier sur toute page. Une application doit définir des attentes adaptées aux listes réellement variables : bornes plausibles, présence des champs, contexte et date de collecte. Zéro résultat peut être normal ou signaler une structure changée ; décidez comment distinguer ces situations au lieu de masquer toutes les erreurs par une liste vide.