Corso Python / Estrarre dati HTML con Beautiful Soup

Per estrarre dati da HTML, usa un parser per selezionare gli elementi e leggerne testo e attributi. Controlla anche numero di risultati e campi obbligatori: una struttura cambiata non deve trasformarsi silenziosamente in una raccolta apparentemente valida ma vuota.

Partire da un documento statico controllato

Questo esercizio usa una stringa HTML creata nel programma e non invia richieste a siti web. Separare la lettura di un documento dalla raccolta esterna permette di capire il comportamento dei selettori senza dipendere da una connessione. Per un sito esterno dovrai valutare anche accesso, condizioni del servizio, frequenza delle richieste e trattamento dei dati personali.

Beautiful Soup è una libreria esterna. Il pacchetto da installare nell’ambiente virtuale si chiama beautifulsoup4, mentre il modulo da importare si chiama bs4:

python -m pip install beautifulsoup4

Nel costruttore scegliamo "html.parser", il parser incluso in Python. La documentazione ufficiale di Beautiful Soup descrive selettori, attributi e testo. Specificare il parser rende chiaro come viene costruito l’albero; documenti non validi possono essere interpretati in modo diverso da parser differenti.

Eseguire l’estrazione completa

In una cartella di esercitazione salva questo contenuto UTF-8 come parse_html.py. Il nome evita di nascondere il pacchetto bs4 con un file locale omonimo:

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)

Dal terminale nella stessa cartella, con l’ambiente selezionato, esegui python parse_html.py. Usa python3 se quello è il nome del tuo interprete su macOS/Ubuntu. L’esito atteso è:

coffee 3500
lunch 8000
total: 11500

I due elementi li hanno importi 3500 e 8000, la cui somma è 11.500. Il programma richiede esattamente due righe, trova il nome all’interno di ciascuna e controlla l’attributo prima di convertirlo. La combinazione di isascii() e isdecimal() ammette soltanto cifre decimali ASCII, senza segni, spazi o separatori. Zero è valido; -1, abc e 8.5 vengono rifiutati.

Questi sono controlli selezionati per il documento dell’esempio, non una validazione universale di ogni HTML o importo. Il programma verifica che esista un elemento nome, ma non che il suo testo sia non vuoto; non rileva nomi duplicati e non impone un limite economico all’importo. Se questi requisiti servono, definiscili e aggiungi le verifiche relative.

Leggere selettori e testo senza confonderli

Nel selettore #expenses > li, #expenses individua l’ID e > richiede un figlio diretto. #expenses li, senza il simbolo, includerebbe anche discendenti più profondi. Il selettore .name richiede una classe, non il nome di un elemento HTML. Queste distinzioni consentono di restringere l’estrazione alla zona corretta anche quando altrove esistono elementi simili.

select restituisce l’insieme dei risultati; select_one restituisce il primo elemento corrispondente o None se manca. Controlla il risultato prima di accedere al testo. row.get("data-amount") restituisce il valore dell’attributo oppure None: leggerlo come stringa senza verifica nasconderebbe il motivo dell’errore.

get_text(" ", strip=True) separa con spazi i frammenti testuali e rimuove gli spazi esterni da ciascun frammento. Per esempio, sostituendo coffee con <b>coffee</b> shop ottieni coffee shop. L’annidamento non obbliga a cercare la stringa HTML con un’espressione regolare. La lezione sulle espressioni regolari per i testi tratta un compito diverso dall’interpretazione della struttura del documento.

Beautiful Soup vede soltanto l’HTML ricevuto. Un browser può mostrare elementi creati in seguito da JavaScript, assenti nella risposta iniziale: il parser non esegue quel codice per generarli. Prima di cambiare ripetutamente un selettore, confronta il documento fornito con i dati che ti aspetti di trovare.

Rilevare le modifiche e stabilire quando fallire

  1. Cambia la classe name in label nell’HTML, lasciando il selettore invariato. Il programma deve segnalare missing or invalid field; ripristina poi la classe.
  2. Cambia soltanto il primo data-amount in -1 oppure abc e controlla il rifiuto. Ripristina 3500 prima della prova seguente.
  3. Aggiungi un tag annidato nel primo nome e verifica testo e totale invariato; poi torna al documento originale.
  4. Cambia l’ID expenses nel documento. La selezione deve trovare zero righe e il controllo sul conteggio deve fallire, invece di stampare una somma vuota come se fosse un successo.

Il conteggio atteso pari a due è adatto alla prova controllata. In un raccoglitore reale scegli una politica motivata per quantità, campi obbligatori e contesto di raccolta: un elenco può anche essere legittimamente vuoto, ma deve essere distinguibile da un selettore non più valido.

Il ciclo stampa ogni riga mentre la verifica. Se la seconda riga è errata, una prima riga può essere già comparsa nell’uscita standard prima dell’eccezione. Non distribuire quelle stampe parziali come risultato completo: controlla il codice di uscita o valida e accumula tutto prima della pubblicazione del risultato.