Altı kütüphane, tek bir etiketli fixture seti, tek bir puanlayıcı. Bu 22 sentetik fixture içinde, şablon metin sızıntısı en yüksek olan kütüphane — Mozilla's Readability, %23,5 ile — aynı zamanda etiketlenmiş her makale birimini kurtarabilen tek araçtı.
Bu, konunun tek cümlelik özeti; ama bu başlıktaki yazıların çoğu bunu hiç görünür kılmaz, çünkü çoğu yalnızca precision ölçüp geçer.
Aslında ne ölçüldü
Bu setteki her fixture, birim bazında doğruluk verisi içeriyor. Sayfanın her bloğu — makale paragrafları, menü, reklam, kenar çubuğu, yorum dizisi, promosyon alanı — article ya da boilerplate olarak etiketlenmiş ve benzersiz bir sentinel token ile işaretlenmiş durumda. Yani "ayıklayıcı bu birimi geri getirdi mi?" sorusu benzerlik puanı değil, tam alt dize eşleşmesi. Bir sentinel ya çıktıda kalır ya da kalmaz.
Yirmi iki fixture, 91 birim. Altı ayıklayıcı: Mozilla Readability 0.6.0 (jsdom 30.0.1 üzerinden), trafilatura 2.2.0, resiliparse 1.0.9, newspaper4k 0.9.6, goose3 3.1.22 ve jusText 3.0.2. Aynı makinede Python 3.14.2 ve Node 22 çalıştı. Makale, işletim sistemi/CPU bilgisini, tam çağrıları, tekrar sayısını veya ısınma politikasını korumuyor; bu yüzden zamanlama sütunu taşınabilir bir benchmark değil, yerel bir gözlem.
Çalıştırmadan önce kendime iki kural koydum. Her Python kütüphanesi kendi boş sanal ortamına kuruldu; böylece bellek ayak izi kendine ait kaldı, yanındaki aracın yüklediklerinden miras alınmadı. Ayrıca hiçbir çalıştırıcı metrik hesaplamadı — hepsi ham çıkarılmış metni döktü ve tüm sayıları tek bir puanlayıcı üretti; böylece altı araç, birbirine benzeyen altı "precision" tanımıyla değil, aynı matematikle karşılaştırıldı.
Başlık tablosu
| Kütüphane | Makale recall (22'sinin tamamı) | Şablon metin sızıntısı | İçerik token precision | Precision yanıtlanan fixture | Kirletici token sayısı |
|---|---|---|---|---|---|
| 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, 22 fixture'ın tamamı üzerinden toplandı. Sızıntı oranı, toplam içerik-token precision ve kontaminasyon, hem makale hem de şablon blokları içeren 11 fixture üzerinden hesaplandı; “yanıtlanan” sütunu bunlardan kaçına çıktı verildiğini gösteriyor. Tam fixture bazlı rakamlar sixway-scores.json içinde.
Tablodaki satırlardan biri varsayılan değil. resiliparse'ın extract_plain_text fonksiyonu varsayılan olarak main_content=False ile gelir; ben ise onu main_content=True ile çağırdım. Aradaki fark az değil: varsayılan hâlde setteki 17 şablon bloğunun 17'sini de sızdırıyor — yani tüm menü, reklam, kenar çubuğu, yorum ve promosyon alanlarını — ama bayrak açıkken bu sayı 17'de 1'e düşüyor. Yukarıdaki diğer tüm kütüphaneler varsayılan ayarlarıyla çağrıldı. Dolayısıyla resiliparse'ın 0.0588 sızıntı oranı, sizden ana içeriği istediğinizde olan şey; extract_plain_text(html) tek başına ise farklı bir ürün (default-vs-main-content.json).
İlk ve ikinci sütunu birlikte okuyun; çünkü bunlardan yalnızca birine bakarsanız yanlış aracı seçersiniz.
Readability hiç kaçırmıyor. 22 fixture'ın tamamında kusursuz recall ve bu konuda tek başına. Ama bunun bedeli var: 17 şablon birimin 4'ü sızdı, 35 kirletici token geldi; bu, trafilatura'nın sızıntı oranının dört katı. Kaçaklarının dördünün üçü aynı tipte: makalenin kardeşi konumunda duran, nötr sınıflı bir promo bloğu; sibling-append sezgisi onu içeri alıyor. Çıktısını bir modele verirseniz, o token'ların ücretini ödersiniz ve model onları makale gibi okur.
newspaper4k dengeli olan. Sıfır sızıntı, sıfır kirletici token, 0.9865 recall ve 22 fixture'ın hepsinde çıktı. Elimde iş yükünü bilmeden tek bir araç seçecek olsam bunu seçerdim; üstelik çoğu kişinin aklına ilk gelen araç da bu değil.
goose3'ün precision'ı kusursuz, ama testteki recall'ı en kötü. Döndürdüğü her içerik kelimesi gerçekten makaleydi. Ama iki fixture'da hiç sonuç üretmedi ve o iki fixture'da da çıktı vermedi. Sessiz kalmaya izin veriliyorsa kusursuz precision ucuzdur.
İki kütüphaneyi olduğundan iyi gösteren precision sayısı
Bu son nokta somutlaştırılmalı; çünkü ben bunu neredeyse yayımlıyordum.
Buradaki precision ve F1, yalnızca çıktı üretildiğinde geçerli. Bir fixture'da boş string döndüren bir kütüphane, ne paya ne paydaya katkı yapar — yani geri çekilmek bedava olur ve temkinli bir ayıklayıcının precision'ı, sessiz olduğu için daha iyi görünür.
goose3'ün toplam precision'ı, çıktı verdiği 10 scored fixture üzerinde 1.0000 idi. jusText'inki 11'in 10'unda 0.8760 idi. Readability, trafilatura, resiliparse ve newspaper4k 11'de 11 yanıt verdi. Şimdi tabloda precision'ın yanına bu payda da kondu; böylece geri çekilme, cazip bir oran arkasında kaybolamıyor.
Bunun daha kötü bir versiyonu da vardı. İlk puanlayıcım, article fidelity için kullanılan 11 fixture'lık aynı sette article recall ortalaması alıyordu — yani şablon metin içermeyen fixture'lar hariç tutulmuştu; bu, sızıntıyı ölçmek için doğru olan şey. Bu yöntem resiliparse'ı 1.0000 recall ile gösteriyordu. Oysa 22 fixture'ın tamamı üzerinden bakınca resiliparse 0.9054'te kalıyor; çünkü makalesi tamamen <p> olmadan yalnızca <li> öğeleri içinde duran fixture'da çıktı veriyor ama 6 makale biriminin 0'ını geri getiriyor. O fixture'da şablon metin yoktu, bu yüzden ortalamanın dışında kaldı ve gerçek bir başarısızlık kusursuz bir skorun arkasına gizlendi.
Her biri tam olarak nerede kırılıyor
| Fixture | Ne test ediyor | Kim hiçbir şey kurtaramıyor |
|---|---|---|
Makale tamamen <li> içinde, <p> yok | yapı varsayımları | resiliparse (0/6), goose3 (çıktı yok) |
| Tek bir 129 karakterlik makale birimi | kısa içerik eşiği | jusText |
| On kısa paragraf, uzun paragraf yok | kısa içerik eşiği | jusText |
| Neredeyse boş belge | gerçek null sınırı | goose3, jusText |
Bunların her biri genel bir “ayıklama daha kötü” durumu değil, özel ve yeniden üretilebilir bir davranış:
- resiliparse ve goose3 ikisi de paragrafları varsayıyor. Gövdesi liste olan bir sayfaya — changelog, teknik döküman, SSS, tarif — baktığınızda resiliparse listenin içeriğini almadan metni geri veriyor; goose3 ise hiçbir şey vermiyor. Burada daha tehlikeli olan resiliparse, çünkü bir şey döndürmesi başarı gibi görünüyor.
- jusText'in bir uzunluk uçurumu var ve oldukça keskin. Buna birazdan daha ayrıntılı değineceğim.
- Neredeyse boş belge, hiçbir şey döndürmemenin makul sayılabileceği tek durum; bu yüzden bunu iki kütüphaneye de eksi yazmam.
jusText: eğim değil, uçurum
jusText 22 fixture'ın 19'unda çıktı verdi ve şablon metnin %47'sini sızdırdı — testteki en yüksek oran; yani itibarıyla ters düşüyor. Ama asıl ilginç sayı, her şeyi yeniden çalıştırmama neden olan sayıydı.
jusText her bloğu, dil için tanımlı stopword listesine göre stopword yoğunluğuyla sınıflandırır; ardından bağlam duyarlı bir geçiş yapıp neargood bloğu yalnızca yanında zaten good bir blok varsa good seviyesine yükseltir. Bir blok tek başına yalnızca length_high eşiğinin üstündeyse good olur; varsayılan değer 200 karakterdir. Hiçbir bloğun bu çizgiyi geçmediği bir belgede, hiçbir şey yükseltmeyi başlatamaz ve tüm sayfa şablon metne çöker.
En uzun paragrafı 151 karakter olan bir belgede bunu taradım:
length_high | Good paragraf sayısı | Dönen karakter sayısı |
|---|---|---|
| 200 (varsayılan) | 0 | 0 |
| 150 | 8 | 832 |
| 120 | 8 | 832 |
| 100 | 8 | 832 |
| 80 | 8 | 832 |
Tam bir paragraf eşiği geçince 0'dan 832 karaktere sıçrıyor; sonra ne kadar daha gevşetirseniz gevşetin hiçbir şey değişmiyor. Tek bir paragrafın çizgiyi aşması, tüm belgeyi açıyor.
Buna karar vermeden önce length_low değerini dört farklı seviyede ve max_link_density değerini iki seviyede daha taradım — sekiz kombinasyonun hepsi sıfır döndü. Bu projenin kuralı şu: negatif bir yetenek iddiası için, alanı adlandıran satıcı kaynaklı hata yoksa en az üç farklı parametre şekli denenmiş olmalı; tek bir verimsiz parametre, kütüphane hakkında sonuç değildir. Sayılar justext-length-threshold.json içinde.
Bunların hiçbiri jusText'in kötü ayıkladığını söylemiyor. Gerçek, doğal dil içeren bir sayfada varsayılan ayarlarla 1.190 karakter temiz makale metni döndürdü. Söylediği şey şu: jusText'in belgelenmiş bir ayarı var ve bu ayar kısa paragraf belgeleri için adeta bir aç/kapa anahtarı gibi davranıyor; bu anahtarın varsayılan konumu da bu tür belgeler için yanlış.
Ne kuruyorsunuz ve içe aktarma size neye mal oluyor

Aynı fixture'lar, aynı makine, her kütüphane kendi boş sanal ortamında.
| Kütüphane | Paketler | site-packages | Soğuk içe aktarma | Çıkarma p50 |
|---|---|---|---|---|
| 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 |
Bu çalıştırmada resiliparse, en düşük soğuk içe aktarma ve medyan çıkarma değerlerine sahipti: 15 ms ve 0.06 ms. Ancak eksik protokolün destekleyebileceğinden daha kesin çapraz-runtime oranlar vermek doğru olmaz; özellikle de en yavaş tekil çıkarımının 1.098 ms olduğu düşünüldüğünde. Sunucusuz boyutlandırma için bu rakamları kullanmadan önce ayrı başlatma, ilk çağrı ve kararlı durum dağılımlarına ihtiyaç var.
trafilatura ile resiliparse kalite açısından başa baş — içerik-token F1'de 0.9697'ye karşı 0.9681, sızıntı oranı ikisinde de 0.0588 — ve bu kadar küçük fark üzerinden bir kazanan ilan etmiyorum. Ama footprint tarafında yakın değiller: 21.0 MiB'e karşı 69.9 MiB, 5 paket ile 17 paket. Asıl yaptığınız takas, resiliparse'ın liste körlüğü ile trafilatura'nın üç ek önkoşulu arasında.
Kendi test ortamımda bulduğum iki hata, yayımlamadan önce
Yukarıdaki karşılaştırma neredeyse hiç gerçekleşmeyecekti; sebebi de tek bir satırdan daha değerli.
Fixture seti altı kütüphanenin ikisini göremiyordu. İlk fixture'lar her birimi benzersiz saçma token dizileriyle yazıyordu — zzart01vf64 zzart01v56i gibi — recall'ın tam eşleşme olmasını sağlayan şey de buydu. Ama bu, fixture'larda tek bir İngilizce işlev kelimesi bile olmaması demekti. Readability, trafilatura ve resiliparse DOM'dan yapısal karar veriyordu; bu yüzden etkilenmediler. goose3 ve jusText ise stopword sayarak karar veriyor; sayacak stopword yoktu: ikisi de 22 fixture'ın tamamında boş string döndürdü.
İki kütüphaneyi sıfır puanla gösteren bir tablo otoriter görünürdü ama hiçbir şey ifade etmezdi. Yazmadan önce gerçek bir sayfada kontrol ettim: goose3 1.017 karakter, jusText 1.190 karakter döndürdü. Kütüphaneler iyiydi; test ortamı onları temsil edemiyordu.
Bu yüzden fixture'lar, sentinel'ları taşıyan İngilizce nesirle yeniden kuruldu — aynı yapı, aynı sınıflar, aynı DOM konumları, aynı birim sınırları, aynı sentinel'lar; 1.568 token bire bir değiştirildi. goose3, 0'dan 22'nin 20'sine yükseldi.
Sonra yeniden kurulum kendi içinde iki şeyi bozdu ve ikisi de benim hatamdı. Bir İngilizce kelime yaklaşık altı karakterdir; zzart01vf64 ise yaklaşık on iki. Bire bir değişim, her birimi yarıya indirdi — 21.646 karakterlik birim metni 10.986'ya düştü ve en uzun birim 1.513'ten 622'ye indi. Bu, uzunluk üzerine kurulu fixture'ların amacını sessizce değiştirdi. Uzunluk uçurumuna duyarlı olan jusText, bununla birlikte 22'nin 19'undan 6'sına düştü. Eğer yarıya inmiş sürümü yayımlasaydım, jusText'in sayısı üç kat kadar yanlış olurdu ve kütüphaneyi olduğundan daha kötü gösterirdi.
İkinci hata: Her birimi tek bir ortak korpustan çekmek stopword yoğunluğunu geri getirdi ama token-level puanlamanın dayandığı özelliği bozdu. Makale ve şablon metin kelime dağarcığı birbirinden ayrı olmalı; aksi takdirde "çıkarılan token'lar şablon metin token'larıdır" hesabı the kelimesine kadar düşer. 22 fixture'ın 10'unda kelime dağarcıkları çakıştı; orijinal sürümde bu sayı sıfırdı. Çözüm, her birim için içerik kelimelerini sonekle ayırmak ve işlev kelimelerini olduğu gibi bırakmaktı — lexical kütüphanelerin sayacağı gerçek stopword'ler, puanlayıcı için de ayrık içerik sözlüğü.
Bu yüzden buradaki token-level sütunlar content_token_* diye adlandırıldı ve yayımlanmış Readability-vs-trafilatura sayılarıyla yeniden kullanılmadı. Bunlar farklı bir nicelik; yalnızca içerik kelimeleri üzerinden ölçülüyor ve birini ötekine karıştırmak yanlış olurdu.
Yeniden kurulum sırasında bir şey daha ortaya çıktı; bu benim hatam değildi: üç bağlantı yoğunluğu fixture'ında </a> etiketi bir kelimenin içinde yer alıyordu — <a href="/x">zzsibp015qlhf zzsi</a>bp015qbht — çünkü anchor, tam bir oranı tutturmak için karakter ofsetine göre konumlandırılmıştı. Render edilen metin değişmediği için orijinal puanlama bunu fark etmedi, ama öğe bazında çalışan herhangi bir ayıklayıcı, diğerlerinin tek kelime gördüğü yerde iki parça görüyor. Düzeltildi; bağlı karakter farkı da sessizce yutulmak yerine kayda geçirildi.
Kim ne kullanmalı
Bir modele besleyip token başına mı ödüyorsunuz? newspaper4k ya da goose3. İkisi de sıfır şablon metin birimi ve sıfır kirletici token sızdırdı. Her sayfada bir yanıt istiyorsanız newspaper4k; tahmindense sessizliği tercih ediyorsanız ve sayfalarınızda paragraflar varsa goose3.
Gecikmeye hassas bir Python yolunu optimize mi ediyorsunuz? resiliparse'ı karşılaştırmaya alın. Burada en düşük gözlenen içe aktarma ve medyan çıkarma değerlerine sahipti; kalite açısından trafilatura'ya da çok yakındı — ama yalnızca main_content=True ile; bu da varsayılan değil. Önce liste ağırlıklı düzenleri kontrol edin ve bu yerel zamanları birebir cross-runtime hız oranına çevirmeyin.
Arşivleme mi yapıyorsunuz, yoksa eksik içerik fazla içerikten kötüyse? Readability. Her fixture'da her makale birimini geri getiren tek araç bu; 35 stray token, bir paragrafı kaybetmekten daha ucuz bir bedel.
Çok dilli iş mi? jusText'i dahil etmek mantıklı olabilir; çünkü dil stoplist'leriyle gelir. Bu çalışma çok dilli çıkarımı test etmedi, bu yüzden bu özellik onu değerlendirmeye değer kılar; kazandığını kanıtlamaz. length_high değerini temsilî paragraf uzunluklarına göre test edin.
Makale olmayan bir şey mi? Bunların hiçbiri değil. Hepsi bir sayfanın tek bir ana düzyazı gövdesi olduğu varsayımına dayanıyor; ürün listesi, arama sonuçları sayfası ya da gösterge paneli bu varsayımı parametrelerle çözülemeyecek biçimde bozar.
Yönetilen API nereye oturuyor
Yukarıdakilerin hepsi sizin çalıştırdığınız kütüphaneler: HTML verirsiniz, metin alırsınız. Gözlenen hata modları sayfa yapısına göre değişir; bu yüzden seçtiğiniz varsayılanları kendi korpusunuzda doğrulayın. Yapılandırılmış alan çıkarımı ile fetch/render katmanı bu karşılaştırmanın dışında.
Yazar notu: Thunderbit URL-girdi akışları ve yapılandırılmış çıktı için sunduğumuz yönetilen hizmettir. Bu fixture'larda çalıştırılmadı; dolayısıyla burada kalite karşılaştırması ima edilmiyor. Asıl karar sınırı şu: Elinizde zaten HTML var ve yerel bir metin ayıklayıcı mı istiyorsunuz, yoksa fetch/render ve operasyonların servis tarafından yönetilmesini mi?
Dürüst çerçeve şu: HTML sizdeyse ve metin istiyorsanız, bu altı araçtan biri ücretsiz ve iyi; bu tablo hangisi olduğunu gösteriyor. Sayfaları büyük ölçekte çekiyorsanız veya düz yazı yerine satırlar istiyorsanız, bu bambaşka bir satın alma.
Eğer bunun yerine yönetilen fetcher'lar arasında seçim yapıyorsanız, web scraping API roundup o alanı; SEO ve veri API'lerinin maliyet karşılaştırması ise fiyatlarını anlatıyor. Self-hosted tarafta open-source scraper pillar daha geniş resmi veriyor; aslında ihtiyacınız düz metin değil de Markdown ise, Python'da HTML'yi Markdown'a dönüştürme çoğu kaybın olduğu yer.
Web Verisi Çıkarma için Thunderbit’i Deneyin
Sonuç
Bir kazanan yok; tek bir kazanan ilan eden tablo, gerçek bir takası gizliyor olurdu.
Seçim yapmadan önce küçük bir kabul korpusu oluşturun: yalnızca liste içeren makaleler, kısa paragraflar, promo kardeş bloklar, neredeyse boş bir sayfa ve boş döndürmenin kirlenmeden daha iyi olduğu örnekler ekleyin. Makale kurtarmayı, şablon metin sızıntısını ve geri çekilmeyi ayrı ayrı puanlayın. Bu fixture'larda Readability recall lehineydi, newspaper4k en dengeli satırı verdi ve resiliparse liste içeriği körlüğü olan bir gecikme adayıydı; bu etiketler test edilen yapıların dışına doğrulanmadan taşınmamalı.
Size gerçekten söyleyeceğim şey bunların hepsinden daha dar: seçmeden önce fixture'ları kendi sayfa yapılarınıza karşı çalıştırın. Başta kullandığım test ortamını altı kütüphanenin ikisi göremiyordu; içlerinden biri de toplam başarısızlığı gizleyen kusursuz recall puanı aldı. Karşılaştırma tablosu bunun başlangıcıdır, yerine geçeni değil.
Web Verisi Çıkarma için Thunderbit’i Deneyin Get Started Free
SSS
Bu rakamlar, bu kütüphaneler için yayımlanan benchmark'larla karşılaştırılabilir mi? Hayır, ve ben onları o şekilde alıntılamazdım. Bunlar, sentetik ama etiketli birimlere sahip kontrollü fixture'lar; yani altı araç da aynı baytları gördü ve aralarındaki karşılaştırma adil. scrapinghub article-extraction benchmark gibi yayımlanmış sonuçlar gerçek dünya korpusları kullanır; bu, farklı ve daha zor bir şeyi ölçer. Bu tabloyu bu altı aracı birbirine karşı kıyaslamak için kullanın, bir makaledeki sayı ile değil.
Readability ve trafilatura ikisi de DOM tabanlıyken neden Readability'nin sızıntı oranı daha yüksek? Çünkü her biri sınırı farklı yerden çiziyor. Readability'nin dört sızıntısının üçü, makalenin kardeşi olan nötr sınıflı promo bloklar; sibling-append sezgisi, bitişik ve az bağlantılı uzun içeriğin muhtemelen hikâyenin parçası olduğu varsayımıyla bunları içeri çekiyor. Çoğu zaman öyle. Ama bu fixture'larda o bir promo. trafilatura, neyi eklediği konusunda daha sıkı davranıyor ve aynı birimlerden birini sızdırdı.
goose3 ve jusText için precision sayılarına güvenmeli miyim? Sadece örnek sayısıyla birlikte. İkisi de hem makale hem şablon metin içeren 11 fixture'ın 10'unda puanlandı; çünkü birinde hiç çıktı vermediler ve çıktı olmayan bir fixture oranı iki tarafta da etkilemez. goose3'ün 1.0000 precision'ı, yanıt verdiği sayfalar için gerçek; 22 fixture'daki 0.8243 recall ise aynı gerçeğin diğer yarısı.
jusText'in uzunluk eşiği gerçek sayfalarda önemli mi?
Tamamen paragraf uzunluklarınıza bağlı. 300 karakterlik paragrafları olan bir haber makalesi ilk paragrafta length_high eşiğini aşar ve normal davranır — işte bu yüzden jusText, varsayılanlarla gerçek bir sayfada 1.190 temiz karakter döndürdü. Kısa paragraflar, liste öğeleri veya ürün özetleri olan bir sayfa ise hiç eşiği geçmeyebilir; o zaman jusText kısmi yanıt yerine boş string döndürür. Bunu üretimde öğrenmek yerine açıkça ayarlayın.
Burada ne test edilmedi? Gerçek dünya sayfalarının kendisi. jusText'in stoplist'leri ana satış noktası olmasına rağmen çok dilli çıkarım. Yük altında bellek davranışı. Makale olmayan hiçbir sayfa — ürün listeleri, arama sonuçları, gösterge panelleri yok. Kodlama uç durumları. Ve Node ile Python ekosistemleri kütüphane davranışı açısından karşılaştırıldı, runtime performansı açısından değil; dolayısıyla bu sınırdaki milisaniye rakamları kesin oran değil, büyüklük mertebesi olarak okunmalı.


