De extractor die de meeste boilerplate lekte, miste in deze fixture-set geen enkele contentunit

Laatst bijgewerkt op August 14, 2026
De extractor die de meeste boilerplate lekte, miste in deze fixture-set geen enkele contentunit
AI-samenvatting
Zes bibliotheken, één geannoteerde fixture-set, één scorer. In deze 22 synthetische fixtures was de bibliotheek met de hoogste boilerplate-lek — Mozilla's Readability, met 23,5% — ook de enige die elke gelabelde article-unit wist terug te halen. Dat is in één zin de afruil, en de meeste artikelen over dit onderwerp leggen die nooit bloot, omdat de meesten alleen precision meten en daar stoppen. Je betaalt per token? Dan zijn newspaper4k of goose3 logische keuzes. Beide lekten nul boilerplate-units en nul storende tokens. newspaper4k als je op elke pagina een antwoord wilt; goose3 als je liever stilte hebt dan een gok, en je pagina's paragrafen hebben.

Zes bibliotheken, één geannoteerde fixture-set, één scorer. In deze 22 synthetische fixtures was de bibliotheek met de hoogste boilerplate-lek — Mozilla's Readability, met 23,5% — ook de enige die elke gelabelde article-unit wist terug te halen.

Dat is in één zin de afruil, en de meeste artikelen over dit onderwerp leggen die nooit bloot, omdat de meesten alleen precision meten en daar stoppen.

Wat er echt is gemeten

Elke fixture in deze set bevat ground truth per unit. Elk blok van de pagina — de alineatekst van het artikel, de navigatie, de advertentie, de sidebar, de commentaarthread, de promo — is gelabeld als article of boilerplate en voorzien van een unieke sentinel-token. Dus "heeft de extractor deze unit teruggevonden" is exacte substring-membership, geen similarity-score. Een sentinel overleeft óf in de output, óf niet.

Tweeëntwintig fixtures, 91 units. Zes extractors: Mozilla Readability 0.6.0 (via jsdom 30.0.1), trafilatura 2.2.0, resiliparse 1.0.9, newspaper4k 0.9.6, goose3 3.1.22 en jusText 3.0.2. Python 3.14.2 en Node 22 draaiden op dezelfde machine. Het artikel bewaart het OS/CPU, de exacte aanroepen, het aantal herhalingen en de warm-up policy niet, dus de timingkolom is een lokale observatie en geen overdraagbare benchmark.

Twee regels heb ik mezelf opgelegd voordat er ook maar iets draaide. Elke Python-bibliotheek werd geïnstalleerd in zijn eigen lege virtualenv, zodat de footprint alleen van die bibliotheek is en niet wordt geërfd van wat een buurman meekreeg. En geen enkele runner berekent zelf een metric — elke tool dumpt alleen de ruwe geëxtraheerde tekst, en één scorer produceert alle cijfers. Zo worden zes tools vergeleken op exact dezelfde rekenwijze, in plaats van op zes net iets anders gedefinieerde varianten van "precision".

De hoofdtafel

BibliotheekArticle-recall (alle 22)Boilerplate-lekContent-token precisionBeantwoorde precision-fixturesVerstorende tokens
Readability1.00000.23530.910911/1135
trafilatura0.98650.05880.941111/114
newspaper4k0.98650.00000.945211/110
resiliparse0.90540.05880.938111/117
jusText0.83780.47060.876010/1174
goose30.82430.00001.000010/110

Recall is geaggregeerd over alle 22 fixtures. Leak-rate, geaggregeerde content-token precision en contaminatie gebruiken de 11 fixtures met zowel article- als boilerplate-units; “beantwoord” laat zien hoeveel van die fixtures output opleverden. De volledige cijfers per fixture staan in sixway-scores.json.

Eén rij in die tabel is geen standaardgedrag. resiliparse's extract_plain_text gebruikt main_content=False als default, en ik heb het aangeroepen met main_content=True. Dat verschil is niet klein: met de defaults lekt het 17 van de 17 boilerplate-units in de set — elke nav, ad, sidebar, commentaarthread en promo — tegenover 1 van de 17 met de vlag aan. Alle andere bibliotheken hierboven werden met hun defaults aangeroepen. Dus de 0,0588 leak-rate van resiliparse is wat je krijgt als je om main content vraagt, en extract_plain_text(html) op zichzelf is een ander product (default-vs-main-content.json).

Lees de eerste en de tweede kolom samen, want als je er maar één leest, kies je het verkeerde hulpmiddel.

Readability mist nooit. Perfecte recall op alle 22 fixtures, en daarin is het uniek. Daar betaalt het wel voor: 4 van de 17 boilerplate-units lekten door, 35 storende tokens, vier keer de leak-rate van trafilatura. Drie van die vier leaks hebben dezelfde vorm — een promo-blok zonder bijzondere status dat als sibling naast het artikel staat, en door de sibling-append-heuristiek wordt meegenomen. Voer je de output aan een model, dan betaal je voor die tokens en leest het model ze gewoon als artikel.

newspaper4k is de evenwichtige keuze. Nul leak, nul storende tokens, 0,9865 recall en output op alle 22 fixtures. Als ik er blind één moest kiezen zonder de workload te kennen, zou dit mijn keuze zijn — en het is niet degene waar de meeste mensen automatisch aan denken.

goose3 heeft perfecte precision en de slechtste recall van de test. Elke content-woord die het terugstuurde, was echt article-content. Het leverde op twee fixtures helemaal niets op en produceerde op diezelfde twee ook geen output. Perfecte precision is goedkoop als je mag weigeren.

Het precisiecijfer dat twee bibliotheken te goed liet lijken

Dat laatste punt moet concreet gemaakt worden, want dit was bijna een valkuil in mijn publicatie.

Precision en F1 zijn hier voorwaardelijk op output. Een bibliotheek die op een fixture een lege string teruggeeft, draagt niets bij aan de teller en niets aan de noemer — dus afzien is gratis, en de precision van een conservatieve extractor lijkt daardoor beter dan die van een grondige, puur omdat hij zwijgt.

goose3 had een geaggregeerde precision van 1.0000 over de 10 gescoorde fixtures waarin het output terugstuurde. jusText zat op 0.8760 over 10 van de 11. Readability, trafilatura, resiliparse en newspaper4k beantwoordden 11 van de 11. De tabel laat nu dat aantal in de noemer naast precision zien, zodat afzien niet achter een gunstige ratio kan verdwijnen.

Er bestond een nog slechtere versie hiervan. Mijn eerste scorer nam de article recall gemiddeld over dezelfde content-fidelity-set van 11 fixtures — de set zonder fixtures met alleen content en geen boilerplate, wat correct is om leak te meten. Daarin rapporteerde hij resiliparse op 1.0000 recall. Over alle 22 fixtures zit resiliparse op 0.9054, omdat op de fixture waarvan het artikel volledig in <li>-elementen leeft en nergens een <p> voorkomt, het wel output teruggeeft maar 0 van de 6 article-units terughaalt. Die fixture heeft geen boilerplate, dus viel buiten het gemiddelde, en een echte fout verdween achter een perfecte score.

Waar elk van deze tools daadwerkelijk stukloopt

FixtureWat wordt getestWie haalt niets terug
Artikel volledig in <li>, geen <p>aannames over structuurresiliparse (0/6), goose3 (geen output)
Eén article-unit van 129 tekensdrempel voor korte contentjusText
Tien korte alinea's, geen langedrempel voor korte contentjusText
Bijna leeg documentde echte nulgrensgoose3, jusText

Dit zijn allemaal specifieke, reproduceerbare gedragingen en geen algemene "slechter bij extractie":

  • resiliparse en goose3 gaan beide uit van paragrafen. Geef je ze een pagina waarvan de body vooral uit een lijst bestaat — een changelog, een specificatie, een FAQ, een recept — dan geeft resiliparse tekst terug zonder de lijstinhoud, terwijl goose3 niets teruggeeft. resiliparse is hier de gevaarlijkere van de twee, omdat iets teruggeven op succes lijkt.
  • jusText heeft een lengtedrempel, en die is scherp. Daarover hieronder meer.
  • Het bijna lege document is het ene geval waarin niets teruggeven eigenlijk juist kan zijn, dus dat zou ik geen bibliotheek aanrekenen.

jusText: een cliff, geen helling

jusText leverde output op 19 van de 22 fixtures en lekte 47% van de boilerplate — de hoogste score in de test, wat precies het tegenovergestelde is van zijn reputatie. Maar het interessante getal is het getal dat mij dwong alles opnieuw te draaien.

jusText classificeert elk blok op basis van stopword-dichtheid tegenover een taalspecifieke stoplist, en doet daarna een contextgevoelige pass waarbij een neargood-blok alleen tot good wordt gepromoveerd als het naast een bestaand good-blok staat. Een blok bereikt op eigen kracht good alleen boven length_high, standaard 200 tekens. Op een document waar niets over die grens gaat, seedt niets de promotie en vervalt de hele pagina tot boilerplate.

Ik testte dit op een document waarvan de langste alinea 151 tekens heeft:

length_highGoede alinea'sTeruggegeven tekens
200 (default)00
1508832
1208832
1008832
808832

Van nul naar 832 tekens zodra precies één alinea de drempel passeert, en daarna verandert er niets meer, hoe ver je hem ook verder versoepelt. Eén alinea over de grens ontgrendelt het hele document.

Voordat ik daar conclusies uit trok, heb ik length_low over vier waarden geswept en max_link_density over twee — acht combinaties, allemaal met nul output. De regel in dit project is dat een claim over een negatieve capability minstens drie parameter-vormen vereist of een foutmelding van de vendor zelf die het veld noemt, en één niet-productieve parameter zegt niets over de bibliotheek. De cijfers staan in justext-length-threshold.json.

Dit zegt niet dat jusText slecht extraheert. Op een echte pagina in natuurlijke taal leverde het bij de defaults 1.190 tekens schone article-tekst op. Het zegt wel dat jusText een gedocumenteerde knop heeft die zich gedraagt als een aan/uit-schakelaar, en dat de standaardstand van die schakelaar verkeerd is voor documenten met korte alinea's.

Wat je installeert, en wat import kost

Measured results chart: Install footprint vs cold import

Dezelfde fixtures, dezelfde machine, elke bibliotheek in een eigen lege virtualenv.

BibliotheekPackagessite-packagesCold importExtractie p50
resiliparse521.0 MiB0.015 s0.06 ms
jusText322.4 MiB0.777 s0.56 ms
goose31644.3 MiB2.181 s1.85 ms
newspaper4k2247.5 MiB2.812 s2.69 ms
trafilatura1769.9 MiB1.584 s0.51 ms
Readability + jsdom32 (npm)26 MiB0.473 s6.37 ms

In deze run had resiliparse de laagste cold-import- en mediane extractiewaarden: 15 ms en 0,06 ms. Exacte cross-runtime-ratio's zouden meer suggereren dan dit onvolledige protocol aankan, zeker omdat de langzaamste losse extractie 1.098 ms was. Voor je deze cijfers gebruikt voor serverless sizing, heb je aparte distributies nodig voor startup, first-call en steady state.

trafilatura en resiliparse zitten qua kwaliteit op een dood spoor naast elkaar — 0.9697 tegenover 0.9681 content-token F1, en dezelfde 0,0588 leak-rate — en ik noem daar geen winnaar op een verschil dat zo klein is. Qua footprint liggen ze niet dicht bij elkaar: 21,0 MiB tegenover 69,9 MiB, 5 packages tegenover 17. De afruil die je echt maakt is resiliparse's blindheid voor lijsten tegenover de drie extra vereisten van trafilatura.

Twee bugs in mijn eigen testomgeving, ontdekt vóór publicatie

De vergelijking hierboven was bijna niet gebeurd, en waarom niet zegt meer dan welke rij dan ook.

De fixture-set kon twee van de zes bibliotheken niet zien. De originele fixtures schrijven elke unit als een reeks unieke onzinnige tokens — zzart01vf64 zzart01v56i — en dat is precies wat recall exact maakt. Het betekent ook dat er geen Engelse function words in de fixtures zitten. Readability, trafilatura en resiliparse beslissen structureel, op basis van de DOM, dus die werden niet geraakt. goose3 en jusText beslissen lexicaal, door stopwords te tellen, en er waren er geen om te tellen: beide gaven op alle 22 fixtures een lege string terug.

Een tabel waarin twee bibliotheken op nul scoren zou gezaghebbend hebben geleken en niets betekend hebben. Ik heb dat eerst gecheckt op een echte pagina: goose3 gaf 1.017 tekens terug en jusText 1.190. De bibliotheken waren prima. De testomgeving kon ze alleen niet representeren.

Dus zijn de fixtures herbouwd met Engelse prose waarin de sentinels bleven zitten — dezelfde structuur, dezelfde classes, dezelfde DOM-posities, dezelfde unit-grenzen, dezelfde sentinels, 1.568 tokens één-op-één vervangen. goose3 ging daarmee van 0 naar 20 van de 22.

Daarna brak de rebuild zelf nog twee dingen, en die lagen volledig aan mij. Een Engels woord is ongeveer zes tekens; zzart01vf64 is ongeveer twaalf. Door één-op-één te vervangen halveerde elke unit — 21.646 tekens aan unit-tekst werden 10.986, en de langste unit zakte van 1.513 naar 622. Daarmee herschreef ik stilzwijgend precies die fixtures waarvan lengte het hele doel is. jusText, waarvan het gedrag een lengtedrempel is, zakte daardoor van 19 van de 22 naar 6 van de 22. Had ik de gehalveerde versie gepubliceerd, dan was jusText's cijfer met een factor drie fout geweest, en wel in de richting die het als slechter laat lijken.

Het tweede probleem: alle units uit één gedeeld corpus halen herstelde wel de stopword-dichtheid, maar vernietigde de eigenschap waarop de token-level scoring leunt. Artikel- en boilerplate-vocabularies moeten disjunct zijn, anders telt "geëxtraheerde tokens die boilerplate-tokens zijn" ineens het woord the mee. Tien van de 22 fixtures kregen overlappende vocabularies, tegenover nul in de originele set. De oplossing was om content-woorden per unit een suffix te geven en function words ongemoeid te laten — echte stopwords voor de lexicale bibliotheken om te tellen, en een disjuncte content-vocabulaire voor de scorer.

Daarom heten de token-level kolommen hier ook content_token_* en zijn ze niet hergebruikt uit de gepubliceerde Readability-versus-trafilatura-cijfers. Het is een andere grootheid, gemeten over alleen contentwoorden, en het zou fout zijn om het ene als het andere te citeren.

Tijdens die herbouw dook nog iets op dat niet door mij was veroorzaakt: drie link-density-fixtures plaatsen </a> in het midden van een woord<a href="/x">zzsibp015qlhf zzsi</a>bp015qbht — omdat de anchor op karakteroffset is gepositioneerd om exact op de gewenste ratio uit te komen. De gerenderde tekst blijft hetzelfde, dus de originele scoring merkte het niet op, maar elke extractor die per element werkt in plaats van per tekst-run ziet twee fragmenten waar de rest één woord ziet. Hersteld, met het linked-character-delta vastgelegd in plaats van stilzwijgend geabsorbeerd.

Wie wat zou moeten gebruiken

Je voedt een model en betaalt per token? newspaper4k of goose3. Beide lekten nul boilerplate-units en nul storende tokens. newspaper4k als je op elke pagina een antwoord wilt; goose3 als je liever stilte hebt dan een gok, en je pagina's paragrafen hebben.

Je optimaliseert een Python-pad dat gevoelig is voor latency? Neem resiliparse mee in de bake-off. Het had hier de laagste gemeten import- en mediane extractiewaarden en zat qua kwaliteit dicht bij trafilatura — maar alleen met main_content=True, wat niet de default is. Check eerst layouts met veel lijsten, en maak van deze lokale timings geen exacte cross-runtime speed ratio.

Archivering, of alles waarbij content missen erger is dan extra content? Readability. Het is de enige die op elke fixture elke article-unit terughaalde, en 35 losse tokens zijn een kleine prijs als het alternatief is een alinea verliezen.

Meertalig werk? jusText is een kandidaat om mee te nemen, omdat het taal-specifieke stoplists meelevert. Deze studie testte geen meertalige extractie, dus die eigenschap is een reden om het te evalueren, geen bewijs dat het wint. Test length_high tegen representatieve alinealengtes.

Iets dat geen artikel is? Geen van deze. Ze zijn allemaal gebouwd op de aanname dat een pagina één hoofdblok proza heeft, en een productoverzicht, zoekresultatenpagina of dashboard breekt die aanname op manieren die geen enkele parameter oplost.

Waar een managed API past

Alles hierboven betreft een bibliotheek die je zelf draait: je levert HTML aan en krijgt tekst terug. Hun zichtbare faalmodi verschillen per paginavorm, dus valideer de gekozen defaults tegen je eigen corpus. Gestructureerde veldextractie en fetchen/renderen vallen buiten deze vergelijking.

Auteurstoevoeging: Thunderbit is onze managed service voor URL-in-workflows en gestructureerde output. Die is niet op deze fixtures getest, dus hieruit volgt geen kwaliteitsvergelijking. De relevante scheidslijn is of je de HTML al hebt en lokaal tekst wilt extraheren, of dat je fetchen/renderen en operations door een service wilt laten afhandelen.

Eerlijk gezegd: als je de HTML al hebt en tekst wilt, dan is een van deze zes gratis en goed, en deze tabel vertelt je welke. Als je op schaal pagina's ophaalt, of je wilt rijen in plaats van proza, dan koop je iets anders.

Als je juist tussen hosted fetchers kiest, behandelt onze web scraping API-roundup dat landschap en onze kostenvergelijking van SEO- en data-API's laat zien wat ze kosten. Voor de self-hosted kant is de open-source scraper-pillar het bredere overzicht, en als je in plaats van platte tekst eigenlijk Markdown nodig hebt, dan laat HTML naar Markdown converteren in Python zien waar de meeste verliesposten zitten.

Probeer Thunderbit voor webgegevens-extractie

Oordeel

Er is geen winnaar, en een tabel die er wel één aanwijst, zou over een echte afruil liegen.

Bouw eerst een kleine acceptatie-corpus voordat je kiest: neem artikelen die alleen uit lijsten bestaan, korte alinea's, promo-siblings, een bijna lege pagina en voorbeelden waarin niets teruggeven beter is dan contaminatie. Score article-recovery, boilerplate-leak en abstention apart. Op deze fixtures koos Readability voor recall, leverde newspaper4k de meest gebalanceerde rij op, en was resiliparse een latency-kandidaat met een blind spot voor lijstcontent; die labels moeten niet buiten de geteste vormen worden doorgetrokken zonder validatie.

Wat ik je in het echt zou zeggen is smaller dan dat: draai de fixtures tegen je eigen paginavormen voordat je kiest. Twee van de zes konden de testomgeving waarmee ik begon niet eens zien, en één daarvan scoorde een perfecte recall die een totale mislukking verborg. Een vergelijkingstabel is daarvoor een startpunt, geen vervanging.

Probeer Thunderbit voor webgegevens-extractie Get Started Free

Veelgestelde vragen

Zijn deze cijfers vergelijkbaar met de gepubliceerde benchmarks voor deze bibliotheken? Nee, en ik zou ze ook niet zo citeren. Dit zijn gecontroleerde fixtures met synthetische maar gelabelde units, dus alle zes zagen identieke bytes en de onderlinge vergelijking is eerlijk. Gepubliceerde cijfers zoals de scrapinghub article-extraction benchmark gebruiken echte corpora en meten iets anders en moeilijkers. Gebruik deze tabel om deze zes bibliotheken onderling te vergelijken, niet tegen een getal uit een paper.

Waarom is Readability's leak-rate zoveel hoger dan die van trafilatura, terwijl beide DOM-based zijn? Vanwege de grens die elk van hen trekt. Drie van Readability's vier leaks zijn promo-blokken zonder bijzondere status die als siblings naast het artikel staan, en de sibling-append-heuristiek trekt ze mee vanuit de gedachte dat aangrenzende lange content met weinig links waarschijnlijk deel van het verhaal is. Vaak klopt dat. Op deze fixtures is het een promo. trafilatura is strikter in wat het toevoegt en lekte één van dezelfde units.

Moet ik de precision-cijfers voor goose3 en jusText vertrouwen? Alleen samen met het aantal samples. Beide zijn gescoord op 10 van de 11 fixtures die zowel article- als boilerplate-units hebben, omdat ze op één ervan niets teruggaven, en een fixture zonder output draagt bij aan geen van beide kanten van de ratio. goose3's 1.0000 precision is echt voor de pagina's waarop het antwoord gaf; zijn 0.8243 recall over alle 22 fixtures is de andere helft van hetzelfde verhaal.

Maakt jusText's lengtedrempel uit op echte pagina's? Dat hangt volledig af van je alinea-lengtes. Een nieuwsartikel met alinea's van 300 tekens passeert length_high al bij de eerste en gedraagt zich normaal — daarom gaf jusText op een echte pagina met defaults 1.190 schone tekens terug. Een pagina met korte alinea's, lijstitems of productteksten haalt die grens misschien nooit, en dan geeft jusText een lege string terug in plaats van een gedeeltelijk antwoord. Stel het expliciet in in plaats van er pas in productie achter te komen.

Wat is hier niet getest? Echte webpagina's, helemaal niet. Meertalige extractie, ondanks dat de stoplists van jusText juist het belangrijkste verkoopargument zijn. Geheugen onder load. Elke pagina die geen artikel is — geen productoverzichten, geen zoekresultaten, geen dashboards. Encoding-edge-cases. En de twee Node- en Python-ecosystemen zijn vergeleken op bibliotheekgedrag, niet op runtime-performance, dus de milliseconde-cijfers over die grens heen moeten als ordegroottes worden gelezen en niet als precieze ratio's.

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