Šest knihoven, jedna anotovaná sada fixture, jeden vyhodnocovací skript. V těchto 22 syntetických fixture měla knihovna s nejvyšším únikem boilerplate — Mozilla Readability s 23,5 % — zároveň jako jediná dosáhla na všechny označené jednotky článku.
To je ten kompromis v jedné větě. A většina textů na tohle téma ho vůbec nezmíní, protože se většinou spokojí jen s precision a dál už nejde.
Co se ve skutečnosti měřilo
Každá fixture v téhle sadě má ground truth po jednotlivých jednotkách. Každý blok stránky — odstavce článku, navigaci, reklamu, postranní panel, vlákno komentářů, promo blok — je označen jako article nebo boilerplate a nese unikátní sentinel token. Takže otázka „dostal se extractor k této jednotce?“ je přesná kontrola přítomnosti podřetězce, ne žádná podobnostní metrika. Sentinel buď v outputu zůstane, nebo ne.
Dvacet dva fixture, 91 jednotek. Šest extractorů: Mozilla Readability 0.6.0 (přes jsdom 30.0.1), trafilatura 2.2.0, resiliparse 1.0.9, newspaper4k 0.9.6, goose3 3.1.22 a jusText 3.0.2. Python 3.14.2 a Node 22 běžely na stejném stroji. Článek neuchovává OS/CPU, přesné spuštění, počet opakování ani politiku zahřívání, takže sloupec s časováním je lokální pozorování, ne přenositelný benchmark.
Před spuštěním jsem si nastavil dvě pravidla. Každá Python knihovna se instalovala do vlastního prázdného virtualenv, takže její footprint je jen její vlastní a nic nezdědila po tom, co přitáhl soused. A žádný runner nepočítal metriku — každý jen vypsal raw extrahovaný text a veškerá čísla spočítal jeden společný vyhodnocovač. Šest nástrojů se tak porovnává nad identickou aritmetikou, ne nad šesti podobně vypadajícími definicemi „precision“.
Hlavní tabulka
| Knihovna | Recall článku (všech 22) | Únik boilerplate | Precision obsahových tokenů | Odpovězené precision fixture | Kontaminující tokeny |
|---|---|---|---|---|---|
| Readability | 1.0000 | 0.2353 | 0.9109 | 11/11 | 35 |
| trafilatura | 0.9865 | 0.0588 | 0.9411 | 11/11 | 4 |
| newspaper4k | 0.9865 | 0.0000 | 0.9452 | 11/11 | 0 |
| resiliparse | 0.9054 | 0.0588 | 0.9381 | 11/11 | 7 |
| jusText | 0.8378 | 0.4706 | 0.8760 | 10/11 | 74 |
| goose3 | 0.8243 | 0.0000 | 1.0000 | 10/11 | 0 |
Recall je agregovaný přes všech 22 fixture. Míra úniku, celková precision obsahových tokenů a kontaminace používají 11 fixture, které obsahují jak article, tak boilerplate jednotky; „answered“ ukazuje, u kolika z nich byl vůbec nějaký výstup. Kompletní čísla po fixture jsou v sixway-scores.json.
Jeden řádek v tabulce není výchozí chování. extract_plain_text v resiliparse má default main_content=False a já jsem ho volal s main_content=True. Rozdíl je velký: s defaultem napříč sadou propustí 17 z 17 boilerplate jednotek — všechny navigace, reklamy, postranní panely, komentáře i promoboxy — zatímco s přepínačem jen 1 z 17. Všechny ostatní knihovny výše byly volány se svými defaulty. Míra úniku 0,0588 u resiliparse je tedy to, co dělá při požadavku na hlavní obsah, zatímco extract_plain_text(html) samo o sobě je jiný produkt (default-vs-main-content.json).
Čtěte první a druhý sloupec dohromady, protože jeden bez druhého vás dovede k nesprávnému nástroji.
Readability nikdy nic nepřehlédne. Má perfektní recall na všech 22 fixture, a je v tom sama. Platí za to: 4 z 17 boilerplate jednotek unikly, 35 kontaminujících tokenů, čtyřnásobek oproti trafilatuře. Tři ze čtyř úniků mají stejný tvar — neutrálně klasifikovaný promo blok jako sourozenec článku, který heuristika připojení sourozenců vtáhne dovnitř. Pokud jeho výstup krmíte modelem, platíte za ty tokeny a model je čte jako součást článku.
newspaper4k je vyvážená volba. Žádný únik, žádné kontaminující tokeny, recall 0,9865 a výstup na všech 22 fixture. Kdybych měl vybrat jeden nástroj bez znalosti workloadu, vzal bych tenhle — a není to ten, po kterém většina lidí sahá.
goose3 má perfektní precision a v testu nejhorší recall. Každé obsahové slovo, které vrátil, skutečně patřilo článku. Zároveň na dvou fixture nevytáhl vůbec nic a na těch samých dvou ani nevypisoval žádný output. Perfektní precision je levná, pokud je povoleno prostě odmítnout odpověď.
Číslo precision, které dvě knihovny nadhodnocuje
To poslední stojí za to říct úplně na rovinu, protože jsem se na tom málem spálil i při publikaci.
Precision a F1 jsou tady podmíněné tím, že knihovna něco vůbec vrátí. Knihovna, která na fixture vrátí prázdný řetězec, nepřidá nic do čitatele ani do jmenovatele — takže odmítnutí je zdarma a opatrný extractor vypadá na precision lépe než důkladný, a to jen proto, že mlčí.
goose3 mělo agregovanou precision 1.0000 přes 10 vyhodnocených fixture, kde něco vrátilo. jusText měl 0,8760 přes 10 z 11. Readability, trafilatura, resiliparse a newspaper4k odpověděly na všech 11 z 11. Tabulka proto teď uvádí vedle precision i jmenovatel, aby se odmítnutí nemohlo schovat za lichotivý poměr.
Byla tu i horší verze tohohle. Můj první scorer průměroval recall článku přes stejnou 11fixtureovou sadu pro fidelity obsahu — tedy přes sadu, která vynechává fixture bez boilerplate, což je správně pro měření úniku. Vyšel mu u resiliparse recall 1.0000. Přes všech 22 fixture má resiliparse ve skutečnosti 0,9054, protože na fixture, kde článek žije celý jen v <li> prvcích a nikde není žádný <p>, sice něco vrátí, ale z 6 z 6 article jednotek nic neobnoví. Ta fixture nemá žádný boilerplate, takže spadla mimo průměr, a skutečné selhání se schovalo za perfektním skóre.
Kde který nástroj skutečně selhává
| Fixture | Co testuje | Kdo neobnoví nic |
|---|---|---|
Článek celý v <li>, žádné <p> | předpoklady o struktuře | resiliparse (0/6), goose3 (žádný výstup) |
| Jedna article jednotka o 129 znacích | práh pro krátký obsah | jusText |
| Deset krátkých odstavců, žádný dlouhý | práh pro krátký obsah | jusText |
| Téměř prázdný dokument | skutečná nulová hranice | goose3, jusText |
Všechny tyhle případy jsou konkrétní, reprodukovatelné chování, ne obecné „horší v extrakci“:
- resiliparse i goose3 předpokládají odstavce. Když některý z nich pošlete na stránku, jejíž tělo tvoří seznam — changelog, specifikace, FAQ, recept — resiliparse vrátí text bez obsahu seznamu a goose3 nevrátí nic. Resiliparse je v tom nebezpečnější, protože „něco“ vypadá jako úspěch.
- jusText má ostrý pádový práh délky. K tomu víc níže.
- Téměř prázdný dokument je jediný případ, kdy nic nevrátit je spíš správně, takže to bych ani jednomu nástroji nevyčítal.
jusText: propast, ne pozvolný sklon
jusText vracel output na 19 z 22 fixture a propustil 47 % boilerplate — nejvíc v testu, což je přesný opak jeho pověsti. Zajímavé ale je číslo, kvůli kterému jsem to celé spustil znovu.
jusText klasifikuje každý blok podle hustoty stop slov vůči jazykovému stoplistu a pak provádí kontextový průchod, který povýší blok neargood na good jen tehdy, když sousedí s už existujícím blokem good. Blok se sám o sobě dostane do good až nad length_high, který má výchozí hodnotu 200 znaků. Na dokumentu, kde žádný blok tu hranici nepřekročí, se nic nestane seedem pro povýšení a celá stránka se rozpadne na boilerplate.
Projel jsem to na dokumentu, jehož nejdelší odstavec má 151 znaků:
length_high | Good odstavce | Vrácené znaky |
|---|---|---|
| 200 (default) | 0 | 0 |
| 150 | 8 | 832 |
| 120 | 8 | 832 |
| 100 | 8 | 832 |
| 80 | 8 | 832 |
Z nuly na 832 znaků ve chvíli, kdy přes práh přejde právě jeden odstavec, a pak už se nic nemění, i kdybyste to povolili ještě víc. Jeden odstavec nad čarou odemkne celý dokument.
Než jsem z toho vyvodil závěr, projel jsem length_low přes čtyři hodnoty a max_link_density přes dvě — osm kombinací, všechny s nulovým výsledkem. Pravidlo tohoto projektu říká, že tvrzení o negativní schopnosti se nesmí opřít o méně než tři testované tvary parametrů nebo o vlastní chybové hlášení dodavatele, které pojmenuje pole, a jeden neúčinný parametr není poznatek o knihovně. Čísla jsou v justext-length-threshold.json.
Tohle neříká, že jusText extrahuje špatně. Na skutečné stránce s přirozeným jazykem v defaultu vrátil 1 190 znaků čistého textu článku. Říká to, že jusText má zdokumentovaný parametr, který se chová jako vypínač, a že výchozí poloha toho vypínače je u krátkých odstavců špatně.
Co instalujete a kolik stojí import

Stejné fixture, stejný stroj, každá knihovna ve vlastním prázdném virtualenv.
| Knihovna | Balíčky | site-packages | Cold import | p50 extrakce |
|---|---|---|---|---|
| resiliparse | 5 | 21.0 MiB | 0.015 s | 0.06 ms |
| jusText | 3 | 22.4 MiB | 0.777 s | 0.56 ms |
| goose3 | 16 | 44.3 MiB | 2.181 s | 1.85 ms |
| newspaper4k | 22 | 47.5 MiB | 2.812 s | 2.69 ms |
| trafilatura | 17 | 69.9 MiB | 1.584 s | 0.51 ms |
| Readability + jsdom | 32 (npm) | 26 MiB | 0.473 s | 6.37 ms |
V tomhle běhu měla resiliparse nejnižší cold-import i medián extrakce: 15 ms a 0,06 ms. Přesné poměry napříč runtime by přeháněly, co ten neúplný protokol dovoluje tvrdit, zvlášť když její nejpomalejší jednotlivá extrakce byla 1 098 ms. Než se tato čísla použijí pro serverless sizing, je potřeba odděleně měřit startup, první volání i ustálený stav.
trafilatura a resiliparse jsou na kvalitě prakticky remíza — 0,9697 proti 0,9681 F1 na obsahových tokenech, identická míra úniku 0,0588 — a já nevyhlašuji vítěze kvůli tak malému rozdílu. Na footprintu ale blízko nejsou: 21,0 MiB proti 69,9 MiB, 5 balíčků proti 17. Skutečný kompromis je mezi slepotou resiliparse vůči seznamům a třemi dalšími závislostmi trafilatury.
Dvě chyby v mém vlastním testbedu, odhalené před publikací
Srovnání výše skoro vůbec nevzniklo — a důvod za to stojí víc než kterýkoli jednotlivý řádek v tabulce.
Testbed neviděl dvě ze šesti knihoven. Původní fixture zapisovaly každou jednotku jako posloupnost unikátních nesmyslných tokenů — zzart01vf64 zzart01v56i — a právě to zajišťuje přesný recall. Zároveň to ale znamená, že fixture neobsahují žádná anglická funkční slova. Readability, trafilatura a resiliparse se rozhodují strukturálně z DOMu, takže byly v pořádku. goose3 a jusText se rozhodují lexikálně podle stop slov, a ta k počítání nebyla: oba vrátily na všech 22 fixture prázdný řetězec.
Tabulka se dvěma knihovnami na nule by působila autoritativně a neznamenala by nic. Než jsem ji vůbec začal psát, zkontroloval jsem to na reálné stránce: goose3 vrátil 1 017 znaků a jusText 1 190. Knihovny byly v pořádku. Testbed je neuměl reprezentovat.
Fixture jsem tedy přestavěl s anglickou prózou, do které byly sentinel tokeny vložené — stejná struktura, stejné třídy, stejné pozice v DOM, stejné hranice jednotek, stejné sentinely, 1 568 tokenů nahrazených kus za kus. goose3 se tak posunul z 0 na 20 z 22.
Pak ale rebuild rozbil dvě vlastní věci, a obě byly moje chyba. Anglické slovo má asi šest znaků; zzart01vf64 asi dvanáct. Nahrazení kus za kus rozseklo délku všech jednotek na polovinu — 21 646 znaků textu jednotek se změnilo na 10 986 a nejdelší jednotka spadla z 1 513 na 622. Tím se tiše přepsaly fixture, jejichž hlavním účelem je délka. jusText, jehož chování je délková propast, se tím samo o sobě posunul z 19 z 22 na 6 z 22. Kdybych publikoval půlenou verzi, jeho číslo by bylo špatně o faktor tři, a ještě směrem, který z něj dělá horší knihovnu.
Druhá chyba: čerpání všech jednotek z jednoho sdíleného korpusu obnovilo hustotu stop slov, ale zničilo vlastnost, na které závisí token-level scoring. Slovník článku a slovník boilerplate musí být disjunktní, jinak „extracted tokens that are boilerplate tokens“ začne počítat slovo the. U deseti z 22 fixture se slovníky překrývaly, zatímco v původní sadě nebyl žádný overlap. Oprava spočívala v tom, že se obsahovým slovům přidala jednotková přípona a funkční slova zůstala bez ní — pro lexikální knihovny skutečná stop slova k počítání, pro scorer disjunktní obsahový slovník.
Proto se zde sloupce na úrovni tokenů jmenují content_token_* a nepřebírají se z publikovaných čísel Readability versus trafilatura. Je to jiná veličina, měřená jen na obsahových slovech, a citovat jednu jako druhou by bylo chybné.
Při rebuildingu se ukázala ještě jedna věc, která nebyla moje: tři fixture s link density mají </a> uvnitř slova — <a href="/x">zzsibp015qlhf zzsi</a>bp015qbht — protože anchor byl umístěn na přesnou znakovu pozici, aby vyšel konkrétní poměr. Renderovaný text je stejný, takže původní scoring si toho nevšiml, ale každý extractor, který pracuje po elementech a ne po textových bězích, vidí dva fragmenty tam, kde ostatní vidí jedno slovo. Opraveno, a rozdíl v délce vazby zaznamenán místo toho, aby se tiše pohltil.
Kdo by měl co použít
Krmit model a platit za tokeny? newspaper4k nebo goose3. Oba propustily nula boilerplate jednotek a nula kontaminujících tokenů. newspaper4k, pokud chcete odpověď na každé stránce; goose3, pokud je vám milejší ticho než odhad a vaše stránky mají odstavce.
Optimalizace Python cesty citlivé na latenci? Zařaďte do srovnání resiliparse. V tomhle testu měla nejnižší pozorovaný import i medián extrakce a kvalitou byla velmi blízko trafilatuře — ale jen s main_content=True, což není default. Nejprve otestujte layouty bohaté na seznamy a nepřevádějte tyto lokální časy na přesný cross-runtime poměr rychlosti.
Archivace, nebo cokoli, kde je horší ztratit obsah než přidat navíc? Readability. Je jediná, která na všech fixture obnovila každý article blok, a 35 přebytečných tokenů je malá cena, pokud alternativou je ztratit odstavec.
Vícejazyčná práce? jusText je kandidát k vyzkoušení, protože dodává jazykové stoplisty. Tahle studie vícejazyčnou extrakci netestovala, takže to je důvod, proč ho vyhodnotit, ne důkaz, že vyhrává. Otestujte length_high proti reprezentativním délkám odstavců.
Cokoli, co není článek? Nic z toho. Všechny tyto nástroje staví na předpokladu, že stránka má jedno hlavní tělo prózy, a produktový listing, stránka s výsledky vyhledávání nebo dashboard ten předpoklad porušují způsoby, které žádný parametr neopraví.
Kde dává smysl spravované API
Všechno výše je o knihovnách, které provozujete sami: předáte HTML a dostanete text. Jejich chybové režimy se liší podle tvaru stránky, takže zvolené defaulty ověřte na vlastním korpusu. Strukturovaná extrakce polí a fetching/rendering nejsou součástí tohoto srovnání.
Poznámka autora: Thunderbit je naše spravovaná služba pro workflow typu URL-in a strukturovaný výstup. Nebyla spuštěna přes tyto fixture, takže z toho neplyne žádné srovnání kvality. Relevantní hranice rozhodnutí je, jestli už HTML máte a chcete lokální textový extractor, nebo chcete fetching/rendering i provoz řešit službou.
Upřímné shrnutí: pokud už HTML máte a chcete text, jedna z těchto šesti možností je zdarma a dobrá, a tahle tabulka vám řekne která. Pokud stránku fetchujete ve velkém nebo chcete řádky místo prózy, je to jiný nákup.
Pokud se rozhodujete spíš mezi hostovanými fetchery, naše shrnutí web scraping API pokrývá tuhle oblast a naše srovnání cen SEO a data API ukazuje, kolik stojí. Pro self-hosted část je širší pohled v open-source scraper pillaru a pokud ve skutečnosti potřebujete Markdown místo prostého textu, převod HTML do Markdown v Pythonu je místo, kde se ztrácí nejvíc.
Vyzkoušet Thunderbit pro extrakci webových dat
Verdikt
Neexistuje vítěz a tabulka, která by jednoho jmenovala, by lhala o skutečném kompromisu.
Před výběrem si postavte malý akceptační korpus: zahrňte články jen z listů, krátké odstavce, promo sousedy, téměř prázdnou stránku a případy, kde je lepší nic nevrátit než kontaminaci. Hodnoťte zvlášť obnovu článku, únik boilerplate a odmítnutí odpovědi. Na těchto fixture favorizovala Readability recall, newspaper4k měla nejvyváženější řádek a resiliparse byla kandidát na nízkou latenci s neviditelností vůči listovému obsahu; tyto nálepky by se bez ověření neměly přenášet na jiné tvary stránek.
Co bych vám skutečně řekl, je užší než cokoli z toho: spusťte fixture nad vlastními tvary stránek, než si vyberete. Dvě ze šesti neviděly původní testbed a jedna z nich měla perfektní recall, který zakryl totální selhání. Srovnávací tabulka je k tomu výchozí bod, ne náhrada.
Vyzkoušet Thunderbit pro extrakci webových dat Get Started Free
Často kladené otázky
Jsou tato čísla srovnatelná s publikovanými benchmarky pro tyto knihovny? Ne, a tak bych je ani necitoval. Jde o řízené fixture se syntetickými, ale označenými jednotkami, takže všech šest vidělo identické bajty a srovnání mezi nimi je férové. Publikované hodnoty, jako je benchmark scrapinghub pro article extraction, používají reálné korpusy a měří něco jiného a těžšího. Tuhle tabulku používejte k porovnání těchto šesti nástrojů mezi sebou, ne proti číslu z článku.
Proč má Readability mnohem vyšší únik než trafilatura, když obě jsou DOM-based? Protože každá z nich kreslí hranici jinde. Tři ze čtyř úniků Readability jsou neutrálně klasifikované promo bloky jako sourozenci článku, které její heuristic appendování sourozenců vtáhne dovnitř s myšlenkou, že přilehlý dlouhý obsah s nízkým link density je asi součást příběhu. Často je. Na těchto fixture je to promo blok. trafilatura je přísnější v tom, co připojuje, a unikla jí jedna stejná jednotka.
Mám věřit číslům precision pro goose3 a jusText? Jen spolu s velikostí vzorku. Oba byly hodnoceny na 10 z 11 fixture, které mají jak article, tak boilerplate jednotky, protože na jedné z nich nic nevrátily, a fixture bez outputu nepřispívá ani do jedné strany poměru. Precision 1.0000 u goose3 je skutečná pro stránky, na kterých odpověděla; recall 0.8243 přes všech 22 fixture je druhá polovina téhož faktu.
Záleží práh délky v jusText na reálných stránkách?
Záleží čistě na délce vašich odstavců. Zpravodajský článek s odstavci o 300 znacích překročí length_high hned na prvním a bude se chovat normálně — proto jusText vrátil na reálné stránce v defaultu 1 190 čistých znaků. Stránka s krátkými odstavci, položkami seznamu nebo produktovými anotacemi to ale nemusí nikdy překročit, a pak jusText vrátí prázdný řetězec místo částečné odpovědi. Nastavte to explicitně, ne až v produkci.
Co se tady netestovalo? Skutečné stránky vůbec. Vícejazyčná extrakce, přestože právě stoplisty jsou hlavní předností jusText. Paměť při zátěži. Jakákoli stránka, která není článek — žádné produktové listingy, žádné výsledky hledání, žádné dashboardy. Hraniční případy kódování. A dvě ekosystémové větve, Node a Python, byly porovnávány podle chování knihoven, ne podle runtime výkonu, takže milisekundová čísla přes tuhle hranici berte jako řády velikosti, ne přesné poměry.


