Tecnologie e protezioni 8 min di lettura

Selettori CSS per il web scraping: panoramica dei costrutti

Guida rapida ai selettori CSS per l’estrazione di dati: classi, attributi, pseudo-classi, combinatori ed errori tipici nella selezione degli elementi.

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

Nel materiale precedente — «Parser del DOM»: abbiamo visto come il browser e le librerie trasformano l’HTML in un albero di nodi e come si può percorrere quell’albero via codice. Ma attraversare il DOM «a mano», passando di genitore in figlio, è noioso e fragile. Nella pratica dello scraping di siti web, il nodo che serve si individua quasi sempre con un’unica espressione breve: un selettore CSS.

I selettori CSS nascono per i fogli di stile: descrivono a quali elementi applicare una regola. Ma la stessa sintassi è perfetta anche per trovare gli elementi. Per questo quasi tutti gli strumenti di scraping — da BeautifulSoup a Scrapy e Playwright: supportano la selezione via CSS. Vediamo i costrutti uno per uno.

A cosa servono i selettori CSS nello scraping

Un selettore è un modo dichiarativo di dire «dammi tutti gli elementi fatti così». Invece di una decina di righe che attraversano l’albero, scrivi una sola riga, e il motore scende lungo il DOM e restituisce le corrispondenze. Il risultato è:

  • compatto: un selettore al posto di un ciclo;
  • leggibile: l’espressione somiglia alla descrizione della posizione dell’elemento nella pagina;
  • portabile: lo stesso selettore funziona in Python, in JS e nella console del browser (document.querySelectorAll).

L’abilità chiave nello scraping è guardare la struttura della pagina in DevTools e comporre un selettore che colpisca esattamente il bersaglio e, allo stesso tempo, sopravviva alle piccole modifiche del layout.

Selettori di base

Sono gli atomi con cui si costruisce tutto il resto.

Selettore Che cosa seleziona Esempio
* qualsiasi elemento *
div elementi per nome di tag a, span, tr
.price elementi con la classe price .product-card
#main l’elemento con id="main" #catalog

Classe e tag si possono incollare senza spazio — è una «E» logica: a.button significa «link che ha la classe button». E div.card.featured è un div con due classi insieme.

css
span.price.current     /* <span class="price current"> */
li#first-item          /* <li id="first-item"> */

Raggruppamento

La virgola unisce più selettori in un’unica lista: tornano gli elementi che corrispondono a uno qualsiasi di essi:

css
h1, h2, h3             /* tutti i titoli di questi tre livelli */
.price-new, .price-old /* sia il prezzo nuovo sia quello vecchio */

Comodo quando il prodotto in alcune schede ha la classe price-new e in altre price-old, e bisogna catturare entrambe le varianti con un’unica query.

Combinatori: relazioni tra elementi

I combinatori descrivono come gli elementi sono disposti l’uno rispetto all’altro nell’albero. È la parte più importante per uno scraping robusto.

Discendente (spazio)

A B — un elemento B che si trova da qualche parte dentro A, a qualsiasi profondità.

css
.product-card .price   /* .price in un punto qualsiasi dentro la scheda */

Figlio diretto (>)

A > BB è figlio immediato di A. Non scende più in profondità.

css
ul.menu > li           /* solo le voci di primo livello del menu, senza quelle annidate */

La differenza è sostanziale: .menu li cattura anche le voci dei sottomenu, .menu > li no.

Adiacente (+)

A + B — l’elemento B che viene subito dopo A e ha lo stesso genitore.

css
h2 + p                 /* il primo paragrafo subito dopo il titolo */
.label + .value        /* il valore subito dopo la sua etichetta */

Fratelli generici (~)

A ~ B — tutti i B che vengono dopo A sotto lo stesso genitore (non necessariamente attaccati).

css
.in-stock ~ .price     /* tutti i prezzi che seguono il contrassegno «disponibile» */

Selettori di attributi

Spesso l’elemento non ha una classe comoda, ma ha un attributo caratteristico. I selettori di attributi salvano la situazione quando le classi sono dinamiche o piene di rumore.

Costrutto Significato
[data-id] esiste l’attributo data-id (con qualsiasi valore)
[type="submit"] corrispondenza esatta del valore
[class~="price"] il valore contiene la parola price tra parole separate da spazi
[href^="https"] il valore inizia con https
[href$=".pdf"] il valore finisce con .pdf
[href*="product"] il valore contiene la sottostringa product
[lang|="en"] il valore è en oppure inizia con en-
[type="text" i] il flag i — confronto senza distinguere maiuscole e minuscole
css
a[href^="/catalog/"]           /* link interni al catalogo */
img[src$=".webp"]              /* immagini in formato webp */
div[data-testid*="product"]    /* contenitori con il marcatore di prodotto in un attributo data */
input[name="csrf"]             /* campo nascosto del token */

I selettori di attributi sono particolarmente preziosi nel monitoraggio dei prezzi dei negozi online: le grandi piattaforme spesso espongono i dati in attributi data-* (data-price, data-sku, data-currency), e aggrapparsi a questi è più affidabile che a classi che cambiano a ogni release del frontend.

Pseudo-classi: selezione per posizione e stato

Le pseudo-classi filtrano gli elementi in base alla loro posizione tra i vicini o a condizioni aggiuntive.

Posizionali

css
li:first-child         /* primo figlio */
li:last-child          /* ultimo figlio */
tr:nth-child(2)        /* seconda riga */
tr:nth-child(odd)      /* righe dispari */
tr:nth-child(even)     /* righe pari */
tr:nth-child(3n)       /* una riga ogni tre */
li:nth-last-child(1)   /* il primo dalla fine */

La formula an+b di :nth-child() è flessibile: nth-child(2n+1) seleziona i dispari, nth-child(n+3) tutti a partire dal terzo.

Per tipo di tag

:nth-child conta tutti i fratelli di fila, mentre :nth-of-type conta solo gli elementi dello stesso tag:

css
p:first-of-type        /* il primo paragrafo tra i fratelli */
img:nth-of-type(2)     /* la seconda immagine */
span:last-of-type      /* l'ultimo span */

È la salvezza quando il <p> che cerchi è il secondo paragrafo, ma prima di lui si sono infilati tag di altro tipo.

Negazione e stato

css
input:not([type="hidden"])   /* tutti i campi tranne quelli nascosti */
li:not(.sold-out)            /* prodotti non marcati come esauriti */
a:not([rel="nofollow"])      /* link senza nofollow */

:not() accetta al suo interno un altro selettore e lo inverte. Esistono anche :empty (elementi vuoti), :checked, :disabled e altri — ma nello scraping statico si usano soprattutto le pseudo-classi posizionali e :not().

Nota bene: gli pseudo-elementi (::before, ::after, ::first-line) nello scraping non si usano — descrivono contenuto generato dagli stili, che non esiste nell’HTML di partenza.

Costruiamo un selettore complesso

Le espressioni reali combinano tutto quanto visto sopra. Un esempio: estrarre il prezzo corrente dalla scheda prodotto di un catalogo:

css
div.product-card a[href^="/product/"] span.price:not(.price--old)

Si legge così: dentro la scheda prodotto, nel link alla pagina del prodotto, prendi lo span con la classe price che non è il prezzo vecchio (barrato).

Strumenti: dove funziona tutto questo

I selettori CSS sono supportati da quasi tutto lo stack di scraping.

Python — BeautifulSoup (tramite la libreria soupsieve):

python
from bs4 import BeautifulSoup

soup = BeautifulSoup(html, "html.parser")

# .select() restituisce una lista; .select_one() il primo elemento
cards = soup.select("div.product-card")
for card in cards:
    title = card.select_one("h3.title").get_text(strip=True)
    price = card.select_one("span.price:not(.price--old)").get_text(strip=True)
    print(title, price)

Python — parsel / Scrapy (un wrapper sopra lxml, veloce):

python
from parsel import Selector

sel = Selector(text=html)
prices = sel.css("span.price::text").getall()   # ::text è un'estensione propria di parsel
links = sel.css("a.product-link::attr(href)").getall()

Qui ::text e ::attr(name) sono pseudo-elementi non standard di Scrapy/parsel: estraggono direttamente il testo o l’attributo, senza bisogno di una chiamata a parte.

JavaScript / browser — API nativa, stessa sintassi:

javascript
const cards = document.querySelectorAll("div.product-card");
const price = document.querySelector("span.price:not(.price--old)")?.textContent.trim();

Playwright / Selenium — per le pagine dinamiche, dove i dati vengono caricati dagli script:

python
# Playwright
price = page.locator("div.product-card span.price").first.inner_text()

In altre parole: impari i selettori una volta e li usi sia negli scraper HTTP leggeri sia nei browser headless.

Applicazione nel monitoraggio dei prezzi

In un tipico progetto di monitoraggio dei prezzi dei negozi online lo scenario è questo:

  1. Elenco dei prodotti del catalogo. Un selettore del tipo div.catalog__item trova tutte le schede nella pagina dei risultati.
  2. Link al prodotto. Da ogni scheda, a[href^="/product/"]::attr(href) estrae l’URL.
  3. Prezzo e disponibilità. Nella pagina del prodotto, [data-price]::attr(data-price) o span.price--current fornisce il prezzo, mentre .availability:not(.out-of-stock) conferma che il prodotto è in vendita.
  4. Prezzo vecchio e sconto. span.price--old cattura il prezzo barrato, per calcolare l’entità dello sconto.

Poiché ogni concorrente ha un layout diverso, per ciascuna piattaforma si scrive un set di selettori dedicato, e il motore di scraping di siti web li applica secondo un calendario e riversa i risultati in una tabella comune per il confronto.

Errori frequenti e consigli

  • Non aggrapparti alle classi «spazzatura». Le classi tipo .css-1ab2c3 vengono generate dai bundler e cambiano a ogni deploy. Preferisci gli attributi data-*, gli id significativi o i combinatori strutturali.
  • Usa > dove la struttura conta. Taglia fuori le corrispondenze casuali nei blocchi annidati.
  • Verifica il selettore in DevTools. Il comando $$("il tuo selettore") nella console del browser mostra subito quanti elementi sono stati trovati.
  • Prevedi delle varianti. Elenca, separate da virgola, più possibili classi del prezzo: aumenta la resistenza agli A/B test del negozio.
  • Ricorda i limiti dei CSS. I selettori non sanno «risalire al genitore» in base al contenuto né filtrare per il testo del nodo. Dove il CSS non arriva, entrano in gioco XPath o le espressioni regolari — ne parliamo nel prossimo materiale.

Conclusione

I selettori CSS sono lo strumento principale di indirizzamento nel DOM. I selettori di base (tag, classe, id) definiscono il «che cosa», i combinatori (spazio, >, +, ~) il «dove», e i selettori di attributi insieme alle pseudo-classi aggiungono condizioni precise. Insieme coprono la stragrande maggioranza dei compiti di scraping di siti web, e un selettore ben costruito rende lo scraper resistente ai piccoli ritocchi del layout — un aspetto critico quando si parla di monitoraggio dei prezzi dei negozi online su base regolare.

Nel prossimo articolo vedremo come estrarre dati dal testo «grezzo» con le espressioni regolari, là dove la struttura del DOM non basta più.