Mozilla's Readability è la versione JavaScript standalone dell'extractor alla base di Firefox's Reader View. Pubblicato come pacchetto Apache-2.0 @mozilla/readability, prende il contenuto degli articoli da un DOM già vivo. In Node, quindi, serve un’implementazione DOM come jsdom. Non scarica le pagine, non esegue JavaScript e non fa estrazione di schemi.
La versione testata è la latest su npm, 0.6.0, pubblicata il 3 marzo 2025, con 11.361 stelle sul repository al controllo del 27 luglio 2026 (ultimo push il 9 luglio 2026, quindi main è molto avanti rispetto al pacchetto pubblicato). L'ho eseguita su jsdom 29.1.1 e Node v22.22.3, su macOS arm64, contro 22 fixture HTML etichettate create apposta per il test, e tutte le misurazioni qui riportate derivano da quel setup. Sul lato pratico è lo strumento meno impegnativo di questa categoria: due minuti per l'installazione, nessun binario, nessun browser in cache, output identico a ogni rerun. Ciò che la rende interessante non è tanto usarla — è che i fallimenti sono prevedibili da poche costanti nel sorgente, e una di queste pesa più di quanto lascino intendere i documenti.
Su 22 fixture sintetiche controllate, Readability ha recuperato tutti e 74 i blocchi articolo etichettati. Questo risultato circoscritto non vuol dire che non perda mai testo dell'articolo: il benchmark pubblico su pagine reali riporta un recall di 0,982, e qui non si sono riprodotte le forme di errore note. Il fallimento più evidente nelle fixture è stato l'aggiunta di contenuto dei fratelli adiacenti, consentita da un gate sulla link density a livello sorgente fissato a 0,25. Quanto sia grave, a livello percepito, cambia parecchio in base al banco di prova.
Cos'è davvero readability.js, e tre cose che non è
Readability è un passaggio di scoring basato su regole su un DOM. Scorre gli elementi candidati, assegna a ciascuno un content score, propaga questi punteggi agli antenati, sceglie il sottoalbero con il punteggio più alto e poi esegue passaggi di pulizia per togliere ciò che sembra contorno della pagina. Questa è tutta la strategia di estrazione: nessun modello, nessun training data, nessuna regola per sito. È per questo che funziona su una pagina che non ha mai visto — ed è per questo che i suoi fallimenti sono prevedibili dal sorgente, che è la parte più interessante.
Tre cose che non è, e che spesso confondono le persone:
- Non è un fetcher. Prende un
document, non un URL. Fetching, retry, anti-bot e header sono problemi vostri. - Non è un renderer. Nessuna esecuzione JavaScript. Vede solo ciò che c'è nel DOM che gli fornite.
- Non è un extractor strutturato. Restituisce
title,byline,excerpt,content(HTML),textContent,length,siteName. Niente schema, niente righe tipizzate, niente{name, price}.
Quattro costanti fanno la maggior parte del lavoro

Leggere Readability.js in node_modules dice più sul comportamento di qualsiasi pagina di documentazione. Quattro meccanismi spiegano gran parte di ciò che fa la libreria:
- Content score per paragrafo valutato:
1 + (commaCount + 1) + min(floor(len / 100), 3). I paragrafi sotto i 25 caratteri non vengono contati affatto. I punteggi si propagano agli antenati con divisori: il parent prende il punteggio pieno, il grandparent metà, e gli antenati più profondilevel · 3. DEFAULT_CHAR_THRESHOLD = 500— la lunghezza minima dell'articolo per un parsing “riuscito”. Sotto questa soglia, il parser rilancia l'estrazione con meno passaggi di pulizia.- La regex
unlikelyCandidates— intercetta sottostringhe di classi e id comecomment,footer,menu,related,sidebar,social,sponsor. I nodi corrispondenti vengono eliminati prima dello scoring. - Il gate di append dei fratelli in
grabArticle— dopo la scelta del candidato principale, vengono considerati i suoi siblings per l'inclusione. Un fratello viene aggiunto se il suo punteggio supera la soglia, oppurenodeLength > 80 && linkDensity < 0.25, oppurenodeLength < 80 && nodeLength > 0 && linkDensity === 0 && contiene un punto.
La link density è Σ(linkText.length · coef) / textLength, con coef = 0.3 per gli href # nudi e 1 in caso contrario. Quel gate spiega le fughe di sibling misurate qui; non è l'intero algoritmo di estrazione.
Setup, e la dipendenza che non compare nel pitch
npm install @mozilla/readability jsdom e sei a posto. Due minuti, nessun binario, nessun download post-install, nessun browser in cache. Sul fronte dell'installazione questo strumento è molto vicino all'ideale.
Ma “zero dipendenze” è una frase che riguarda l'algoritmo, non il runtime. Readability lavora su un document vivo e, in Node, questo significa che l'implementazione DOM la devi fornire tu — qui jsdom, nella versione 29.1.1. jsdom non è leggero e, per la maggior parte delle pipeline, è il costo dominante nel ciclo, non l'estrazione. Va messo in conto.
Un'altra trappola che mi è costata un rerun: Readability.parse() muta il DOM che riceve. Se parsate lo stesso documento jsdom due volte, la seconda chiamata vede un documento che la prima ha già smontato. Nel mio harness, ogni parsing costruisce un nuovo jsdom. Se state ciclizzando su pagine e riusate un oggetto document per risparmiare tempo, quello è il bug che state per aprire.
Come l'ho testata
Non l'ho puntata su siti di news reali. Le pagine live vi danno un punteggio senza dirvi perché — e con un'euristica, il “perché” è tutto il valore. Ho quindi generato 22 fixture HTML con 91 blocchi etichettati (74 articolo, 17 boilerplate), dove ogni parola in un blocco è prefissata con la stringa sentinel univoca di quel blocco. I vocabolari tra blocchi sono disgiunti, quindi un token estratto mappa a un solo blocco, e “recuperato” o “perso” è un test di appartenenza esatto, non un match sfumato.
La fase di estrazione e quella di scoring sono volutamente separate. Il runner Node emette solo il testo grezzo estratto, i booleani di isProbablyReaderable e le link density misurate. Tutta la precision e il recall vengono calcolati dopo, da quello stesso testo grezzo e dalle etichette, tramite uno script separato. Nessuna costante di metrica è scritta a mano nell'harness, ed è l'unico modo in cui mi fido dei miei numeri.
Poi ho passato i medesimi byte a trafilatura 2.1.0, per un confronto sullo stesso banco di prova. Ogni fixture è stata parsata tre volte; tutte e 22 hanno restituito testo byte-identical a ogni run.
Potete ispezionare ogni fase invece di fidarvi solo della tabella riassuntiva. tests/build_fixtures.mjs crea l'HTML annotato e la ground truth; tests/run_readability.mjs registra l'output dell'estrazione e del predictor; e tests/metrics.py assegna il punteggio a quei record a posteriori. L'output grezzo di Readability, le metriche calcolate e il confronto sugli stessi input sono conservati in artifacts/raw/. Questa separazione conta quando un risultato sembra sospetto: potete capire se il parser ha restituito testo inatteso, se il set di etichette era sbagliato o se il codice di scoring ha classificato male. La riproduzione su questo pacchetto verifica le affermazioni fatte qui, ma resta un controllo dell'harness — non una prova che il mix di pagine reali di una produzione abbia la stessa distribuzione di errori.
Il limite di scope è reale e non negoziabile: queste sono pagine sintetiche controllate, non un corpus reale. Le cifre autorevoli sulle pagine reali arrivano dal pubblico article-extraction-benchmark, che valuta readability_js 0.6.0 — la stessa versione testata qui — con word-F1 0,947 ± 0,005 (precision 0,914 ± 0,008, recall 0,982 ± 0,003) su circa 181 pagine reali. Lo cito; non l'ho riprodotto.
Qui vengono usate le righe del benchmark sulla release corrente; le righe storiche superate sono state escluse. Le fixture controllate aggiungono una scomposizione per blocco che mostra quale forma di contenuto attiva quale regola, invece di sostituire il corpus pubblico di pagine reali.
Il recall è stato perfetto nel pacchetto di fixture sintetiche
74 su 74. Su tutte le 22 fixture, Readability non ha perso neppure un blocco articolo etichettato — e sulle undici fixture sintetiche pulite che mescolavano articolo e boilerplate, il token recall micro-averaged è risultato 1.000. Nessuna frase dell'articolo è mancata.
A questo però si associano due cautele:
Si tratta di pagine sintetiche pulite, a colonna singola. Gli articoli reali hanno nesting più profondi, infilano annunci nel corpo e a volte perdono il paragrafo iniziale per un artefatto di scoring — quella classe di errore è riportata nel tracker (#437, #901, e le perdite di contenuto prima di una tabella in #922). Le mie fixture non hanno attivato nessuno di questi casi, quindi non sto dicendo che siano risolti — sto dicendo che il mio test non li ha raggiunti. Su pagine reali il recall del benchmark per questa versione è 0,982, non 1,000.
Resta però la direzione del risultato, che è la parte utile. Il problema di Readability non è che butta via il vostro articolo. È ciò che si porta dietro insieme all'articolo.
Il numero della precision, e perché servono tre etichette

Un numero è facile da citare e difficile da difendere. Sulle undici fixture miste, Readability ha tenuto 5 dei 17 blocchi boilerplate — un leak rate di 0,294.
Non è un leak rate reale del mondo. Tre setup diversi misurano tre cose diverse, e solo uno descrive pagine normali:
| Cosa sta misurando il numero | Risultato |
|---|---|
| Set di fixture pesato in modo avversariale — 6 delle 11 pagine miste sono state costruite apposta per battere il sibling gate | 5 di 17 blocchi boilerplate mantenuti (0,294) |
L'unica pagina realistica — corpo <article> circondato da nav, banner pubblicitario, sidebar, commenti, footer, più un promo con classe neutra | 5 di 6 blocchi di contorno rimossi; 1 mantenuto |
| ~181 pagine reali, benchmark pubblico (non la mia esecuzione) | precision 0,914, recall 0,982, word-F1 0,947 — readability_js 0.6.0 |
Leggete la prima riga come uno stress test, non come una previsione. Readability non perde il 29% del boilerplate nel mondo reale. Sulla pagina realistica, tutto ciò che portava una classe riconosciuta dalla regex unlikelyCandidates — nav-menu, ad-banner, sidebar, comments, site-footer — è stato rimosso in modo pulito, tutti e cinque. L'unico sopravvissuto era il blocco che ho progettato per evitare quella regex.
Il gate 0,25: dove la rimozione del boilerplate si ferma
La regola di append dei sibling è documentata nel sorgente. Per quanto sono riuscito a vedere, nessuno aveva misurato con precisione dove scatta il cambio. Ho quindi costruito un gradiente: un <p class="teaser-block"> con classe neutra fuori dall'<article>, un articolo di quattro paragrafi senza ambiguità, destinato a vincere come candidato principale, e come unica variabile la lunghezza e la link density del promo. Le densità sono calcolate con la formula di Readability, misurate a runtime e non ipotizzate:
| Blocco promo | Lunghezza testo interno | Oltre 80 caratteri | Link density misurata | Esito |
|---|---|---|---|---|
| Nessun link | 126 | sì | 0.000 | tenuto |
| Un link breve | 126 | sì | 0.143 | tenuto |
| Un link più lungo | 126 | sì | 0.278 | rimosso |
| Metà del testo linkato | 126 | sì | 0.476 | rimosso |
| Una sola frase, termina con un punto | 60 | no | 0.000 | tenuto |
| Stesso testo, senza punto | 59 | no | 0.000 | rimosso |
La condizione nel sorgente usa una soglia 0,25; i campioni misurati l'hanno oltrepassata da entrambi i lati, con 0,143 mantenuto e 0,278 rimosso. Un ramo separato ha mantenuto una frase da 60 caratteri che finisce con un punto e ha rimosso la versione da 59 caratteri senza punto. Il recall dell'articolo è rimasto 4/4 in ciascun ramo, quindi questi campioni isolano un effetto di precision.
Fuori da un harness di test, quel gate significa in sostanza: prosa lunga, con pochi link e neutra, accanto all'articolo, è articolo. E questo descrive un sacco di cose che articolo non sono — un box “Letture correlate” scritto come un paragrafo, un invito alla newsletter, una nota dell'editor, un teaser sponsorizzato che il team marketing ha scritto in frasi complete con il link rimosso per ragioni di tracking.
In un indice RAG, un paragrafo promo con pochi link può diventare un chunk estratto e creare il rischio che retrieval o generazione lo trattino come contenuto dell'articolo. La regola di sorgente rende plausibile quel failure mode; questa review non ha eseguito una valutazione end-to-end su retrieval o citazione del modello.
Per un filtro hard specifico del sito, prefiltrate i contenitori DOM sorgente noti, conservate l'ascendenza del nodo sorgente per il confronto prima della serializzazione, oppure applicate dopo un filtraggio testuale validato con attenzione. L'HTML restituito da solo potrebbe non preservare più se un nodo si trovava originariamente fuori dal contenitore principale. Il sibling gate non è regolabile tramite le opzioni pubbliche.
Tre assunzioni che le fixture hanno smentito
Le fixture hanno smentito tre assunzioni: che charThreshold rifiuti gli articoli brevi, che siano necessari i tag semantici e che i contenuti brevi non in prosa vengano eliminati. Le prove qui sotto sono la parte rilevante; non serve alcuna dichiarazione di preregistrazione.
charThreshold = 500 non è un dirupo
La lettura popolare è che un articolo sotto i 500 caratteri ritorni null. Non è così. Ho variato la lunghezza del body da 120 a 1500 caratteri con valori di charThreshold pari a 200, 500 e 1000:
| Lunghezza del body | Parsing riuscito a ogni soglia | Lunghezza estratta |
|---|---|---|
| 120 | sì | 161 |
| 300 | sì | 342 |
| 460 | sì | 509 |
| 520 | sì | 569 |
| 800 | sì | 841 |
| 1500 | sì | 1555 |
Piatto. Lunghezza estratta identica su tutte e tre le impostazioni della soglia, a ogni dimensione del body. La soglia non blocca il valore di ritorno — decide se rilanciare il grab con i flag di cleanup rimossi, e su una pagina pulita non c'è nulla da rimuovere, quindi il setaccio restituisce lo stesso contenuto in entrambi i casi. Il vero confine del null è “nessun testo estraibile affatto”.
Ed è qui che emerge il fallimento reale, e risulta più scomodo di un falso null. Ho passato una pagina quasi vuota — una barra di navigazione e un blurb di quattro parole. Ha restituito con successo, e l'“articolo” restituito includeva la nav. Senza un vero articolo, Readability vi consegna il boilerplate etichettato come articolo. Se fate crawling su larga scala e trattate un risultato non nullo come “questa pagina aveva contenuto”, quell'assunzione è sbagliata.
I tag semantici non fanno il lavoro
Mi aspettavo un calo del recall togliendo l'impalcatura. Stesso testo dell'articolo, due skin: una con <main><article><h1> e classi descrittive, una con <div class="x1"> e paragrafi resi come semplici <div>. Risultato: 4 blocchi articolo su 4 recuperati in entrambi i casi, zero boilerplate leaked in entrambi. Quando l'articolo è chiaramente il blocco di testo più denso della pagina, lo scoring basato su lunghezza e virgole lo trova senza alcun aiuto semantico. “Readability ha bisogno dei tag <article>” è folklore.
Il limite onesto di questa affermazione: la mia pagina aveva un solo blocco di contenuto ovvio. Dove i semantici probabilmente farebbero la differenza è una pagina con due sottoalberi densi in competizione, e quel tie-break non l'ho testato.
Il contenuto non in prosa sopravvive intatto
La regola “i paragrafi sotto i 25 caratteri non vengono contati” mi faceva aspettare perdite su tabelle e caption. Ancora una volta sbagliato — quella regola influenza lo scoring dei candidati, non la retention. Una volta che il contenitore vince, tutto ciò che c'è dentro lo segue:
| Tipo di contenuto dentro l'articolo | Readability | trafilatura |
|---|---|---|
| Paragrafi di prosa (×2) | mantenuti | mantenuti |
| Celle di tabella dati (×2) | mantenute | mantenute |
Blocco di codice <pre> | mantenuto | mantenuto |
Riga singola sotto i 25 caratteri in <p> (×2) | mantenuta | mantenuta |
<figcaption> | mantenuto | eliminato |
| Totale | 8/8 | 7/8 |
È un asse in cui il cleaner più aggressivo perde. Se le vostre pagine sono documentazione, tutorial o qualsiasi cosa con blocchi di codice e figure con caption, il comportamento di Readability — tenere l'intero sottoalbero vincente — è un vantaggio.
isProbablyReaderable dice no quando parse() dice sì

Il README suggerisce di chiamare isProbablyReaderable(doc) come controllo preliminare economico prima di impegnarsi in un parsing completo. Nei miei test quel gate ha respinto tre forme di pagina distinte che parse() poi ha gestito senza problemi:
| Forma della pagina | Verdetto del predictor | parse() | Quale leva la risolve |
|---|---|---|---|
Contenuto solo in elementi <li> | false | succeeds | nessuna — false a ogni minScore 1–80 e a ogni minContentLength 40–200 |
| Dieci paragrafi, ciascuno sotto 140 caratteri | false | succeeds | minContentLength ≤ 100 (minScore non cambia nulla) |
| Un paragrafo da 408 caratteri | false | succeeds | minScore ≤ 10 (il punteggio è ≈16,4) |
| Articolo normale (controllo) | true | succeeds | — |
I tre fallimenti hanno tre cause diverse, e solo due sono regolabili. Il caso <li> è strutturale: il predictor assegna punteggio solo ai nodi p, pre e article (più i parent div > br), quindi una pagina il cui contenuto vive in list item non corrisponde a nulla, totalizza zero e nessuna regolazione della soglia la recupera — una forma già riportata nell'issue #662. Il caso dei molti brevi è un gate minContentLength che salta ogni paragrafo prima dello scoring, quindi dieci paragrafi sostanziali sommano a zero; abbassare quel valore lo risolve, mentre intervenire su minScore no. Il caso del singolo paragrafo è aritmetico: il punteggio è sqrt(408 − 140) ≈ 16,4, sotto il minScore predefinito di 20 — un paragrafo isolato deve arrivare a 140 + 20² = 540 caratteri per superare da solo la soglia.
Il README avverte che il predictor produce falsi negativi. Quello che aggiungerei è la regola pratica: non usarlo come unico gatekeeper. Se una pagina conta, parsatela e controllate la lunghezza del risultato. Il parsing non è così costoso rispetto alla build jsdom che avete già pagato.
Stessi byte, due extractor
Eseguire trafilatura 2.1.0 sugli stessi identici fixture dà una lettura più pulita che allineare due numeri misurati su due banchi di prova diversi, perché l'input è identico byte per byte:
| Misura (11 fixture miste) | @mozilla/readability | trafilatura |
|---|---|---|
| Recall dei blocchi articolo | 1.000 | 1.000 |
| Blocchi boilerplate mantenuti | 5/17 (0,294) | 1/17 (0,059) |
| Token F1 (micro) | 0,948 | 0,969 |
| Recall del contenuto non in prosa | 8/8 | 7/8 |
| Articolo molto breve (120 caratteri), token F1 | 0,800 | 0,571 |
Nessuno dei due domina queste fixture. Trafilatura ha mantenuto meno blocchi dei fratelli, mentre Readability ha conservato più contenuto breve e non prosa. La precision assoluta di entrambi gli strumenti è abbassata dal testo di heading non etichettato, quindi il conteggio dei leak a livello di blocco è il segnale diretto più pulito. Il benchmark pubblico su pagine reali ordina i loro word-F1 in modo simile, ma i corpus e le metriche sono diversi e questa non è una validazione cross-banco.
Robustezza, in breve: ho eseguito una gemella volutamente malformata della pagina canonica (<p> non chiuso, <b>/<i> annidati male, un </div> fuori posto) e il recall è stato 3/3 con zero leak, in linea con la versione ben formata. Il merito va al tree builder HTML5 di jsdom, che ripara il caos prima ancora che Readability lo veda. Nessuna fixture ha mandato in crash il parser.
Pro e contro
Pro
- Il recall sull'articolo è il suo punto forte: 74/74 blocchi etichettati recuperati su 22 fixture sintetiche, token recall 1.000 sul set misto.
- Il contorno della pagina classificato via regex viene rimosso in modo affidabile — nav, banner pubblicitari, sidebar, commenti e footer spariscono tutti sulla pagina realistica (5 su 6).
- Il contenuto non in prosa è mantenuto integralmente: tabelle, codice
<pre>, caption di figure e righe sotto i 25 caratteri sono sopravvissuti tutti (8/8), mentre trafilatura ha eliminato una caption. - Nessuna dipendenza dal markup semantico — un articolo normalizzato in
<div>ha ottenuto lo stesso punteggio della versione con<article>/<main>. - Gli articoli brevi non vengono rifiutati per errore: contenuti puliti recuperati fino a 120 caratteri, identici con
charThreshold200/500/1000. - Totalmente deterministica: tutte e 22 le fixture hanno restituito testo identico in tre esecuzioni.
- Installazione in due minuti, licenza Apache-2.0, e la versione su npm è quella testata (0.6.0), quindi nulla qui è superato.
Contro
- Il sibling-append gate è aggirabile: prosa promozionale lunga, con pochi link e classi neutre, è indistinguibile dal testo dell'articolo e viene appesa con
linkDensity < 0,25. - Su pagine povere di contenuto restituisce il boilerplate come articolo invece di
null— la fixture quasi vuota è tornata con la nav bar come corpo. isProbablyReaderableproduce falsi negativi su tre forme di pagina diverse, e una di queste non si risolve con alcun tuning.- Richiede un DOM completo a runtime — la narrativa “nessuna dipendenza” nasconde il costo di jsdom, che domina il ciclo.
parse()muta il documento in input, quindi bisogna ricostruire il DOM per ogni pagina.- Nessun fetching, nessun rendering JavaScript, nessun output strutturato. È una fase della pipeline, non l'intera pipeline.
- I miss su pagine reali riportati nel tracker (perdita del primo paragrafo e contenuto prima di una tabella) non si sono riprodotti sulle mie fixture, quindi non posso dirvi se siano rari o se le mie pagine non li abbiano semplicemente mai raggiunti.
Chi dovrebbe usarlo, e chi dovrebbe evitarlo
Scegliete Readability se avete già l'HTML in mano e volete estrarre l'articolo in puro JavaScript, dentro un servizio Node dove aggiungere una dipendenza Python sarebbe scomodo. (Non ho raccolto tempi come distribuzione corretta, quindi non faccio alcuna promessa di velocità oltre a “la build jsdom domina il ciclo, non l'estrazione”.) Funzioni da reader mode, archiviazione offline degli articoli, newsletter via email, pulsanti “vista pulita”, estensioni browser, pipeline di documentazione con blocchi di codice e caption — questo è il suo terreno, e i numeri di recall dicono che è un buon terreno. Il suo comportamento è anche leggibile direttamente dal sorgente, e vale più di quanto sembri quando devi spiegare a un collega perché è passato proprio quel blocco.
Evitatelo se la precision nella rimozione del boilerplate è la metrica con cui venite giudicati, e soprattutto se state alimentando un indice per LLM in cui un promo stray diventa un chunk recuperabile. Evitatelo se le vostre pagine renderizzano il contenuto lato client, perché legge il DOM che gli passate e non esegue JavaScript. Evitatelo se vi serve {title, price, sku} invece di prosa — nessuna configurazione trasforma un extractor di contenuti in uno guidato da schema. E se state processando pagine in cui “questa pagina aveva davvero un articolo?” è una domanda concreta, non fidatevi di un ritorno non nullo come risposta.
Alternative, e dove si colloca lo stack Thunderbit
Nulla di tutto questo è una critica a una libreria Apache-2.0 gratuita mantenuta da Mozilla — Readability è infrastruttura, è dentro Firefox da anni, e per l'estrazione in reader mode è l'implementazione di riferimento per un motivo. Se volete un quadro più ampio, tengo un confronto aggiornato nel riassunto degli scraper open source e una survey più ampia in i migliori strumenti di web scraping.
Per gli stessi fixture attraverso tutti e sei gli extractor, guardate il confronto di estrazione tra sei librerie.
Nota dell'autore: Thunderbit è la nostra opzione gestita per rendering ed estrazione a partire da URL. Non è stata eseguita su queste fixture, quindi non implica alcuna equivalenza di qualità. Il confine rilevante è se avete già un DOM e volete l'estrazione locale dell'articolo, oppure se volete fetching/rendering e output strutturato come servizio. L'auto-hosting evita una fee d'uso del vendor ma comporta comunque costi di infrastruttura e manutenzione.
Il trade-off onesto: Readability è gratuito, trasparente e vostro da eseguire — potete leggere il gate esatto che ha deciso l'output, cosa che un'API gestita non vi offre. Uno stack gestito costa e nasconde il meccanismo, ma copre le fasi di fetch-render-structure che altrimenti dovreste assemblare voi. Se vi interessa il lato assistito dall'AI di quello spettro, ho scritto di scraping di qualsiasi sito web con AI e di AI crawler altrove. Scegliete in base alle fasi che volete davvero possedere.
Prova Thunderbit per l'estrazione di dati dal web
Verdetto
Readability è una buona opzione quando avete già un DOM, usate JavaScript/Node e preferite un po' di contenuto dei fratelli in più piuttosto che omissioni aggressive. In questo pacchetto di fixture ha recuperato tutti i 74 blocchi articolo etichettati e ha mantenuto tabelle, codice e caption. Quel risultato è però limitato a pagine sintetiche a colonna singola; il recall pubblico su pagine reali è 0,982, i miss noti vicini a titoli o tabelle non sono stati riprodotti, e le pagine povere di contenuto possono restituire boilerplate come articolo.
Bisogna però pesare bene il difetto. La sua principale superficie di errore in queste fixture è la precision, e si trova in una riga ben specifica e documentata del sorgente: un sibling oltre gli 80 caratteri con link density sotto 0,25 viene aggiunto all'articolo, che ne faccia parte o meno. L'ho visto passare da 0,143 a 0,278 su testo identico. Il benchmark su pagine reali riporta precision 0,914 e recall 0,982. Se state passando il testo estratto a un indice da cui un modello citerà poi i contenuti, ispezionate sia il boilerplate mantenuto sia il corpo omesso, invece di assumere che una delle due classi di errore non esista.
Prova Thunderbit per l'estrazione di dati dal web Get Started Free
FAQ
Mozilla Readability rimuove tutto il boilerplate?
No, e il numero dipende moltissimo da ciò che state misurando. Sul benchmark pubblico di pagine reali, readability_js 0.6.0 ottiene precision 0,914 — quindi circa l'8,6% di ciò che restituisce non è body content. Su una mia pagina di test realistica ha rimosso 5 dei 6 blocchi di contorno (nav, banner, sidebar, commenti e footer spariti), mantenendo solo un paragrafo promo con classe neutra. Su un set di fixture pesato apposta con blocchi progettati per battere l'euristica, ha mantenuto 5 su 17 — quest'ultimo dato è uno stress test, non una misura reale.
Mi serve jsdom per usare readability.js in Node?
Sì, o un'altra implementazione DOM. Readability è JavaScript puro, ma lavora su un oggetto document vivo, quindi in Node il DOM lo fornite voi — nel mio setup jsdom 29.1.1. La descrizione “senza dipendenze” si riferisce all'algoritmo, non al runtime. Notate anche che parse() muta il documento che riceve, quindi create un DOM fresco per ogni pagina invece di riutilizzarne uno.
Cosa fa davvero l'opzione charThreshold?
Non quello che immagina la maggior parte delle persone. Non fa sì che gli articoli brevi ritornino null — ho recuperato contenuti puliti fino a 120 caratteri, con lunghezza estratta identica per charThreshold 200, 500 e 1000. La soglia controlla se il parser rilancia l'estrazione con i flag di cleanup rimossi; su una pagina pulita non c'è nulla da rimuovere, quindi l'output resta uguale in entrambi i casi. Il vero caso null è una pagina senza alcun testo estraibile, e persino una pagina con solo nav è tornata non nulla, restituendo la nav come articolo.
Dovrei chiamare isProbablyReaderable prima di parse()?
Usatelo come indizio, non come gate. Ha restituito false su tre forme di pagina in cui parse() ha poi avuto successo: contenuto dentro elementi <li>, dieci paragrafi sotto i 140 caratteri e un singolo paragrafo da 408 caratteri. Il caso <li> non si può correggere con il tuning, perché il predictor valuta solo i nodi p, pre e article; il caso dei molti brevi richiede un minContentLength più basso; il caso del paragrafo lungo richiede un minScore più basso, perché un singolo paragrafo deve arrivare a 540 caratteri per superare da solo la soglia predefinita. Se una pagina conta, parsatela e verificate il risultato.
Readability o trafilatura per l'estrazione degli articoli?
Sugli stessi byte delle fixture, trafilatura ha mantenuto meno boilerplate (1/17 blocchi contro 5/17), mentre Readability ha recuperato più contenuto breve e ha conservato un <figcaption> che trafilatura ha eliminato. Scegliete in base alla tolleranza agli errori e al runtime. Il benchmark pubblico è un contesto separato, non una validazione di questo risultato sulle fixture.


