Tecnologie e protezioni 8 min di lettura

Espressioni regolari per il parsing: sintassi, pattern e limiti

Espressioni regolari nel parsing dei dati: sintassi di base, pattern utili, limiti di applicazione e perché per l’HTML serve un parser e non le regex.

TW
Team Web-Scraping.it
Raccolta dati per le esigenze del business
Pubblicato il 27 aprile 2025

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:

code
[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 virgola

Gli 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ù
code
\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:

code
".*"              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:

code
^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'altra

Le 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.

code
(\d+)[.,](\d+)        parte intera e parte decimale del numero, separatamente

I 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 (?: ):

code
(?:https?://)?(\S+)   «http://» o «https://» facoltativo, senza salvarlo

Gruppi con nome

Per non contare i numeri, al gruppo si dà un nome (?P<name>...) (in Python) o (?<name>...):

code
(?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:

code
<(\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)
code
\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 numero

Il 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
python
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»:

python
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.5

Tutti i prezzi nel testo (il nuovo e il vecchio):

python
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:

python
sku = re.search(r"\bCodice[:\s]+([A-Z0-9\-]+)", text)

Telefono (normalizzazione a sole cifre):

python
digits = re.sub(r"\D", "", "+39 02 1234 5678")   # «390212345678»

Email:

python
emails = re.findall(r"[\w.+-]+@[\w-]+\.[\w.-]+", text)

Estrarre un valore JSON da uno script inline:

python
# 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:

python
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.