Vai al contenuto principale
Torna al blog
Web Scraping Dati Node.js Playwright

API, RSS, HTML o browser headless: come scegliere la strategia di scraping per ogni fonte

Quattro strategie di web scraping, dalla più stabile alla più fragile: i guasti reali di ognuna e come fare il parsing delle date in spagnolo.

JM
Javier Manzano
CEO & Co-founder • 9 ottobre 2026
API, RSS, HTML o browser headless: come scegliere la strategia di scraping per ogni fonte

Quando ci arriva una lista di siti da cui estrarre dati, la tentazione è aprire Playwright e iniziare a scrivere selettori. Delle quattro strategie che usiamo è la più lenta, quella che consuma più memoria e quella che si rompe più spesso.

In un progetto di aggregazione che manteniamo, con più di 35 fonti pubbliche (portali open data, siti in WordPress, Drupal, React e qualcuno fatto a mano), siamo arrivati a quattro strategie dietro un’unica interfaccia e a una regola per scegliere tra loro: la prima della lista che la fonte consente.

Le quattro strategie, dalla più stabile alla più fragile

StrategiaQuandoCostoCosa si rompe
API pubblica o internaIl sito carica i dati da un JSON~200 ms per richiestaCambi di schema, poco frequenti
RSS / AtomC’è un feed e bastano titolo, link e riassunto~200 ms per feedLa data del feed non è quella del contenuto
HTML staticoIl contenuto arriva nell’HTML del serverUna richiesta per elenco, più le schedeI selettori, a ogni redesign
Browser headlessJavaScript, scroll infinito, «carica altro»3-5 s per pagina, 100-200 MB di RAMTempi di attesa e il binario del browser

Scendere di un gradino costa velocità e stabilità. Per questo l’inventario di ogni fonte parte sempre dall’alto.

1. Cerca l’API prima di guardare l’HTML

Molti più siti di quanto sembri caricano i dati da un JSON. Ci sono tre posti dove guardare, in quest’ordine.

Portali open data. Molte pubbliche amministrazioni pubblicano i loro dataset in JSON o CSV con licenza di riutilizzo. Sono la fonte migliore possibile, anche se non la più pulita. In uno dei nostri, il prezzo arriva come stringa vuota quando non c’è prezzo, il campo «gratuito» arriva come 0 o 1 numerico e l’indirizzo è un oggetto annidato con chiavi con trattino (street-address) che la nostra mappatura per percorsi con punti non risolveva al primo colpo. Nessuno di questi dettagli è nella documentazione del portale: emergono elaborando la prima risposta reale.

WordPress. Una parte enorme dei siti piccoli e medi è WordPress, e molti lasciano aperta la REST API. Prova /wp-json/wp/v2/posts. Se il sito usa un plugin di calendario come The Events Calendar, /wp-json/tribe/events/v1/events restituisce gli eventi con data di inizio, data di fine, luogo e costo già strutturati. Tre delle nostre fonti, che a prima vista richiedevano uno scraper HTML, escono così.

La scheda Network. Apri i DevTools, filtra per Fetch/XHR e ricarica. Se il sito è una SPA, i dati arrivano da qualche endpoint, che spesso accetta parametri di data e paginazione. Una delle nostre fonti, una biglietteria online, restituisce solo una finestra di due settimane, quindi l’URL salvato nella configurazione contiene dei segnaposto che si risolvono a ogni esecuzione:

https://ejemplo.com/api/sesiones?desde={{today}}&hasta={{today+14}}

Prima di usare un endpoint interno conviene leggere i termini d’uso del sito. Che un JSON sia accessibile non significa che il suo riutilizzo sia consentito.

2. RSS, veloce ma con una trappola nella data

Un feed RSS si elabora in millisecondi e non cambia quasi mai formato. Il problema è ciò che non contiene. Il pubDate di ogni elemento è la data in cui è stato pubblicato l’articolo, non quella dell’evento, dell’offerta o del prodotto di cui parla. E non ci sono prezzo, luogo né data di fine.

Inoltre i feed dei media mescolano i contenuti. Uno di quelli che usiamo include articoli, schede di locali e guide nello stesso canale; il guid ha un prefisso per tipo (Content-Venue-213605, Content-Article-…) ed è ciò che permette di separarli.

Usiamo l’RSS come fonte di scoperta: rileva che c’è qualcosa di nuovo e, se serve, visitiamo la scheda per completare i dati.

3. HTML statico, con i selettori in configurazione

Quando il contenuto arriva nell’HTML restituito dal server, bastano una richiesta HTTP e cheerio. La decisione che qui ci ha fatto risparmiare più tempo è tenere i selettori nella configurazione della fonte e non nel codice:

{
  "listUrl": "https://ejemplo.com/agenda",
  "selectors": {
    "card": ".agenda-item",
    "title": ".agenda-item__title",
    "link": "a",
    "date": ".agenda-item__date",
    "image": "img"
  }
}

Quando il sito cambia design, la correzione consiste nel modificare quel JSON. Non serve fare un deploy.

Due casi da prevedere:

  • Classi con hash. I siti fatti con Gatsby o Next.js e CSS Modules generano classi come card-module--title--a1b2c, e l’hash cambia a ogni build del sito. Il selettore deve andare per prefisso: [class*="card-module--title"].
  • Elenchi incompleti. Se la card dell’elenco non porta la descrizione, bisogna visitare la scheda di ogni elemento. Lo facciamo a lotti con concorrenza limitata e solo per gli elementi che non ce l’hanno già.

4. Browser headless, quando non resta altro

Playwright serve in tre situazioni: il contenuto viene disegnato con JavaScript, la pagina carica altri risultati con lo scroll o con un pulsante «carica altro», oppure il sito rifiuta qualsiasi client HTTP che non sia un browser.

Cosa abbiamo imparato mantenendolo:

  • Aspetta un selettore preciso, non un tempo fisso. waitForSelector('.agenda-item') è affidabile; waitForTimeout(3000) fallisce il giorno in cui il sito è lento.
  • networkidle può non arrivare mai nei siti con analytics o chat che continuano a fare richieste. Dopo aver cliccato «carica altro» accettiamo il timeout di networkidle e controlliamo se sono comparse nuove card.
  • Chiudi il browser in un finally. Ogni istanza di Chromium occupa tra 100 e 200 MB, e un browser che resta aperto dopo un’eccezione finisce per mandare giù il worker.
  • Il binario fa parte del deploy. Lanciamo Chromium da un percorso fissato tramite variabile d’ambiente, e l’immagine Docker lo installa. Un ambiente rimasto senza quella variabile ha avuto una fonte ferma con browserType.launch: Executable doesn't exist, e il codice non aveva alcun difetto.

Un browser proprio non risolve tutti i blocchi. Diverse nostre fonti restituiscono 403 a qualsiasi client HTTP, una di esse con il robots.txt aperto. Se il blocco dipende dall’IP o dall’impronta del browser, nemmeno un Playwright lanciato da un server in datacenter passa. Per questi casi integriamo Bright Data sulla singola fonte, senza toccare le altre.

Il problema che hanno tutte: le date in spagnolo

La strategia non conta: alla fine bisogna convertire testo in date. Nelle nostre fonti convivono «Del 10 al 28 de junio», «Martes, 3 de marzo», «jun 25», «25.06» e «25/06/2026».

La trappola sta nel Date di JavaScript. In Node:

new Date('25 junio')  // Mon Jun 25 2001
new Date('jun 25')    // Mon Jun 25 2001
new Date('10')        // Mon Oct 01 2001

V8 riconosce «jun» come giugno dalle prime tre lettere e, senza anno, va al 2001. Non lancia alcun errore: l’evento viene salvato con una data di 25 anni fa e sparisce da tutti gli elenchi.

Le regole che applichiamo, in quest’ordine:

  1. Togliere il giorno della settimana all’inizio («Martes, »).
  2. Provare i formati numerici espliciti: DD.MM.YYYY, DD.MM, DD/MM/YYYY.
  3. Provare i nomi dei mesi in spagnolo prima di new Date(), e usare l’anno in corso quando l’anno manca.
  4. Usare new Date() solo alla fine, e scartare i numeri isolati e qualsiasi anno precedente al 2000.
  5. Negli intervalli come «Del 10 al 28 de junio», l’inizio eredita mese e anno dalla fine.

Ogni formato strano che compare in una nuova fonte diventa un caso di test.

Un’interfaccia, quattro implementazioni

Ciò che permette di mescolare strategie senza complicare il sistema è che tutte restituiscono la stessa cosa:

interface SourceStrategy {
  fetch(source: Source): Promise<RawItem[]>;
}

Un registro risolve la strategia in base alla chiave salvata in ogni fonte (api, rss, html-scraper, dynamic). Poi tutti i risultati passano dallo stesso normalizzatore (rimuovere l’HTML, pulire gli spazi, convertire le categorie in slug, dedurre se è gratuito) e vengono salvati con una chiave univoca per fonte e identificativo esterno. Rieseguire una fonte aggiorna ciò che esisteva già e non duplica nulla.

Così, passare una fonte da HTML a browser headless, o da diretta a Bright Data, significa cambiarne la configurazione. Il resto della pipeline non se ne accorge.

Checklist per l’inventario di una fonte

Prima di scrivere codice, per ogni sito:

  • C’è un’API pubblica, un portale open data o /wp-json?
  • Quali richieste Fetch/XHR fa la pagina al caricamento?
  • Ha un RSS? Che data riporta?
  • Il contenuto è nell’HTML del server o lo disegna JavaScript?
  • Quali campi sono solo nella scheda di dettaglio?
  • Quali formati di data usa?
  • Risponde 403 a un client HTTP? E a Playwright?
  • Cosa dicono il suo robots.txt e i suoi termini d’uso?

Se hai una lista di fonti e vuoi sapere quale strategia serve a ciascuna, nel nostro servizio di web scraping partiamo proprio da quella scheda.

Non perderti nulla

JM

Javier Manzano

CEO & Co-founder in Soamee

Appassionato di tecnologia e sviluppo software. Condividendo conoscenze e esperienze per aiutare altri sviluppatori a crescere.

Ti è piaciuto questo articolo?

Se hai bisogno di aiuto con il tuo progetto di sviluppo, siamo qui per te.

Web scraping: API, RSS, HTML o browser headless

Raccontaci la tua sfida. Ti proponiamo la soluzione.

Senza impegno. In meno di 24 ore riceverai una proposta con ambito, tempistiche e budget. Nessuna clausola nascosta.

Prenota una call gratuita →