Mozilla's Readability adalah port JavaScript mandiri dari extractor yang jadi mesin di balik Firefox's Reader View. Dirilis sebagai paket Apache-2.0 @mozilla/readability, alat ini mengambil konten artikel langsung dari DOM yang sedang aktif. Di Node, artinya Anda perlu implementasi DOM seperti jsdom. Alat ini tidak melakukan fetch halaman, tidak menjalankan JavaScript di dalamnya, dan tidak melakukan ekstraksi skema.
Versi yang diuji adalah npm latest di 0.6.0, dirilis pada 3 Maret 2025, dengan 11.361 bintang di repo saat saya cek pada 27 Juli 2026 (terakhir di-push 9 Juli 2026, jadi main sudah jauh lebih maju daripada paket yang dipublikasikan). Saya menjalankannya dengan jsdom 29.1.1 di Node v22.22.3, macOS arm64, pada 22 fixture HTML berlabel yang saya buat khusus untuk pengujian ini, dan semua angka di sini berasal dari setup tersebut. Dari sisi praktik, ini adalah alat yang paling ringan di kategori ini: dua menit untuk instal, tanpa binary, tanpa browser yang diam di cache, dan output-nya identik setiap kali rerun. Yang menarik bukan cara pakainya — melainkan kegagalannya yang bisa diprediksi dari beberapa konstanta di source, dan salah satunya ternyata lebih menentukan daripada yang dijelaskan dokumentasi.
Dari 22 fixture sintetis terkontrol, Readability berhasil mengambil kembali semua 74 blok artikel berlabel. Hasil yang terbatas ini bukan berarti ia tidak pernah kehilangan teks artikel: benchmark publik pada halaman nyata melaporkan recall 0,982, dan pola masalah yang sudah dikenal tidak muncul di sini. Kegagalan fixture yang paling kelihatan adalah konten sibling tambahan yang lolos lewat gate link-density level source di 0,25. Seberapa besar efeknya sangat bergantung pada testbed.
Sebenarnya apa itu readability.js, dan tiga hal yang bukan dia
Readability adalah langkah scoring berbasis aturan di atas DOM. Ia menelusuri elemen kandidat, memberi skor konten pada tiap elemen, meneruskan skor itu ke ancestor, memilih subtree dengan skor tertinggi, lalu menjalankan pembersihan untuk menghapus hal-hal yang terlihat seperti elemen dekoratif halaman. Itulah seluruh strategi ekstraksi artikelnya — tanpa model, tanpa data latih, tanpa aturan khusus per situs. Desain inilah yang membuatnya bisa bekerja pada halaman yang belum pernah dilihat — dan juga membuat kegagalannya bisa diprediksi dari source, bagian yang justru paling menarik.
Tiga hal yang bukan dia, dan tiga hal ini sering bikin orang kejebak:
- Bukan fetcher. Ia menerima
document, bukan URL. Fetching, retry, anti-bot, dan header adalah urusan Anda. - Bukan renderer. Tidak ada eksekusi JavaScript. Apa pun yang ada di DOM yang Anda berikan itulah yang dilihatnya.
- Bukan extractor terstruktur. Anda hanya mendapat
title,byline,excerpt,content(HTML),textContent,length,siteName. Tidak ada schema, tidak ada baris bertipe, tidak ada{name, price}.
Empat konstanta yang mengerjakan sebagian besar pekerjaan

Membaca Readability.js di node_modules memberi tahu Anda lebih banyak soal perilaku daripada halaman dokumentasi mana pun. Ada empat komponen utama yang menjelaskan sebagian besar perilaku library ini:
- Content score per paragraf yang dinilai:
1 + (commaCount + 1) + min(floor(len / 100), 3). Paragraf di bawah 25 karakter tidak dihitung sama sekali. Skor diteruskan ke ancestor dengan pembagi — parent mendapat skor penuh, grandparent setengah, dan ancestor yang lebih dalamlevel · 3. DEFAULT_CHAR_THRESHOLD = 500— panjang artikel minimum agar parse dianggap "berhasil". Di bawah itu, sieve penghapus flag dijalankan ulang dengan lebih sedikit langkah pembersihan.- Regex
unlikelyCandidates— mencocokkan substring class dan id seperticomment,footer,menu,related,sidebar,social,sponsor. Node yang cocok dibuang sebelum scoring. - Gate penambahan sibling di
grabArticle— setelah kandidat utama dipilih, sibling-nya dipertimbangkan untuk ikut disertakan. Sibling akan ikut jika skornya melewati ambang, ataunodeLength > 80 && linkDensity < 0.25, ataunodeLength < 80 && nodeLength > 0 && linkDensity === 0 && mengandung titik.
Link density adalah Σ(linkText.length · coef) / textLength, dengan coef = 0,3 untuk href # polos dan 1 untuk yang lain. Gate inilah yang menjelaskan kebocoran sibling yang saya ukur di sini; itu bukan keseluruhan algoritma ekstraksi.
Setup, dan dependency yang tidak disebut di pitch
npm install @mozilla/readability jsdom dan Anda langsung bisa jalan. Dua menit, tanpa binary, tanpa unduhan pasca-instal, tanpa browser yang menumpuk di cache. Dari sisi instalasi, ini nyaris ideal.
Namun klaim "zero dependencies" itu merujuk ke algoritma, bukan runtime. Readability bekerja pada document yang aktif, dan di Node artinya Anda sendiri yang menyediakan implementasi DOM — pada kasus ini jsdom 29.1.1. jsdom bukan komponen kecil, dan untuk banyak pipeline justru itulah biaya terbesar di loop, bukan ekstraksinya. Siapkan budget untuk itu.
Satu footgun lagi yang membuat saya harus rerun: Readability.parse() memodifikasi DOM yang diberikan. Jika Anda parse document jsdom yang sama dua kali, pemanggilan kedua akan melihat document yang sudah diacak-acak oleh pemanggilan pertama. Setiap parse di harness saya selalu membangun jsdom baru. Kalau Anda sedang looping halaman dan sengaja memakai ulang object document demi hemat waktu, itulah bug yang sebentar lagi akan Anda laporkan.
Cara saya mengujinya
Saya tidak mengarahkannya ke situs berita live. Halaman live memberi Anda skor tanpa cara melihat kenapa hasilnya begitu — padahal untuk heuristic, bagian "kenapa" justru inti nilainya. Sebagai gantinya, saya membuat 22 fixture HTML berisi 91 blok berlabel (74 artikel, 17 boilerplate), di mana setiap kata dalam sebuah blok diawali dengan string sentinel unik milik blok tersebut. Kosakata antarblok dibuat saling lepas, jadi token yang diekstrak hanya bisa mengarah ke satu blok, dan status "pulih" atau "bocor" menjadi tes keanggotaan yang tepat, bukan pencocokan samar.
Langkah ekstraksi dan langkah scoring sengaja dipisahkan. Runner Node hanya mengeluarkan teks mentah hasil ekstraksi, boolean isProbablyReaderable, dan link density terukur. Semua precision dan recall dihitung setelahnya dari teks mentah itu terhadap label, menggunakan script terpisah. Tidak ada konstanta metrik yang ditulis manual di mana pun di harness ini, dan itulah satu-satunya cara saya percaya pada angka saya sendiri.
Lalu saya masukkan bytes yang identik ke trafilatura 2.1.0 untuk pembanding pada testbed yang sama. Setiap fixture diparse tiga kali; semua 22 menghasilkan teks yang byte-identical di setiap run.
Anda bisa memeriksa tiap tahap, bukan cuma percaya pada tabel ringkasan. tests/build_fixtures.mjs membuat HTML beranotasi dan ground truth; tests/run_readability.mjs mencatat hasil ekstraksi dan output predictor; dan tests/metrics.py memberi skor pada record tersebut setelah fakta. Output mentah Readability, metrik yang dihitung, dan perbandingan dengan input yang sama disimpan di artifacts/raw/. Pemisahan ini penting saat hasil terlihat mencurigakan: Anda bisa tahu apakah parser mengembalikan teks tak terduga, label set-nya salah, atau kode scoring salah mengklasifikasikannya. Reproduksi pada paket ini memeriksa klaim di sini, tetapi tetap merupakan pengecekan harness — bukan bukti bahwa campuran halaman nyata di deployment punya distribusi kegagalan yang sama.
Batas cakupannya nyata dan penting: ini adalah halaman sintetis terkontrol, bukan korpus dunia nyata. Angka resmi untuk halaman nyata berasal dari article-extraction-benchmark publik, yang memberi skor readability_js 0.6.0 — versi persis yang diuji di sini — sebesar word-F1 0,947 ± 0,005 (precision 0,914 ± 0,008, recall 0,982 ± 0,003) pada sekitar 181 halaman nyata. Itu saya kutip; bukan saya reproduksi.
Baris benchmark untuk rilis terkini yang dipakai di sini; baris historis yang sudah digantikan tidak disertakan. Fixture terkontrol menambahkan dekomposisi per blok yang menunjukkan pola konten mana memicu aturan mana, alih-alih menggantikan korpus halaman nyata publik.
Recall sempurna di paket fixture sintetis
74 dari 74. Di seluruh 22 fixture, Readability tidak kehilangan satu pun blok artikel berlabel — dan pada sebelas fixture sintetis bersih yang mencampur artikel dan boilerplate, micro-averaged token recall mencapai 1,000. Tak satu pun kalimat artikel hilang.
Ada dua catatan untuk itu:
Ini adalah halaman sintetis yang bersih dan satu kolom. Artikel nyata lebih bertingkat, sering menyelipkan iklan di tengah body, dan kadang kehilangan paragraf pembuka karena artefak scoring — kelas miss seperti itu tercatat di tracker (#437, #901, serta konten sebelum tabel yang hilang di #922). Fixture saya tidak memicu satupun di antaranya, jadi saya tidak mengklaim bug itu sudah diperbaiki — saya hanya mengatakan pengujian saya tidak sampai ke sana. Di halaman nyata, recall benchmark untuk versi ini adalah 0,982, bukan 1,000.
Tetap saja, arah hasilnya adalah bagian yang penting. Masalah Readability bukan karena ia membuang artikel Anda. Yang bermasalah adalah apa yang ikut dibawanya.
Angka precision, dan mengapa butuh tiga label

Satu angka mudah dikutip, tapi sulit dipertahankan. Pada sebelas fixture campuran, Readability menyimpan 5 dari 17 blok boilerplate — tingkat kebocoran 0,294.
Itu bukan tingkat kebocoran dunia nyata. Tiga setup berbeda mengukur tiga hal berbeda, dan hanya satu yang benar-benar menggambarkan halaman biasa:
| Apa yang diukur | Hasil |
|---|---|
| Set fixture berbobot adversarial — 6 dari 11 halaman campuran dibuat khusus untuk menaklukkan sibling gate | 5 dari 17 blok boilerplate dipertahankan (0,294) |
Satu halaman realistis — body <article> dikelilingi nav, banner iklan, sidebar, komentar, footer, plus satu promo yang class-nya netral | 5 dari 6 blok chrome dibuang; 1 dipertahankan |
| ~181 halaman nyata, benchmark publik (bukan run saya) | precision 0,914, recall 0,982, word-F1 0,947 — readability_js 0.6.0 |
Baca baris pertama sebagai stress test, bukan ramalan. Readability tidak bocor 29% boilerplate di dunia nyata. Pada halaman yang realistis, semua yang membawa class yang cocok dengan regex unlikelyCandidates — nav-menu, ad-banner, sidebar, comments, site-footer — dibersihkan dengan rapi, semuanya lima. Satu-satunya yang lolos adalah blok yang saya rancang untuk menghindari regex tersebut.
Ambang 0,25: tempat pembersihan boilerplate berhenti
Aturan sibling-append terdokumentasi di source. Setahu saya, belum ada yang mengukur tepat di titik mana ia berbalik. Jadi saya membuat gradien: satu <p class="teaser-block"> yang class-nya netral di luar <article>, artikel empat paragraf yang jelas-jelas menang sebagai kandidat utama, dan hanya panjang serta link density promo yang saya variasikan. Density dihitung dengan formula milik Readability sendiri, diukur saat runtime, bukan diasumsikan:
| Blok promo | Panjang inner-text | Di atas 80 karakter | Link density terukur | Hasil |
|---|---|---|---|---|
| Tanpa link sama sekali | 126 | ya | 0,000 | dipertahankan |
| Satu link pendek | 126 | ya | 0,143 | dipertahankan |
| Satu link lebih panjang | 126 | ya | 0,278 | dibuang |
| Separuh teks berupa link | 126 | ya | 0,476 | dibuang |
| Satu kalimat, diakhiri titik | 60 | tidak | 0,000 | dipertahankan |
| Teks sama, tanpa titik | 59 | tidak | 0,000 | dibuang |
Kondisi source memakai ambang 0,25; sampel terukur mengapitnya, dengan 0,143 dipertahankan dan 0,278 dibuang. Cabang terpisah mempertahankan kalimat 60 karakter yang diakhiri titik dan membuang versi 59 karakter tanpa titik. Recall artikel tetap 4/4 di tiap arm, jadi sampel ini mengisolasi efek precision.
Di luar harness pengujian, gate ini pada dasarnya bilang: prosa panjang, minim link, netral, yang berdampingan dengan artikel adalah bagian dari artikel. Padahal itu juga menggambarkan banyak hal yang bukan artikel — blurb "Related reading" yang ditulis seperti paragraf, pitch newsletter, catatan editor, atau teaser bersponsor yang ditulis tim marketing dalam kalimat penuh lalu link-nya dihapus demi tracking.
Dalam indeks RAG, paragraf promo yang minim link bisa menjadi chunk hasil ekstraksi dan membuat retrieval atau generation memperlakukannya sebagai isi artikel. Aturan source membuat mode kegagalan itu masuk akal; review ini tidak menjalankan evaluasi retrieval end-to-end atau evaluasi kutipan model.
Untuk hard filter spesifik situs, lakukan pre-filter pada container DOM sumber yang sudah diketahui, pertahankan ancestry node sumber untuk perbandingan sebelum serialisasi, atau terapkan filtering pola teks yang sudah divalidasi dengan hati-hati setelahnya. HTML hasil keluaran saja mungkin tidak lagi mempertahankan apakah suatu node awalnya berada di luar container utama. Sibling gate ini tidak bisa disetel lewat opsi publik.
Tiga asumsi yang dipatahkan fixture
Fixture membantah tiga asumsi: bahwa charThreshold menolak artikel pendek, bahwa semantic tag diperlukan, dan bahwa konten pendek non-prosa akan dibuang. Bukti di bawah adalah bagian yang relevan; tidak perlu ada klaim preregistrasi.
charThreshold = 500 bukan jurang pemisah
Pemahaman umum mengatakan artikel di bawah 500 karakter akan mengembalikan null. Ternyata tidak. Saya menyapu panjang body dari 120 sampai 1500 karakter terhadap nilai charThreshold 200, 500, dan 1000:
| Panjang body | Parse berhasil di semua threshold | Panjang hasil ekstraksi |
|---|---|---|
| 120 | ya | 161 |
| 300 | ya | 342 |
| 460 | ya | 509 |
| 520 | ya | 569 |
| 800 | ya | 841 |
| 1500 | ya | 1555 |
Datar. Panjang hasil ekstraksi identik di ketiga pengaturan threshold, pada semua ukuran body. Threshold bukan penentu nilai return — ia hanya menentukan apakah grab dijalankan ulang dengan flag pembersihan yang dihapus, dan pada halaman bersih tidak ada yang perlu dibuang, jadi hasilnya tetap sama. Batas null yang sebenarnya adalah "tidak ada teks yang bisa diekstrak sama sekali."
Dan itu justru memunculkan kegagalan nyata di sini, yang lebih buruk daripada false null. Saya memasukkannya ke halaman hampir kosong — sebuah nav bar dan blurb empat kata. Hasilnya sukses, dan "artikel" yang dikembalikan ikut memuat nav. Kalau tidak ada artikel sungguhan, Readability akan memberi Anda boilerplate yang diberi label sebagai artikel. Jika Anda melakukan crawling skala besar dan menganggap hasil non-null sebagai "halaman ini punya konten," asumsi itu salah.
Semantic tag tidak memegang peranan utama
Saya menduga recall akan turun ketika kerangkanya saya lepaskan. Isi artikel sama, dua tampilan: satu dengan <main><article><h1> dan class yang deskriptif, satu lagi dengan <div class="x1"> dan paragraf sebagai <div> polos. Hasilnya: 4 dari 4 blok artikel dipulihkan di keduanya, dan tidak ada boilerplate yang bocor di keduanya. Saat artikel jelas merupakan blok teks paling padat di halaman, scoring berbasis panjang dan jumlah koma menemukannya tanpa bantuan semantik apa pun. Anggapan bahwa "Readability butuh tag <article>" hanyalah folklore.
Batas jujur dari klaim itu: halaman saya hanya punya satu blok konten yang jelas. Di mana semantik mungkin berguna adalah halaman dengan dua subtree padat yang saling bersaing, dan saya tidak menguji tie-break itu.
Konten non-prosa tetap utuh
Aturan "paragraf di bawah 25 karakter tidak dihitung" membuat saya mengira tabel dan caption akan banyak hilang. Ternyata salah lagi — aturan itu memengaruhi scoring kandidat, bukan retensi. Begitu container menang, semua isi di dalamnya ikut terbawa:
| Jenis konten di dalam artikel | Readability | trafilatura |
|---|---|---|
| Paragraf prosa (×2) | dipertahankan | dipertahankan |
| Sel tabel data (×2) | dipertahankan | dipertahankan |
Blok kode <pre> | dipertahankan | dipertahankan |
Baris satu kalimat di bawah 25 karakter <p> (×2) | dipertahankan | dipertahankan |
<figcaption> | dipertahankan | dibuang |
| Total | 8/8 | 7/8 |
Ini salah satu dimensi di mana cleaner yang lebih keras justru kalah. Jika halaman Anda berisi dokumentasi, tutorial, atau apa pun dengan code block dan figure berketerangan, perilaku Readability yang mempertahankan seluruh subtree pemenang adalah sebuah fitur.
isProbablyReaderable bilang tidak saat parse() bilang ya

README menyarankan memanggil isProbablyReaderable(doc) sebagai cek pra-pemrosesan yang murah sebelum masuk ke parse penuh. Dalam pengujian saya, gate ini menolak tiga bentuk halaman berbeda yang kemudian diproses parse() tanpa masalah:
| Bentuk halaman | Keputusan predictor | parse() | Tuas mana yang memperbaikinya |
|---|---|---|---|
Konten hanya ada di elemen <li> | false | berhasil | tidak ada — tetap false pada setiap minScore 1–80 dan setiap minContentLength 40–200 |
| Sepuluh paragraf, masing-masing di bawah 140 karakter | false | berhasil | minContentLength ≤ 100 (minScore tidak berpengaruh) |
| Satu paragraf 408 karakter | false | berhasil | minScore ≤ 10 (skornya ≈16,4) |
| Artikel normal (kontrol) | true | berhasil | — |
Tiga kegagalan itu punya tiga sebab berbeda, dan hanya dua yang bisa disetel. Kasus <li> bersifat struktural: predictor hanya memberi skor pada node p, pre, dan article (plus parent div > br), jadi halaman yang kontennya hidup di list item tidak cocok dengan apa pun, skornya nol, dan tidak ada tuning ambang yang bisa memulihkannya — pola yang sudah dilaporkan di issue #662. Kasus banyak paragraf pendek adalah gate minContentLength yang melewatkan tiap paragraf sebelum scoring, jadi sepuluh paragraf yang substansial tetap menjumlah menjadi nol; menurunkan nilai itu memperbaiki hasil, sedangkan menyesuaikan minScore tidak membantu. Kasus satu paragraf adalah aritmetika: skornya sqrt(408 − 140) ≈ 16,4, masih di bawah minScore default 20 — sebuah paragraf tunggal butuh 540 karakter (140 + 20²) agar bisa lolos sendirian.
README memang memperingatkan bahwa predictor bisa menghasilkan false negative. Yang saya tambahkan adalah aturan praktisnya: jangan jadikan itu satu-satunya penjaga gerbang. Jika halaman penting, parse saja lalu cek panjang hasilnya. Parse tidak semahal itu dibanding biaya membangun jsdom yang sudah Anda bayar.
Bytes yang sama, dua extractor
Menjalankan trafilatura 2.1.0 pada fixture yang identik memberi pembacaan yang lebih bersih daripada menaruh dua angka dari dua testbed berbeda berdampingan, karena input-nya sama persis byte demi byte:
| Ukuran (11 fixture campuran) | @mozilla/readability | trafilatura |
|---|---|---|
| Recall blok artikel | 1,000 | 1,000 |
| Blok boilerplate yang dipertahankan | 5/17 (0,294) | 1/17 (0,059) |
| Token F1 (micro) | 0,948 | 0,969 |
| Recall non-prosa | 8/8 | 7/8 |
| Artikel sangat pendek (120 karakter), token F1 | 0,800 | 0,571 |
Tidak ada yang benar-benar mendominasi fixture ini. Trafilatura mempertahankan lebih sedikit sibling block, sementara Readability lebih baik dalam mempertahankan konten pendek dan non-prosa. Precision token absolut kedua alat sedikit tertekan oleh teks heading yang tidak dilabeli, jadi jumlah kebocoran di tingkat blok adalah sinyal langsung yang lebih bersih. Benchmark halaman nyata publik kebetulan memberi urutan word-F1 yang mirip, tetapi korpus dan metriknya berbeda dan ini bukan validasi lintas testbed.
Soal ketangguhan, singkat saja: saya menjalankan versi kembar yang sengaja dibuat cacat dari halaman kanonik ( <p> tak ditutup, <b>/<i> yang nesting-nya salah, dan </div> nyasar ) dan recall tetap 3/3 tanpa kebocoran, sama seperti versi yang valid. Kredit di sini milik jsdom, yang membangun ulang tree HTML5 dan memperbaiki kekacauan sebelum Readability melihatnya. Tidak ada fixture yang membuat parser crash.
Kelebihan dan kekurangan
Kelebihan
- Recall artikel adalah sisi terkuat: 74/74 blok berlabel dipulihkan di 22 fixture sintetis, token recall 1,000 pada set campuran.
- Elemen halaman yang cocok dengan regex dibersihkan dengan andal — nav, banner iklan, sidebar, komentar, dan footer semuanya hilang pada halaman realistis (5 dari 6).
- Konten non-prosa dipertahankan sepenuhnya: tabel, kode
<pre>, caption gambar, dan baris di bawah 25 karakter semuanya lolos (8/8), sementara trafilatura membuang satu caption. - Tidak bergantung pada markup semantik — artikel
<div>yang dinetralkan mendapat skor identik dengan versi<article>/<main>. - Artikel pendek tidak salah ditolak: konten bersih berhasil dipulihkan sampai 120 karakter, identik di
charThreshold200/500/1000. - Sepenuhnya deterministik: semua 22 fixture menghasilkan teks identik di tiga run.
- Instalasi dua menit, Apache-2.0, dan versi di npm sama dengan yang saya uji (0.6.0), jadi tidak ada yang sudah usang.
Kekurangan
- Gate sibling-append bisa dieksploitasi: prosa promo panjang, minim link, dan class netral tidak bisa dibedakan dari teks artikel dan ikut terbawa saat
linkDensity < 0,25. - Pada halaman miskin konten, ia mengembalikan boilerplate sebagai artikel, bukan
null— fixture hampir kosong saya kembali dengan nav bar sebagai body. isProbablyReaderablemenghasilkan false negative pada tiga bentuk halaman, dan salah satunya tidak bisa diperbaiki dengan tuning apa pun.- Butuh DOM penuh saat runtime — framing "tanpa dependency" menyembunyikan biaya jsdom, yang justru mendominasi loop.
parse()memodifikasi document input, jadi Anda harus membangun ulang DOM per halaman.- Tidak ada fetching, tidak ada rendering JavaScript, tidak ada output terstruktur. Ini satu tahap dalam pipeline, bukan seluruh pipeline.
- Miss pada halaman nyata yang dilaporkan di tracker (paragraf pembuka dan sebelum-tabel) tidak muncul di fixture saya, jadi saya tidak bisa bilang apakah itu kasus langka atau hanya halaman saya yang tidak pernah mencapai kondisi itu.
Siapa yang cocok memakainya, dan siapa yang sebaiknya skip
Pilih Readability jika Anda sudah memegang HTML dan ingin mengekstrak artikelnya murni dalam JavaScript, di dalam service Node, saat menambah dependency Python terasa merepotkan. (Saya tidak mengumpulkan timing sebagai distribusi yang proper, jadi saya tidak membuat klaim kecepatan selain "build jsdom yang mendominasi loop, bukan ekstraksinya.") Fitur mode baca, arsip artikel offline, newsletter email, tombol "clean view", ekstensi browser, pipeline dokumentasi dengan code block dan caption — itu wilayahnya, dan angka recall menunjukkan ini pilihan yang bagus. Perilakunya juga bisa dibaca langsung dari source, yang nilainya ternyata besar ketika Anda harus menjelaskan ke rekan kerja kenapa satu blok tertentu ikut terbawa.
Skip jika precision pembersihan boilerplate adalah metrik yang Anda dinilai, apalagi jika Anda memasukkan hasilnya ke indeks LLM di mana paragraf promo nyasar bisa menjadi chunk yang bisa diambil kembali. Skip jika halaman Anda merender konten di sisi klien, karena ia membaca DOM apa pun yang Anda berikan dan tidak menjalankan JavaScript. Skip jika yang Anda butuhkan adalah {title, price, sku} alih-alih prosa — tidak ada konfigurasi yang bisa mengubah content extractor menjadi schema-driven. Dan jika Anda memproses halaman di mana pertanyaan "apakah halaman ini punya artikel sama sekali" benar-benar penting, jangan anggap return non-null sebagai jawaban.
Alternatif, dan posisi Thunderbit dalam stack ini
Ini bukan serangan terhadap library Apache-2.0 gratis yang dipelihara Mozilla — Readability adalah infrastruktur, sudah lama tertanam di Firefox, dan untuk ekstraksi mode baca ia memang referensi yang wajar. Kalau Anda ingin lihat lanskap yang lebih luas, saya punya perbandingan berkelanjutan di open-source scrapers roundup dan survei yang lebih lebar di the best web scraping tools.
Untuk fixture yang sama di keenam extractor, lihat perbandingan ekstraksi enam library.
Catatan penulis: Thunderbit adalah opsi managed kami untuk rendering dan ekstraksi berbasis URL. Alat ini tidak dijalankan pada fixture ini, jadi tidak ada klaim kualitas yang harus dicocokkan. Batas yang relevan adalah apakah Anda sudah punya DOM dan ingin ekstraksi artikel lokal, atau ingin fetching/rendering dan output terstruktur dijalankan sebagai layanan. Self-hosting menghindari biaya pemakaian vendor, tetapi tetap membawa biaya infrastruktur dan maintenance.
Trade-off yang jujur: Readability gratis, transparan, dan sepenuhnya Anda yang menjalankan — Anda bisa membaca gate persis yang memutuskan output Anda, sesuatu yang tidak diberikan API managed. Stack managed memang berbayar dan menyembunyikan mekanismenya, tetapi ia menutupi tahap fetch-render-structure yang sebaliknya harus Anda rakit sendiri. Kalau Anda ingin sisi yang dibantu AI dari spektrum itu, saya pernah menulis tentang scraping website apa pun dengan AI dan tentang AI crawler di tempat lain. Pilih berdasarkan tahap mana yang benar-benar ingin Anda miliki.
Coba Thunderbit untuk Ekstraksi Data Web
Kesimpulan
Readability layak dipilih ketika Anda sudah punya DOM, memakai JavaScript/Node, dan lebih suka konten sibling tambahan sesekali daripada omisi yang agresif. Dalam paket fixture ini ia memulihkan semua 74 blok artikel berlabel dan mempertahankan tabel, kode, dan caption. Hasil itu dibatasi oleh halaman sintetis satu kolom; recall pada halaman nyata publik adalah 0,982, miss yang sudah dikenal di sekitar pembuka/tabel tidak terulang, dan halaman miskin konten bisa mengembalikan boilerplate sebagai artikel.
Yang penting adalah mengukur kelemahannya dengan benar. Titik gagal utamanya pada fixture ini ada di precision, dan lokasinya spesifik, bahkan tertulis di source: sibling yang lebih dari 80 karakter dengan link density di bawah 0,25 akan ikut ditambahkan ke artikel Anda, apakah itu bagian darinya atau tidak. Saya melihat ambang itu bergeser antara 0,143 dan 0,278 pada teks yang sama. Benchmark halaman nyata melaporkan precision 0,914 dan recall 0,982. Kalau Anda menyalurkan teks hasil ekstraksi ke indeks yang nantinya akan dikutip model, periksa baik boilerplate yang ikut terbawa maupun body yang hilang, bukan menganggap salah satu kelas error itu tidak ada.
Coba Thunderbit untuk Ekstraksi Data Web Get Started Free
FAQ
Apakah Mozilla Readability menghapus semua boilerplate?
Tidak, dan angkanya sangat bergantung pada apa yang Anda ukur. Pada benchmark publik halaman nyata, readability_js 0.6.0 mencetak precision 0,914 — jadi sekitar 8,6% dari yang dikembalikan bukan body content. Pada halaman uji realistis milik saya, alat ini membersihkan 5 dari 6 blok chrome (nav, iklan, sidebar, komentar, footer semuanya hilang), dan hanya menyisakan satu paragraf promo ber-class netral. Pada set fixture yang sengaja saya bobot dengan blok yang dirancang untuk menaklukkan heuristic, ia mempertahankan 5 dari 17 — angka terakhir itu adalah stress test, bukan tingkat dunia nyata.
Apakah saya perlu jsdom untuk memakai readability.js di Node?
Ya, atau implementasi DOM lain. Readability murni JavaScript, tetapi ia bekerja pada object document yang aktif, jadi di Node Anda harus menyediakan DOM sendiri — pada setup saya, itu jsdom 29.1.1. Deskripsi "tanpa dependency" merujuk ke algoritmanya, bukan runtime-nya. Perlu juga dicatat bahwa parse() memodifikasi document yang diberikan, jadi bangun DOM baru untuk setiap halaman, jangan pakai ulang yang lama.
Apa sebenarnya fungsi opsi charThreshold?
Bukan seperti yang kebanyakan orang kira. Opsi ini bukan membuat artikel pendek menjadi null — saya berhasil memulihkan artikel bersih hingga 120 karakter, dengan panjang hasil ekstraksi yang identik pada charThreshold 200, 500, dan 1000. Threshold ini mengatur apakah parser akan menjalankan ulang grab dengan flag pembersihan dihapus; pada halaman bersih tidak ada yang perlu dihapus, jadi hasilnya tetap sama. Kasus null yang benar-benar terjadi adalah halaman tanpa teks yang bisa diekstrak sama sekali, dan bahkan halaman hanya-nav tetap tidak null, melainkan mengembalikan nav sebagai artikel.
Haruskah saya memanggil isProbablyReaderable sebelum parse()?
Pakai sebagai petunjuk, bukan gerbang utama. Ia mengembalikan false pada tiga bentuk halaman yang kemudian sukses diparse: konten di elemen <li>, sepuluh paragraf masing-masing di bawah 140 karakter, dan satu paragraf 408 karakter. Kasus <li> tidak bisa diperbaiki dengan tuning karena predictor hanya memberi skor pada node p, pre, dan article; kasus banyak paragraf pendek butuh minContentLength yang lebih rendah; kasus satu paragraf panjang butuh minScore yang lebih rendah, karena satu paragraf tunggal harus mencapai 540 karakter untuk melewati default. Jika halaman penting, parse saja lalu cek hasilnya.
Readability atau trafilatura untuk ekstraksi artikel?
Pada bytes fixture yang identik, trafilatura mempertahankan lebih sedikit boilerplate (1/17 blok versus 5/17), sementara Readability memulihkan lebih banyak konten pendek dan mempertahankan <figcaption> yang dibuang trafilatura. Pilih berdasarkan toleransi error dan runtime. Benchmark publik adalah konteks terpisah, bukan validasi atas hasil fixture ini.


