Questo è il terzo articolo della serie. In «Parser del DOM» abbiamo imparato a trasformare la pagina in un albero di nodi, e nell’articolo sui selettori CSS: a indirizzare con precisione gli elementi che servono in quell’albero. Ma quando il nodo è stato trovato, al suo interno c’è spesso testo «sporco»: «Prezzo: 12 990 € (prima 15 990)», un telefono con caratteri di troppo, un codice articolo mescolato alla descrizione. A estrarre da una stringa così proprio il numero o il codice servono le espressioni regolari.
Un’espressione regolare (regex) è un pattern che descrive un insieme di stringhe. Il motore regex scorre il testo e trova i frammenti che corrispondono al pattern. Nello scraping di siti web è uno strumento insostituibile di pulizia finale ed estrazione dei dati.
Quando servono le regex: e quando no
Prima di tutto, un avvertimento importante. Con le espressioni regolari non si fa il parsing dell’HTML per intero. L’HTML è una struttura annidata e non regolare; provare a smontarla con un unico pattern porta a codice fragile e illeggibile. Per navigare nel markup ci sono i parser del DOM e i selettori CSS degli articoli precedenti.
Le regex danno il meglio in un altro ruolo: elaborare un testo breve già estratto:
- tirare fuori il numero del prezzo da una stringa con valuta e spazi;
- normalizzare un telefono o un’email;
- estrarre codice articolo, SKU, una data, l’entità dello sconto;
- scomporre i parametri di un URL o del testo di uno
<script>.
La regola è semplice: la struttura si prende con i selettori, il valore dentro il nodo: con una regex.
Sintassi di base
La maggior parte dei caratteri in un pattern rappresenta sé stessa: il pattern prezzo troverà la sottostringa «prezzo». La potenza arriva con i caratteri speciali (metacaratteri).
| Simbolo | Significato |
|---|---|
. |
qualsiasi carattere (tranne l’a capo) |
\d |
cifra 0–9 |
\D |
non cifra |
\w |
lettera, cifra o _ |
\W |
l’opposto di \w |
\s |
carattere di spaziatura (spazio, tab, a capo) |
\S |
carattere che non è di spaziatura |
\b |
confine di parola |
Per usare un metacarattere come carattere normale (per esempio il punto in un numero), si fa l’escape con il backslash: \., \$, \(.
Classi di caratteri
Le parentesi quadre definiscono un insieme di caratteri ammessi in una posizione:
[aeiou] una delle vocali elencate
[0-9] qualsiasi cifra (lo stesso di \d)
[a-zA-Z] qualsiasi lettera latina di base
[a-zA-ZàèéìòóùÀÈÉÌÒÓÙ] qualsiasi lettera italiana, accenti compresi
[^0-9] qualsiasi carattere TRANNE una cifra (^ tra quadre = negazione)
[\d.,] una cifra, un punto o una virgolaGli intervalli (a-z, 0-9) e le enumerazioni si combinano. ^ all’inizio della classe inverte l’insieme.
Quantificatori: quante ripetizioni
Il quantificatore indica quante volte si ripete l’elemento precedente.
| Quantificatore | Quante volte |
|---|---|
* |
0 o più |
+ |
1 o più |
? |
0 o 1 (facoltativo) |
{3} |
esattamente 3 |
{2,5} |
da 2 a 5 |
{2,} |
2 o più |
\d+ una o più cifre: «12990»
\d{1,3} da una a tre cifre (un gruppo di migliaia)
colou?r «color» e «colour»Avidità e pigrizia
Di default i quantificatori sono avidi (greedy) — catturano il più possibile. Un ? aggiunto li rende pigri (lazy) — catturano il minimo:
".*" avido: dalla prima virgoletta all'ULTIMA della stringa
".*?" pigro: dalla prima virgoletta alla PIÙ VICINAÈ una delle trappole più frequenti: il pattern avido «si mangia» testo di troppo. In pratica, per estrarre valori brevi serve quasi sempre la variante pigra o una classe di caratteri più ristretta al posto di ..
Ancore e confini
Le ancore non corrispondono a caratteri — agganciano il pattern a una posizione:
^inizio ^ — inizio della stringa
fine$ $ — fine della stringa
^\d+$ la stringa è composta interamente di cifre
\bSKU\b «SKU» come parola a sé, non come parte di un'altraLe ancore proteggono dai falsi positivi: ^\d+$ garantisce che nella cella ci sia solo un numero, e non «12 pz».
Gruppi
Le parentesi tonde ( ) raggruppano una parte del pattern e catturano il frammento corrispondente, per poterlo recuperare in seguito.
(\d+)[.,](\d+) parte intera e parte decimale del numero, separatamenteI gruppi si numerano da sinistra a destra: il gruppo 1 — (\d+) prima del separatore, il gruppo 2 — quello dopo.
Gruppi senza cattura
Se le parentesi servono solo a raggruppare (per esempio sotto un quantificatore) ma il risultato non interessa, si usa (?: ):
(?:https?://)?(\S+) «http://» o «https://» facoltativo, senza salvarloGruppi con nome
Per non contare i numeri, al gruppo si dà un nome (?P<name>...) (in Python) o (?<name>...):
(?P<eur>\d+)[.,](?P<cent>\d{2})Poi vi si accede con match.group("eur"): leggibile e resistente al riordino dei gruppi.
Riferimenti all’indietro
Dentro il pattern si può fare riferimento a un gruppo già catturato con \1, \2:
<(\w+)>.*?</\1> tag appaiato: quello di apertura e quello di chiusura coincidono nel nome
(["']).*?\1 stringa tra virgolette singole o doppie\1 esige che nella seconda posizione compaia lo stesso carattere che ha fatto match nel primo gruppo.
Verifiche in avanti e all’indietro
Le costruzioni lookaround verificano il contesto senza includerlo nel risultato — utilissime per «agganciarsi» a un’etichetta e restituire solo il valore.
| Costruzione | Significato |
|---|---|
(?=...) |
davanti c’è... (positive lookahead) |
(?!...) |
davanti NON c’è... (negative lookahead) |
(?<=...) |
dietro c’è... (positive lookbehind) |
(?<!...) |
dietro NON c’è... (negative lookbehind) |
\d+(?=\s*€) numero SEGUITO dal simbolo dell'euro — viene restituito solo il numero
(?<=Prezzo:\s)\d+ numero PRECEDUTO da «Prezzo: » — viene restituito solo il numero
\d+(?!\d) l'ultima cifra del numeroIl lookbehind è particolarmente comodo quando bisogna estrarre il valore che segue un’etichetta fissa senza trascinarsi l’etichetta nel risultato.
Flag (modificatori)
I flag cambiano il comportamento dell’intero pattern:
| Flag (Python) | Effetto |
|---|---|
re.I / (?i) |
senza distinguere maiuscole e minuscole |
re.S / (?s) |
. corrisponde anche all’a capo |
re.M / (?m) |
^ e $ agiscono su ogni riga |
re.X / (?x) |
modalità «verbosa» — consente di commentare il pattern |
re.findall(r"prezzo", text, re.I) # «Prezzo», «PREZZO», «prezzo»Esempi pratici per lo scraping
Raccogliamo i pattern tipici in Python (modulo re). Funzioni utili: re.search (prima corrispondenza), re.findall (tutte, come lista), re.sub (sostituzione), re.finditer (iteratore con i gruppi).
Il prezzo da una stringa «sporca»:
import re
text = "Prezzo: 12 990,50 € (prima 15 990 €)"
# Estraiamo il primo numero ignorando gli spazi che separano le migliaia
m = re.search(r"(\d[\d\s]*\d|\d)(?:[.,](\d{2}))?", text)
eur = re.sub(r"\s", "", m.group(1)) # «12990»
cent = m.group(2) or "00" # «50»
price = float(f"{eur}.{cent}") # 12990.5Tutti i prezzi nel testo (il nuovo e il vecchio):
prices = re.findall(r"\d[\d\s]*\d(?=\s*€)", text)
# ['12 990', '15 990'] → poi li ripuliamo dagli spazi
clean = [int(re.sub(r"\D", "", p)) for p in prices] # [12990, 15990]Codice articolo / SKU:
sku = re.search(r"\bCodice[:\s]+([A-Z0-9\-]+)", text)Telefono (normalizzazione a sole cifre):
digits = re.sub(r"\D", "", "+39 02 1234 5678") # «390212345678»Email:
emails = re.findall(r"[\w.+-]+@[\w-]+\.[\w.-]+", text)Estrarre un valore JSON da uno script inline:
# Nella pagina prodotto il prezzo sta spesso in uno <script> come window.__DATA__ = {...}
m = re.search(r'"price"\s*:\s*"?(\d+(?:\.\d+)?)"?', script_text)Le regex in combinazione con DOM e CSS
La regex rende al massimo non al posto degli strumenti degli articoli precedenti, ma insieme a loro:
from bs4 import BeautifulSoup
import re
soup = BeautifulSoup(html, "html.parser")
# 1) Il selettore CSS estrae il nodo che serve
raw = soup.select_one("span.price").get_text(strip=True)
# 2) La regex ripulisce il testo fino a lasciare il numero
price = int(re.sub(r"\D", "", raw))Il selettore risponde al «dove», la regex — al «che cosa esattamente di quel testo». Questa separazione rende lo scraper preciso e leggibile allo stesso tempo.
Applicazione nel monitoraggio dei prezzi
Nel monitoraggio dei prezzi dei negozi online le espressioni regolari coprono l’«ultimo miglio» dell’estrazione dei dati:
- Normalizzazione del prezzo a un unico formato numerico: togliere gli spazi separatori e la valuta, ricondurre la virgola al punto — altrimenti i valori delle diverse piattaforme non si possono confrontare.
- Scomposizione dello sconto da stringhe come «−23%» o «risparmi 3.000 €».
- Pulizia della disponibilità: da «ne restano 4 pz.» tirare fuori la quantità
\d+. - Identificativi del prodotto: SKU, EAN/codice a barre (
\b\d{13}\b), codice articolo — per abbinare lo stesso prodotto presso venditori diversi. - Dati dagli script: molti negozi mettono il prezzo in un JSON dentro
<script type="application/ld+json">o in variabili inline — la regex estrae il campo che serve senza un parsing completo del JS.
La combinazione «selettore → regex» è la base operativa del motore di scraping di siti web: visita le schede prodotto secondo un calendario, estrae i nodi via CSS e riconduce il loro contenuto a numeri puliti con le espressioni regolari.
Prestazioni e insidie
- Compila i pattern usati di frequente:
re.compile(...)una volta sola, invece di rianalizzare il pattern in un ciclo su migliaia di prodotti. - Evita il backtracking catastrofico. I quantificatori ambigui annidati come
(\d+)+o(.*)*possono «bloccarsi» su una stringa scelta apposta (o per caso). Tieni ristrette le classi di caratteri e preferisci i quantificatori pigri a quelli avidi dove ha senso. - Non complicare oltre misura. Se il pattern è diventato illeggibile, spezza il compito in due regex semplici o torna ai selettori: forse il valore che cerchi sta in un attributo
data-separato, più facile da prendere direttamente (vedi l’articolo sui selettori CSS). - Prova su esempi reali. I prezzi si scrivono in mille modi:
1 990,1.990,1990,00,da 1990. Fai girare il pattern su un set di stringhe vere prese da negozi diversi.
Conclusione
Le espressioni regolari sono uno strumento per estrarre valori dal testo, non per smontare strutture. I metacaratteri e le classi descrivono quali caratteri aspettarsi, i quantificatori — quanti, i gruppi e il lookaround — come isolare il frammento giusto nel suo contesto. In coppia con il parsing del DOM e i selettori CSS formano la filiera completa dello scraping di siti web: trovare il nodo, estrarre il testo, ridurlo a dati puliti. È proprio questa combinazione a garantire un monitoraggio dei prezzi dei negozi online affidabile, dove dal markup eterogeneo dei concorrenti bisogna ricavare con regolarità numeri confrontabili.
Con questo si chiude la serie di base sugli strumenti di scraping: il DOM fornisce l’albero, i selettori CSS (l’indirizzamento, le espressioni regolari) la pulizia finale dei dati.