Mozilla Readability Review: Perfecte vindbaarheid van artikelonderdelen, sibling-lekkage en false negatives van de predictor

Laatst bijgewerkt op August 17, 2026
Mozilla Readability Review: Perfecte vindbaarheid van artikelonderdelen, sibling-lekkage en false negatives van de predictor
AI-samenvatting
Mozilla's Readability is de zelfstandige JavaScript-port van de extractor achter Firefox's Reader View. Uitgebracht als het Apache-2.0-pakket @mozilla/readability, selecteert het artikelinhoud uit een live DOM. Onder Node heeft het daarom een DOM-implementatie nodig, zoals jsdom. Het haalt geen pagina's op, voert hun JavaScript niet uit en doet geen schema-extractie. De versie die ik heb getest is npm latest op 0.6.0, gepubliceerd op 3 maart 2025, met 11.361 sterren op de repo toen ik op 27 juli 2026 keek (laatst gepusht op 9 juli 2026, dus main ligt al ver voor op het gepubliceerde pakket).

Mozilla's Readability is de zelfstandige JavaScript-port van de extractor achter Firefox's Reader View. Uitgebracht als het Apache-2.0-pakket @mozilla/readability, selecteert het artikelinhoud uit een live DOM. Onder Node heeft het daarom een DOM-implementatie nodig, zoals jsdom. Het haalt geen pagina's op, voert hun JavaScript niet uit en doet geen schema-extractie.

De versie die ik heb getest is npm latest op 0.6.0, gepubliceerd op 3 maart 2025, met 11.361 sterren op de repo toen ik op 27 juli 2026 keek (laatst gepusht op 9 juli 2026, dus main ligt al ver voor op het gepubliceerde pakket). Ik heb het gedraaid met jsdom 29.1.1 op Node v22.22.3, macOS arm64, tegen 22 gelabelde HTML-fixtures die hiervoor waren gebouwd, en alle metingen hier komen uit die opzet. In de praktijk is dit de minst veeleisende tool in deze categorie: twee minuten om te installeren, geen binaire bestanden, geen browser in een cache, en bij elke herhaling identieke output. Wat het interessant maakt, is niet het gebruik ervan — het is dat de fouten voorspelbaar zijn vanuit een handvol constanten in de broncode, en één van die constanten bepaalt meer dan de documentatie doet vermoeden.

Over 22 gecontroleerde synthetische fixtures herstelde Readability alle 74 gelabelde artikelblokken. Dat begrensde resultaat betekent niet dat het nooit artikelttekst verliest: op de publieke benchmark met echte pagina's ligt de recall op 0,982, en bekende foutpatronen kwamen hier niet naar voren. De meest zichtbare fixture-fout was extra sibling-inhoud die werd toegelaten door een broncode-gate voor linkdichtheid op 0,25. Hoe ernstig dat lijkt, hangt af van de testopzet.

Wat readability.js eigenlijk is, en drie dingen die het niet is

Readability is een op regels gebaseerde scoringsronde over een DOM. Het loopt door kandidaat-elementen, kent elk element een contentscore toe, schuift die scores door naar de voorouders, kiest de subtree met de hoogste score en doet daarna opschoonrondes om weg te halen wat op paginabediening lijkt. Dat is de hele strategie voor het extraheren van artikelen — geen model, geen trainingsdata, geen regels per site. Precies daarom werkt het op een pagina die het nog nooit heeft gezien — en daarom zijn de fouten voorspelbaar uit de broncode, wat juist het interessante deel is.

Drie dingen is het niet, en precies die drie zorgen vaak voor verwarring:

  • Geen fetcher. Het neemt een document, geen URL. Ophalen, retries, anti-bot en headers zijn jouw verantwoordelijkheid.
  • Geen renderer. JavaScript wordt niet uitgevoerd. Wat er in de DOM zit die je aanlevert, is alles wat het ziet.
  • Geen gestructureerde extractor. Je krijgt title, byline, excerpt, content (HTML), textContent, length, siteName. Geen schema, geen getypeerde rijen, geen {name, price}.

Vier constanten doen het meeste werk

System diagram: Four constants do most of the work

Als je Readability.js in node_modules leest, begrijp je het gedrag beter dan uit welke documentatiepagina dan ook. Vier mechanismen verklaren het grootste deel van wat de library doet:

  • Contentscore per gescoorde alinea: 1 + (commaCount + 1) + min(floor(len / 100), 3). Alinea's onder 25 tekens tellen helemaal niet mee. Scores lopen door naar voorouders met delers — de ouder krijgt de volle score, de grootouder de helft, en diepere voorouders level · 3.
  • DEFAULT_CHAR_THRESHOLD = 500 — de minimale artikellengte voor een "succesvolle" parse. Daaronder draait een sieve voor het verwijderen van flags nog eens opnieuw met minder opschoonpasses.
  • De unlikelyCandidates-regex — matcht class- en id-substrings zoals comment, footer, menu, related, sidebar, social, sponsor. Gematchte nodes worden al vóór de scoring weggegooid.
  • De sibling-append gate in grabArticle — nadat de topkandidaat is gekozen, worden zijn siblings overwogen voor opname. Een sibling gaat mee als zijn eigen score de drempel haalt, of nodeLength > 80 && linkDensity < 0.25, of nodeLength < 80 && nodeLength > 0 && linkDensity === 0 && het een punt bevat.

Linkdichtheid is Σ(linkText.length · coef) / textLength, met coef = 0.3 voor kale #-href's en anders 1. Die gate verklaart de sibling-lekkage die hier is gemeten; het is niet het volledige extractiealgoritme.

Opzet, en de afhankelijkheid die niet in de pitch staat

npm install @mozilla/readability jsdom en je kunt aan de slag. Twee minuten, geen binaries, geen download na installatie, geen browser die in je cache blijft hangen. Op de install-as is dit bijna ideaal.

Maar "zero dependencies" is een uitspraak over het algoritme, niet over de runtime. Readability werkt op een live document, en onder Node betekent dat dat jij een DOM-implementatie levert — hier jsdom, versie 29.1.1. jsdom is niet klein, en voor de meeste pipelines is het de dominante kostenpost in de loop, niet de extractie. Houd daar rekening mee.

Nog één valkuil die me een extra run kostte: Readability.parse() muteert de DOM die het krijgt. Parse hetzelfde jsdom-document twee keer en de tweede call ziet een document dat de eerste al uit elkaar heeft gehaald. Elke parse in mijn harness bouwt daarom een frisse jsdom op. Als je pagina's in een lus verwerkt en een document-object hergebruikt om tijd te besparen, dan is dat de bug die je straks gaat melden.

Hoe ik het testte

Ik heb dit niet tegen live nieuwssites aangezet. Live pagina's geven je wel een score, maar niet de reden waarom — en bij een heuristiek is juist dat "waarom" de echte waarde. Daarom heb ik 22 HTML-fixtures gegenereerd met 91 gelabelde blokken (74 artikel, 17 boilerplate), waarbij elk woord in een blok een uniek sentinel-voorvoegsel van dat blok krijgt. De woordenschat van blokken is onderling volledig gescheiden, dus een geëxtraheerd token verwijst exact naar één blok, en "hersteld" of "gelekt" is een exacte membership test in plaats van een vage match.

De extractiestap en de scoringstap zijn bewust gescheiden. De Node-runner geeft alleen ruwe geëxtraheerde tekst, de isProbablyReaderable-booleans en gemeten linkdichtheden terug. Alle precision en recall worden daarna uit die ruwe tekst berekend tegen de labels, door een apart script. In het harness wordt nergens een metriek-constante met de hand geschreven, en dat is de enige manier waarop ik mijn eigen cijfers vertrouw.

Daarna heb ik dezelfde bytes ook aan trafilatura 2.1.0 gevoed voor een vergelijking op dezelfde testopzet. Elke fixture is drie keer geparset; alle 22 leverden elke keer byte-identieke tekst op.

Je kunt elke stap inspecteren in plaats van de samenvattingstabel te geloven. tests/build_fixtures.mjs maakt de geannoteerde HTML en ground truth; tests/run_readability.mjs registreert extractie en predictor-output; en tests/metrics.py scoort die records achteraf. De ruwe Readability-output, berekende metrics en vergelijking met dezelfde input zijn bewaard in artifacts/raw/. Dat onderscheid is belangrijk wanneer een resultaat verdacht lijkt: dan kun je zien of de parser onverwachte tekst teruggaf, de labelset fout was, of de scoringscode het verkeerd classificeerde. Reproductie op deze set toetst de hier genoemde claims, maar blijft een harness-check — geen bewijs dat een echte paginamix in productie dezelfde foutverdeling heeft.

De scopebeperking is reëel en bepalend: dit zijn synthetische gecontroleerde pagina's, geen real-world corpus. De gezaghebbende cijfers voor echte pagina's komen uit de publieke article-extraction-benchmark, die readability_js 0.6.0 — exact de versie die hier is getest — scoort op word-F1 0,947 ± 0,005 (precision 0,914 ± 0,008, recall 0,982 ± 0,003) over ongeveer 181 echte pagina's. Dat citeer ik; dat heb ik niet gereproduceerd.

Hier worden benchmarkregels van de huidige release gebruikt; verouderde historische regels zijn weggelaten. De gecontroleerde fixtures voegen een decompositie per blok toe die laat zien welke contentvorm welke regel triggert, in plaats van het publieke corpus met echte pagina's te vervangen.

De recall was perfect in de synthetische fixture-set

74 van 74. Over alle 22 fixtures liet Readability geen enkel gelabeld artikelblok vallen — en op de elf schone synthetische fixtures die artikel en boilerplate combineerden, kwam de micro-gemiddelde token recall uit op 1,000. Geen enkele artikelzin verdween.

Daar horen twee kanttekeningen bij:

Dit zijn schone, enkelkoloms synthetische pagina's. Echte artikelen zijn dieper genest, mengen advertenties midden in de body, en verliezen soms hun openingsalinea aan een scoring-artifact — dat type gemis staat in de tracker (#437, #901, en content vóór een tabel in #922). Mijn fixtures triggerden geen van die gevallen, dus ik beweer niet dat ze zijn opgelost — ik zeg alleen dat mijn test ze niet bereikte. Op echte pagina's ligt de benchmark-recall voor deze versie op 0,982, niet op 1,000.

Toch is de richting van het resultaat het nuttigste deel. Het probleem van Readability is niet dat het je artikel weggooit. Het probleem is wat het er allemaal bij meeneemt.

Het precision-cijfer, en waarom het drie labels nodig heeft

Measured results chart: Three scopes behind the precision story

Eén cijfer is makkelijk te citeren en lastig te verdedigen. Op de elf gemengde fixtures hield Readability 5 van de 17 boilerplateblokken vast — een leak rate van 0,294.

Dat is geen echte leak rate. Drie verschillende opzetten meten drie verschillende dingen, en slechts één daarvan beschrijft gewone pagina's:

Wat het cijfer meetResultaat
Adversarially gewogen fixture-set — 6 van de 11 gemengde pagina's zijn bewust gebouwd om de sibling gate te misleiden5 van de 17 boilerplateblokken behouden (0,294)
De ene realistische pagina — body van een <article> omringd door nav, advertentiebanner, sidebar, comments, footer, plus één neutraal geclassificeerde promo5 van de 6 chrome-blokken verwijderd; 1 behouden
~181 echte pagina's, publieke benchmark (niet mijn run)precision 0,914, recall 0,982, word-F1 0,947 — readability_js 0.6.0

Lees de eerste rij als stress test, niet als voorspelling. Readability lekt in het wild geen 29% van de boilerplate. Op de realistische pagina werd alles met een class die de unlikelyCandidates-regex raakt — nav-menu, ad-banner, sidebar, comments, site-footer — netjes verwijderd, alle vijf. De enige overlever was het ene blok dat ik expres had gebouwd om die regex te ontwijken.

De 0,25-gate: waar het verwijderen van boilerplate stopt

De sibling-appendregel staat in de broncode gedocumenteerd. Voor zover ik kon vinden had niemand precies gemeten waar hij omslaat. Dus bouwde ik een gradient: één neutraal geclassificeerde <p class="teaser-block"> buiten de <article>, een doorslaggevende artikeltekst van vier alinea's die zeker als topkandidaat zou winnen, en niets dat veranderde behalve de lengte en linkdichtheid van de promo. De dichtheden zijn berekend met de eigen formule van Readability, runtime gemeten in plaats van aangenomen:

Promo-blokLengte binnen de tekstBoven 80 tekensGemeten linkdichtheidUitkomst
Geen links126ja0,000behouden
Eén korte link126ja0,143behouden
Eén langere link126ja0,278verwijderd
De helft van de tekst gelinkt126ja0,476verwijderd
Eén zin, eindigt op een punt60nee0,000behouden
Zelfde tekst, zonder punt59nee0,000verwijderd

De bronconditie gebruikt een drempel van 0,25; de gemeten samples omsloten die netjes, met 0,143 behouden en 0,278 verwijderd. Een aparte tak hield een zin van 60 tekens met punt vast en verwijderde een versie van 59 tekens zonder punt. Artikelrecall bleef in beide armen 4/4, dus deze samples isoleren puur het precision-effect.

Buiten een test-harness zegt die gate in feite: lange, neutrale tekst met weinig links naast het artikel is artikel. Dat beschrijft heel wat dingen die géén artikel zijn — een "Verder lezen"-tekst in alinea-vorm, een nieuwsbriefpitch, een redactionele noot, een gesponsorde teaser die een marketingteam in volledige zinnen heeft geschreven terwijl de link voor tracking weg is gelaten.

In een RAG-index kan zo'n promo-alinea met weinig links een geëxtraheerd chunk worden en ervoor zorgen dat retrieval of generatie het als artikelinhoud behandelt. De bronregel maakt dat faalpatroon plausibel; deze review heeft geen end-to-end retrieval- of model-quotation-evaluatie uitgevoerd.

Voor een sitespecifieke harde filter kun je bekende source-DOM-containers vooraf filteren, de ancestry van bronnodes bewaren voor vergelijking vóór serialisatie, of daarna zorgvuldig gevalideerde tekstpatroonfilters toepassen. Geretourneerde HTML alleen kan namelijk niet meer laten zien of een node oorspronkelijk buiten de primaire container zat. De sibling gate is via de publieke opties niet instelbaar.

Drie aannames die de fixtures tegenspraken

De fixtures spraken drie aannames tegen: dat charThreshold korte artikelen afwijst, dat semantische tags nodig zijn, en dat korte niet-prozatekst wordt weggegooid. Het bewijs hieronder is het relevante deel; een preregistratieclaim is niet nodig.

charThreshold = 500 is geen afgrond

Volgens de folklore zou een artikel onder 500 tekens null teruggeven. Dat doet het niet. Ik heb de bodylengte gesweept van 120 tot 1500 tekens tegen charThreshold-waarden van 200, 500 en 1000:

BodylengteParse slaagde bij elke thresholdGeëxtraheerde lengte
120ja161
300ja342
460ja509
520ja569
800ja841
1500ja1555

Vlak. Over alle drie threshold-instellingen exact dezelfde geëxtraheerde lengte, bij elke bodygrootte. De threshold bepaalt niet of er iets terugkomt — hij beslist of de grab nog eens opnieuw wordt uitgevoerd met opschoonflags verwijderd, en op een schone pagina is er niets weg te halen, dus komt dezelfde content terug. De echte null-grens is: "helemaal geen tekst die te extraheren valt."

En dáár ontstaat hier de echte fout, en die is vervelender dan een foutieve null. Ik gaf het een bijna lege pagina — een navigatiebalk en een tekstje van vier woorden. Het gaf succesvol resultaat terug, en het "artikel" dat terugkwam bevatte de nav. Zonder echt artikel levert Readability boilerplate terug als artikel. Als je op schaal crawlt en een niet-null resultaat ziet als "deze pagina had content", dan klopt die aanname niet.

Semantische tags doen het werk niet

Ik verwachtte dat recall zou dalen toen ik de scaffolding weghaalde. Zelfde artikeltekst, twee skins: één met <main><article><h1> en beschrijvende classnamen, één met <div class="x1"> en paragrafen als kale <div>'s. Resultaat: 4 van de 4 artikelblokken in beide gevallen hersteld, nul boilerplate gelekt in beide. Wanneer het artikel duidelijk het dichtste tekstblok op de pagina is, vindt de lengte-en-komma-scoringslogica het zonder semantische hulp. "Readability heeft <article>-tags nodig" is folklore.

De eerlijke grens van die claim: mijn pagina had één duidelijk contentblok. Waar semantiek waarschijnlijk wél verschil maakt, is een pagina met twee concurrerende dichte subtrees, en dat heb ik niet getest.

Niet-prozatekst blijft intact

De regel "alinea's onder 25 tekens tellen niet mee" deed me denken dat tabellen en captions verloren zouden gaan. Fout opnieuw — die regel beïnvloedt de scoring van kandidaten, niet het behouden ervan. Zodra de container wint, komt alles daarbinnen mee:

Contenttype binnen het artikelReadabilitytrafilatura
Prozaalinea's (×2)behoudenbehouden
Datatabelcellen (×2)behoudenbehouden
<pre>-codeblokbehoudenbehouden
Eénregelige <p> onder 25 tekens (×2)behoudenbehouden
<figcaption>behoudenverwijderd
Totaal8/87/8

Dat is een as waarop de zwaardere cleaner verliest. Als je pagina's documentatie, tutorials of iets met codeblokken en figuurcaptions zijn, is Readability's gedrag om de hele winnende subtree te behouden juist een pluspunt.

isProbablyReaderable zegt nee wanneer parse() ja zegt

System diagram: isProbablyReaderable says no when parse() says yes

De README raadt aan om isProbablyReaderable(doc) als goedkope voorcheck te gebruiken vóór je een volledige parse uitvoert. In mijn tests wees die gate drie verschillende paginavormen af die parse() daarna prima verwerkte:

PaginavormPredictor-oordeelparse()Welke knop het oplost
Inhoud alleen in <li>-elementenfalseslaagtgeen — fout bij elke minScore 1–80 en elke minContentLength 40–200
Tien alinea's, elk onder 140 tekensfalseslaagtminContentLength ≤ 100 (minScore doet niets)
Eén alinea van 408 tekensfalseslaagtminScore ≤ 10 (score is ≈16,4)
Normaal artikel (controle)trueslaagt—

De drie fouten hebben drie verschillende oorzaken, en slechts twee daarvan zijn instelbaar. Het <li>-geval is structureel: de predictor scoort alleen p-, pre- en article-nodes (plus div > br-ouders), dus een pagina waarvan de inhoud in list items leeft, matcht niets, scoort nul en geen enkele threshold-tuning redt dat — een vorm die al in issue #662 is gemeld. De vele-korte-paragrafen-case is een minContentLength-gate die elke alinea vóór scoring overslaat, waardoor tien behoorlijke alinea's optellen tot niets; een lagere waarde lost dat op, minScore aanpassen niet. De enkele-paragraaf-case is rekenkundig: de score is sqrt(408 − 140) ≈ 16,4, onder de standaard minScore van 20 — een losse alinea heeft dus 540 tekens nodig om alleen al de drempel te halen.

De README waarschuwt wel dat de predictor false negatives kan geven. Wat ik daaraan zou toevoegen is de praktische regel: gebruik hem niet als enige poortwachter. Als een pagina belangrijk is, parse hem en controleer de resultaatlengte. De parse is niet zo duur vergeleken met de jsdom-opbouw die je toch al hebt betaald.

Dezelfde bytes, twee extractors

trafilatura 2.1.0 loslaten op exact dezelfde fixtures geeft een duidelijker beeld dan twee cijfers naast elkaar zetten die op twee verschillende testopzetten zijn gemeten, omdat de input byte voor byte identiek is:

Metriek (11 gemengde fixtures)@mozilla/readabilitytrafilatura
Recall van artikelblokken1,0001,000
Boilerplateblokken behouden5/17 (0,294)1/17 (0,059)
Token F1 (micro)0,9480,969
Recall van niet-proza8/87/8
Zeer kort artikel (120 tekens), token F10,8000,571

Geen van beide domineert deze fixtures volledig. Trafilatura hield minder siblingblokken over, terwijl Readability meer korte en niet-proza-inhoud behield. De absolute token precision van beide tools wordt verlaagd door niet-gelabelde koptekst, dus de blokniveau-lekkage is het zuiverste directe signaal. De publieke benchmark met echte pagina's ordent hun word-F1 toevallig vergelijkbaar, maar de corpora en metrics verschillen en dit is geen cross-testbed validatie.

Robuustheid, kort: ik heb een expres gemankeerde tweeling van de canonieke pagina gedraaid (niet-afgesloten <p>, verkeerd geneste <b>/<i>, een losse </div>) en de recall was 3/3 met nul leaks, gelijk aan de goedgevormde versie. De eer daarvoor gaat naar jsdom's HTML5-treebuilder, die de rommel herstelt voordat Readability het ooit ziet. Geen enkele fixture liet de parser crashen.

Plus- en minpunten

Pluspunten

  • Artikelrecall is de sterke kant: 74/74 gelabelde blokken hersteld over 22 synthetische fixtures, token recall 1,000 op de gemengde set.
  • Regex-geclassificeerde paginabediening wordt betrouwbaar weggehaald — nav, advertentiebanner, sidebar, comments en footer verdwenen allemaal op de realistische pagina (5 van 6).
  • Niet-proza-inhoud blijft volledig behouden: tabellen, <pre>-code, figuurcaptions en regels onder 25 tekens bleven allemaal staan (8/8), terwijl trafilatura een caption weggooide.
  • Geen afhankelijkheid van semantische markup — een geneutraliseerde <div>-artikelversie scoorde identiek aan de <article>/<main>-versie.
  • Korte artikelen worden niet onterecht afgewezen: schone content werd hersteld tot 120 tekens, identiek over charThreshold 200/500/1000.
  • Volledig deterministisch: alle 22 fixtures gaven driemaal identieke tekst terug.
  • Twee minuten installatie, Apache-2.0, en de versie op npm is de versie die ik heb getest (0.6.0), dus niets hiervan is verouderd.

Minpunten

  • De sibling-append gate is te misbruiken: lange, linkarme, neutraal geclassificeerde promo-proza is niet te onderscheiden van artikeltekst en gaat mee zodra linkDensity < 0,25.
  • Op contentarme pagina's geeft het boilerplate terug als artikel in plaats van null — de bijna lege fixture kwam terug met de navigatiebalk als body.
  • isProbablyReaderable geeft false negatives op drie verschillende paginavormen, waarvan één niet te fixen is met tuning.
  • Er is een volledige DOM nodig tijdens runtime — de framing van "geen afhankelijkheden" verbergt de jsdom-kost, die de loop domineert.
  • parse() muteert het invoerdocument, dus je moet per pagina de DOM opnieuw opbouwen.
  • Geen fetchen, geen JavaScript-rendering, geen gestructureerde output. Het is één stap in een pipeline, niet de hele pipeline.
  • Gemiste gevallen op echte pagina's die in de tracker zijn gemeld (openingsalinea en drops vóór tabellen) kwamen op mijn fixtures niet terug, dus ik kan niet zeggen of ze zeldzaam zijn of dat mijn pagina's er gewoon nooit op leken.

Voor wie het geschikt is, en voor wie niet

Kies Readability als je HTML al in handen hebt en daar de artikeltekst uit wilt halen in puur JavaScript, binnen een Node-service waar een Python-afhankelijkheid onhandig zou zijn. (Ik heb timing niet als echte distributie verzameld, dus ik doe geen snelheidsclaim anders dan dat "de jsdom-opbouw de loop domineert, niet de extractie".) Reader-mode-functies, offline artikelarchivering, e-mailnieuwsbrieven, "clean view"-knoppen, browserextensies, documentatiepijplijnen met codeblokken en captions — dat is het domein, en de recall-cijfers laten zien dat het daar goed in is. Het gedrag is ook rechtstreeks uit de broncode leesbaar, en dat is waardevoller dan het klinkt wanneer je een collega moet uitleggen waarom precies één blok is meegekomen.

Sla het over als precisie in boilerplate-verwijdering de metriek is waarop je wordt afgerekend, en zeker als je een LLM-index voedt waarin een losse promo-paragraaf een terugvindbaar chunk wordt. Sla het over als je pagina's client-side renderen, want het leest alleen de DOM die je aanlevert en voert geen JavaScript uit. Sla het over als je {title, price, sku} nodig hebt in plaats van proza — geen configuratie verandert een content extractor in een schema-gedreven extractor. En als je pagina's verwerkt waarbij "had deze pagina überhaupt een artikel" een echte vraag is, vertrouw een niet-null resultaat dan niet als antwoord.

Alternatieven, en waar de Thunderbit-stack past

Dit is geen kritiek op een gratis Apache-2.0-library die door Mozilla wordt onderhouden — Readability is infrastructuur, draait al jaren in Firefox, en voor reader-mode-extractie is het niet voor niets de referentie-implementatie. Als je het bredere veld wilt zien, houd ik een doorlopende vergelijking bij in de open-source scrapers roundup en een bredere survey in the best web scraping tools.

Voor dezelfde fixtures over alle zes extractors heen, zie de six-library extraction comparison.

Opmerking van de auteur: Thunderbit is onze beheerde optie voor renderen en extractie op basis van een URL. Die is niet op deze fixtures gedraaid, dus er wordt geen overeenkomstige kwaliteitsclaim gesuggereerd. De relevante grens is of je al een DOM hebt en lokale artikel-extractie wilt, of ophalen/renderen en gestructureerde output als dienst wilt laten draaien. Self-hosting voorkomt een vendor-gebruikstarief, maar brengt nog steeds infrastructuur- en onderhoudskosten mee.

De eerlijke afruil: Readability is gratis, transparant en van jou om te draaien — je kunt precies de gate lezen die je output heeft bepaald, en dat is iets wat een beheerde API je niet geeft. Een managed stack kost geld en verbergt het mechanisme, maar dekt wel de fetch-render-structure-stappen die je anders zelf moet samenstellen. Als je de AI-ondersteunde kant van dat spectrum zoekt, heb ik elders geschreven over scrapen van elke website met AI en over AI-crawlers. Kies op basis van welke stappen je echt zelf wilt beheren.

Probeer Thunderbit voor webdata-extractie

Oordeel

Readability is een kandidaat wanneer je al een DOM hebt, JavaScript/Node gebruikt en af en toe extra sibling-inhoud prefereert boven agressief weglaten. In deze fixture-set herstelde het alle 74 gelabelde artikelblokken en behield het tabellen, code en captions. Dat resultaat is begrensd door synthetische enkelkolomspagina's; publieke recall op echte pagina's is 0,982, bekende misses rond opening en tabellen zijn niet gereproduceerd, en contentarme pagina's kunnen boilerplate als artikel teruggeven.

Schat de zwakte gewoon goed in. De belangrijkste foutzone in deze fixtures zit in precision, en die ligt op een specifieke, gedocumenteerde regel in de broncode: een sibling van meer dan 80 tekens met linkdichtheid onder 0,25 wordt aan je artikel toegevoegd, of het nu onderdeel ervan is of niet. Ik zag die omslag bij 0,143 versus 0,278 op identieke tekst. De benchmark op echte pagina's rapporteert precision 0,914 en recall 0,982. Als je geëxtraheerde tekst in een index stopt die later door een model wordt geciteerd, controleer dan zowel behouden boilerplate als weggelaten bodytekst in plaats van aan te nemen dat geen van beide foutklassen voorkomt.

Probeer Thunderbit voor webdata-extractie Get Started Free

FAQ's

Haalt Mozilla Readability alle boilerplate weg? Nee, en het cijfer hangt sterk af van wat je meet. Op de publieke benchmark met echte pagina's scoort readability_js 0.6.0 een precision van 0,914 — dus ongeveer 8,6% van wat het teruggeeft is geen bodycontent. Op een realistische testpagina van mij haalde het 5 van de 6 chrome-blokken weg (nav, advertentie, sidebar, comments en footer verdwenen allemaal), en hield alleen een neutraal geclassificeerde promo-alinea over. Op een fixture-set die ik bewust had gewogen met blokken die de heuristiek moesten verslaan, hield het 5 van de 17 blokken vast — dat laatste cijfer is een stress test, geen wereldwijde realiteit.

Heb ik jsdom nodig om readability.js in Node te gebruiken? Ja, of een andere DOM-implementatie. Readability is pure JavaScript, maar het werkt op een live document-object, dus onder Node lever je de DOM zelf aan — in mijn opzet was dat jsdom 29.1.1. De omschrijving "geen afhankelijkheden" gaat over het algoritme, niet over de runtime. Let er ook op dat parse() het document muteert dat het krijgt, dus bouw per pagina een frisse DOM in plaats van één document te hergebruiken.

Wat doet de charThreshold-optie eigenlijk? Niet wat de meeste mensen denken. Het zorgt er niet voor dat korte artikelen null worden — ik heb schone artikelen hersteld tot 120 tekens, met identieke geëxtraheerde lengte bij charThreshold-waarden van 200, 500 en 1000. De threshold bepaalt of de parser de grab opnieuw uitvoert met verwijderde opschoonflags; op een schone pagina is er niets weg te halen, dus is de output in beide gevallen hetzelfde. Het echte null-geval is een pagina zonder enige extraheerbare tekst, en zelfs een pagina met alleen een nav kwam niet als null terug maar gaf de nav als artikel.

Moet ik isProbablyReaderable aanroepen vóór parse()? Gebruik het als hint, niet als poortwachter. Het gaf false op drie paginavormen waar parse() daarna wel slaagde: inhoud in alleen <li>-elementen, tien alinea's die elk onder 140 tekens bleven, en één enkele alinea van 408 tekens. Het <li>-geval is niet te fixen met tuning, omdat de predictor alleen p-, pre- en article-nodes scoort; het veel-korte-paragrafen-geval vraagt om een lagere minContentLength; het ene lange geval om een lagere minScore, omdat een losse alinea 540 tekens nodig heeft om de standaarddrempel te halen. Als een pagina belangrijk is, parse hem en check het resultaat.

Readability of trafilatura voor artikelextractie? Op identieke fixture-bytes hield trafilatura minder boilerplate over (1/17 blokken versus 5/17), terwijl Readability meer korte content herstelde en een <figcaption> behield die trafilatura weggooide. Kies op basis van fouttolerantie en runtime. De publieke benchmark is aparte context, geen validatie van dit fixture-resultaat.

Ke
Ke
CTO bij Thunderbit | Senior Data Scientist & ML-expert Met bijna tien jaar ervaring in machine learning en data science is Ke Shen alumnus van Columbia University en voormalig Senior Data Scientist bij Walmart Labs. Met diepgaande, door vakgenoten erkende expertise in Python, R, Java en statistiek deelt hij praktijkgerichte inzichten over hoe je complexe AI-algoritmen van theorie naar productieklare architectuur brengt.
Inhoudsopgave
Thunderbit · AI-webdata-agent

Gegevens extraheren van elke pagina in 1 klik

Vertrouwd door meer dan 250.000 gebruikers
gratis abonnement beschikbaar
Van webpagina naar spreadsheet
Beschrijf wat je nodig hebt — Thunderbit's AI Agent scrapt het en exporteert het naar Excel, Google Sheets, Airtable of Notion. Gratis om te starten.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week