Mozilla's Readability est le portage JavaScript autonome de l’extracteur qui alimente Firefox's Reader View. Publié sous le package Apache-2.0 @mozilla/readability, il sélectionne le contenu d’un article à partir du DOM actif. Sous Node, il a donc besoin d’une implémentation DOM comme jsdom. Il ne récupère pas les pages, n’exécute pas leur JavaScript et n’effectue aucune extraction de schéma.
La version testée ici est la dernière version npm, 0.6.0, publiée le 3 mars 2025, avec 11 361 étoiles sur le dépôt au moment de ma vérification le 27 juillet 2026 (dernière mise à jour le 9 juillet 2026, donc main est nettement en avance sur le package publié). Je l’ai exécutée avec jsdom 29.1.1 sur Node v22.22.3, macOS arm64, sur 22 fixtures HTML étiquetées conçues pour ce test, et toutes les mesures présentées ici proviennent de cet environnement. À l’usage, c’est l’outil le moins exigeant de cette catégorie : deux minutes d’installation, aucun binaire, aucun navigateur à garder en cache, un résultat identique à chaque relance. Si c’est intéressant, ce n’est pas parce qu’on l’utilise — c’est parce que ses échecs sont prévisibles à partir de quelques constantes dans le code source, et l’une d’elles pèse plus lourd que la documentation ne le laisse entendre.
Sur 22 fixtures synthétiques contrôlées, Readability a retrouvé les 74 blocs d’article étiquetés. Ce résultat borné ne veut pas dire qu’il ne perd jamais de texte d’article : le benchmark public sur des pages réelles affiche un rappel de 0,982, et certains cas connus ne se sont pas reproduits ici. L’échec le plus visible sur fixture venait d’un contenu voisin supplémentaire accepté par une règle de densité de liens au seuil source de 0,25. Sa gravité apparente varie selon le banc d’essai.
Ce qu’est vraiment readability.js — et trois choses qu’il n’est pas
Readability est un passage de scoring fondé sur des règles appliqué à un DOM. Il parcourt les éléments candidats, attribue à chacun un score de contenu, propage ces scores vers les ancêtres, choisit le sous-arbre le mieux noté, puis applique des passes de nettoyage pour retirer ce qui ressemble à des éléments de décor de page. Toute la stratégie d’extraction d’article est là — aucun modèle, aucune phase d’entraînement, aucune règle par site. C’est pour cela qu’il fonctionne sur une page qu’il n’a jamais vue — et pourquoi ses échecs sont prévisibles à partir du code source, ce qui est la partie amusante.
Trois choses qu’il n’est pas, et qui piègent souvent les gens :
- Pas un récupérateur. Il prend un
document, pas une URL. La récupération, les retries, l’anti-bot et les en-têtes sont votre problème. - Pas un moteur de rendu. Aucun JavaScript n’est exécuté. Il voit simplement ce que contient le DOM que vous lui fournissez.
- Pas un extracteur structuré. Vous obtenez
title,byline,excerpt,content(HTML),textContent,length,siteName. Pas de schéma, pas de lignes typées, pas de{name, price}.
Quatre constantes font l’essentiel du travail

Lire Readability.js dans node_modules en dit plus sur le comportement que n’importe quelle page de documentation. Quatre mécanismes expliquent la majeure partie de ce que fait la bibliothèque :
- Score de contenu par paragraphe scoré :
1 + (commaCount + 1) + min(floor(len / 100), 3). Les paragraphes de moins de 25 caractères ne sont pas comptés du tout. Les scores sont propagés vers les ancêtres avec des diviseurs — le parent reçoit le score complet, le grand-parent la moitié, et les ancêtres plus profondslevel · 3. DEFAULT_CHAR_THRESHOLD = 500— la longueur minimale d’article pour qu’un parsing soit considéré comme « réussi ». En dessous, un second passage de récupération se relance avec moins d’étapes de nettoyage.- L’expression régulière
unlikelyCandidates— elle matche des sous-chaînes de class et d’id commecomment,footer,menu,related,sidebar,social,sponsor. Les nœuds correspondants sont écartés avant le scoring. - La règle d’ajout des frères dans
grabArticle— après sélection du meilleur candidat, ses frères sont examinés pour une éventuelle inclusion. Un frère est conservé si son score dépasse le seuil, ou sinodeLength > 80 && linkDensity < 0.25, ou sinodeLength < 80 && nodeLength > 0 && linkDensity === 0 && il contient un point.
La densité de liens est Σ(linkText.length · coef) / textLength, avec coef = 0.3 pour les href bruts # et 1 sinon. Cette règle explique les fuites de contenus voisins mesurées ici ; ce n’est pas l’algorithme complet d’extraction.
Mise en place, et la dépendance qui n’apparaît pas dans le pitch
npm install @mozilla/readability jsdom et vous êtes prêt. Deux minutes, aucun binaire, aucun téléchargement post-installation, aucun navigateur à laisser en cache. Sur l’axe de l’installation, l’outil est presque idéal.
Mais l’expression « zéro dépendance » parle de l’algorithme, pas du runtime. Readability fonctionne sur un document vivant, et sous Node cela signifie que vous fournissez vous-même une implémentation DOM — ici jsdom, en 29.1.1. jsdom n’est pas léger, et dans la plupart des pipelines c’est lui qui coûte le plus dans la boucle, pas l’extraction. Il faut le prévoir.
Un autre piège qui m’a coûté une relance : Readability.parse() modifie le DOM qu’on lui donne. Si vous parsez deux fois le même document jsdom, le second appel voit un document déjà démonté par le premier. Chaque parse de mon harness reconstruit donc un jsdom frais. Si vous bouclez sur des pages en réutilisant un objet document pour gagner du temps, c’est probablement le bug que vous allez finir par ouvrir.
Comment je l’ai testé
Je ne l’ai pas branché sur des sites d’actualité en production. Les pages réelles donnent un score sans permettre de voir pourquoi — et avec une heuristique, le « pourquoi » est toute la valeur. J’ai donc généré 22 fixtures HTML contenant 91 blocs étiquetés (74 article, 17 boilerplate), où chaque mot d’un bloc est préfixé par la chaîne sentinelle unique de ce bloc. Les vocabulaires entre blocs sont disjoints, de sorte qu’un token extrait se rattache à un seul bloc, et qu’un bloc est « récupéré » ou « fuité » via un test d’appartenance exact plutôt qu’une correspondance floue.
L’étape d’extraction et l’étape de scoring sont volontairement séparées. Le runner Node n’émet que le texte extrait brut, les booléens isProbablyReaderable et les densités de liens mesurées. Toute la précision et tout le rappel sont calculés ensuite à partir de ce texte brut, à l’aide d’un script séparé. Aucun constant de métrique n’est écrit à la main dans le harness, et c’est la seule façon dont je fais confiance à mes chiffres.
Ensuite, j’ai envoyé les mêmes octets à trafilatura 2.1.0 pour une comparaison sur le même banc d’essai. Chaque fixture a été parsée trois fois ; les 22 ont renvoyé à chaque passage un texte strictement identique au niveau des octets.
Vous pouvez inspecter chaque étape au lieu de croire le tableau récapitulatif. tests/build_fixtures.mjs génère le HTML annoté et la vérité terrain ; tests/run_readability.mjs enregistre l’extraction et la sortie du prédicteur ; et tests/metrics.py note ces résultats a posteriori. Les sorties brutes de Readability, les métriques calculées et la comparaison à entrée identique sont conservées dans artifacts/raw/. Cette séparation compte quand un résultat paraît suspect : on peut savoir si le parseur a renvoyé un texte inattendu, si l’étiquetage était mauvais, ou si le code de scoring a mal classé le résultat. La reproduction sur ce pack vérifie les affirmations faites ici, mais cela reste une vérification de harness — pas une preuve que le mix de pages réelles d’un déploiement aura la même distribution d’échecs.
La limite de portée est réelle et importante : il s’agit de pages synthétiques contrôlées, pas d’un corpus réel. Les chiffres de référence sur pages réelles proviennent du benchmark public article-extraction-benchmark, qui attribue à readability_js 0.6.0 — exactement la version testée ici — un word-F1 de 0.947 ± 0.005 (précision 0.914 ± 0.008, rappel 0.982 ± 0.003) sur environ 181 pages réelles. Je cite cela ; je ne l’ai pas reproduit.
Les lignes du benchmark de la version courante sont utilisées ici ; les lignes historiques obsolètes ont été exclues. Les fixtures contrôlées ajoutent une décomposition par bloc montrant quelle forme de contenu déclenche quelle règle, au lieu de remplacer le corpus public de pages réelles.
Le rappel était parfait dans le pack de fixtures synthétiques
74 sur 74. Sur l’ensemble des 22 fixtures, Readability n’a perdu aucun bloc d’article étiqueté — et sur les onze fixtures synthétiques propres mélangeant article et boilerplate, le rappel micro-averaged sur les tokens est monté à 1.000. Pas une seule phrase d’article n’a disparu.
Deux réserves s’appliquent à cela :
Ce sont des pages synthétiques propres, à colonne unique. Les vrais articles ont des structures plus profondes, intercalent parfois des publicités au milieu du corps de texte, et perdent parfois leur premier paragraphe à cause d’un artefact de scoring — cette classe d’échec est signalée dans le tracker (#437, #901, et les pertes de contenu avant un tableau dans #922). Mes fixtures n’ont déclenché aucun de ces cas, donc je ne prétends pas qu’ils soient corrigés — je dis seulement que mon test ne les a pas atteints. Sur des pages réelles, le rappel du benchmark pour cette version est de 0,982, pas 1.000.
Le sens du résultat reste néanmoins l’information utile. Le problème de Readability n’est pas de jeter votre article. C’est ce qu’il emporte avec lui.
Le chiffre de précision, et pourquoi il a besoin de trois libellés

Un seul chiffre est facile à citer et difficile à défendre. Sur les onze fixtures mixtes, Readability a conservé 5 blocs boilerplate sur 17 — soit un taux de fuite de 0,294.
Ce n’est pas un taux de fuite réel en production. Trois configurations différentes mesurent trois choses différentes, et une seule d’entre elles décrit des pages ordinaires :
| Ce que mesure le chiffre | Résultat |
|---|---|
| Jeu de fixtures pondéré de façon adversariale — 6 des 11 pages mixtes ont été conçues pour contourner la règle des frères | 5 blocs boilerplate conservés sur 17 (0,294) |
La seule page réaliste — corps en <article> entouré d’une navigation, d’une bannière pub, d’une sidebar, de commentaires et d’un footer, plus un teaser neutre sur le plan des classes | 5 blocs de décor sur 6 supprimés ; 1 conservé |
| ~181 pages réelles, benchmark public (pas mon test) | précision 0,914, rappel 0,982, word-F1 0,947 — readability_js 0.6.0 |
Lisez la première ligne comme un test de résistance, pas comme une prévision. Readability ne fuit pas 29 % du boilerplate dans la vraie vie. Sur la page réaliste, tout ce qui portait une class reconnue par l’expression régulière unlikelyCandidates — nav-menu, ad-banner, sidebar, comments, site-footer — a été supprimé proprement, les cinq blocs. Le seul survivant était celui que j’avais conçu pour contourner cette regex.
La règle des 0,25 : là où le nettoyage du boilerplate s’arrête
La règle d’ajout des frères est documentée dans le code source. Ce que personne n’avait, à ma connaissance, mesuré précisément, c’est l’endroit exact où elle bascule. J’ai donc construit une pente : un <p class="teaser-block"> sans classe explicite, placé hors de <article>, un article décisif de quatre paragraphes garanti gagnant comme meilleur candidat, et rien d’autre que la longueur et la densité de liens du teaser comme variables. Les densités sont calculées avec la formule propre à Readability, mesurées à l’exécution plutôt qu’aux hypothèses :
| Bloc teaser | Longueur du texte interne | Plus de 80 caractères | Densité de liens mesurée | Résultat |
|---|---|---|---|---|
| Aucun lien | 126 | oui | 0.000 | conservé |
| Un lien court | 126 | oui | 0.143 | conservé |
| Un lien plus long | 126 | oui | 0.278 | supprimé |
| La moitié du texte liée | 126 | oui | 0.476 | supprimé |
| Une seule phrase, se terminant par un point | 60 | non | 0.000 | conservé |
| Même texte, sans point | 59 | non | 0.000 | supprimé |
La condition source utilise un seuil de 0,25 ; les échantillons mesurés l’encadrent, avec 0,143 conservé et 0,278 supprimé. Une autre branche a gardé une phrase de 60 caractères terminée par un point et supprimé une version de 59 caractères sans point. Le rappel sur l’article est resté à 4/4 dans chaque branche, donc ces échantillons isolent un effet de précision.
En dehors d’un harness de test, cette règle revient à dire en substance : une prose neutre, longue, avec peu de liens, placée à côté de l’article, est de l’article. Cela décrit beaucoup de choses qui ne sont pas l’article — un encadré de « lecture associée » rédigé comme un paragraphe, un pitch newsletter, une note de rédaction, un teaser sponsorisé rédigé en phrases complètes avec le lien retiré pour des raisons de tracking.
Dans un index RAG, un paragraphe promotionnel à faible densité de liens peut devenir un chunk extrait et créer un risque que la recherche ou la génération le traite comme du contenu d’article. La règle source rend ce mode d’échec plausible ; cette analyse n’a pas évalué de bout en bout la récupération ni les citations générées par un modèle.
Pour un filtre dur propre à un site, pré-filtrez les conteneurs DOM source connus, conservez l’ascendance des nœuds source pour comparaison avant sérialisation, ou appliquez ensuite un filtrage textuel soigneusement validé. Le HTML renvoyé seul ne permet plus forcément de savoir si un nœud se trouvait à l’extérieur du conteneur principal. La règle des frères n’est pas réglable via les options publiques.
Trois hypothèses contredites par les fixtures
Les fixtures ont contredit trois hypothèses : que charThreshold rejette les articles courts, que les balises sémantiques sont nécessaires, et que les contenus non narratifs courts sont supprimés. Les éléments ci-dessous sont ceux qui comptent ; aucune revendication de pré-enregistrement n’est nécessaire.
charThreshold = 500 n’est pas un précipice
La lecture populaire veut qu’un article de moins de 500 caractères renvoie null. Ce n’est pas le cas. J’ai fait varier la longueur du corps de 120 à 1500 caractères avec des valeurs de charThreshold de 200, 500 et 1000 :
| Longueur du corps | Parsing réussi à tous les seuils | Longueur extraite |
|---|---|---|
| 120 | oui | 161 |
| 300 | oui | 342 |
| 460 | oui | 509 |
| 520 | oui | 569 |
| 800 | oui | 841 |
| 1500 | oui | 1555 |
Plat. Longueur extraite identique sur les trois réglages de seuil, quelle que soit la taille du corps. Le seuil ne bloque pas la valeur de retour — il décide seulement si la récupération doit être relancée avec les drapeaux de nettoyage retirés, et sur une page propre il n’y a rien à retirer, donc le tamis renvoie le même contenu dans tous les cas. La vraie frontière du null, c’est « aucun texte exploitable du tout ».
Ce qui produit ici l’échec réel, et c’est plus vicieux qu’un faux null. Je lui ai donné une page quasi vide — une barre de navigation et un petit texte de quatre mots. Il a renvoyé quelque chose, et l’« article » renvoyé incluait la navigation. En l’absence d’un vrai article, Readability vous remet du boilerplate étiqueté comme article. Si vous crawllez à grande échelle et traitez un résultat non nul comme « cette page avait du contenu », cette hypothèse est fausse.
Les balises sémantiques ne font pas le travail à elles seules
Je m’attendais à une baisse de rappel en retirant la structure. Même texte d’article, deux habillages : l’un avec <main><article><h1> et des noms de classes parlants, l’autre avec <div class="x1"> et des paragraphes en simples <div>. Résultat : 4 blocs article sur 4 récupérés dans les deux cas, zéro boilerplate conservé dans les deux cas. Quand l’article est clairement le bloc de texte le plus dense de la page, le scoring longueur-et-virgules le trouve sans aucune aide sémantique. Dire que « Readability a besoin de balises <article> » relève du folklore.
La vraie limite de cette affirmation : ma page contenait un seul bloc de contenu évident. Là où les sémantiques pourraient réellement servir, c’est sur une page avec deux sous-arbres denses en concurrence, et je n’ai pas testé ce cas de départage.
Le contenu non narratif survit intact
La règle « les paragraphes de moins de 25 caractères ne sont pas comptés » m’a fait m’attendre à des pertes sur les tableaux et les légendes. Encore faux — cette règle affecte le scoring des candidats, pas la conservation. Une fois que le conteneur gagne, tout ce qu’il contient l’accompagne :
| Type de contenu dans l’article | Readability | trafilatura |
|---|---|---|
| Paragraphes de prose (×2) | conservés | conservés |
| Cellules de tableau de données (×2) | conservées | conservées |
Bloc de code <pre> | conservé | conservé |
Ligne <p> d’une seule ligne de moins de 25 caractères (×2) | conservée | conservée |
<figcaption> | conservé | supprimé |
| Total | 8/8 | 7/8 |
Voilà un axe sur lequel un nettoyeur plus brutal perd. Si vos pages sont de la documentation, des tutoriels ou tout autre format avec blocs de code et figures légendées, le comportement « garder tout le sous-arbre gagnant » de Readability est un atout.
isProbablyReaderable dit non quand parse() dit oui

Le README suggère d’appeler isProbablyReaderable(doc) comme vérification préliminaire rapide avant de lancer un parse complet. Dans mes tests, cette porte a rejeté trois formes de pages que parse() a ensuite traitées correctement :
| Forme de page | Verdict du prédicteur | parse() | Quel levier corrige cela |
|---|---|---|---|
Contenu uniquement dans des éléments <li> | false | réussit | aucun — faux à tous les minScore de 1 à 80 et à tous les minContentLength de 40 à 200 |
| Dix paragraphes, chacun de moins de 140 caractères | false | réussit | minContentLength ≤ 100 (minScore ne change rien) |
| Un seul paragraphe de 408 caractères | false | réussit | minScore ≤ 10 (le score est ≈16,4) |
| Article normal (contrôle) | true | réussit | — |
Les trois échecs ont trois causes différentes, et seulement deux sont réglables. Le cas <li> est structurel : le prédicteur ne score que les nœuds p, pre et article (ainsi que les parents de div > br), donc une page dont le contenu vit dans des items de liste ne matche rien, obtient un score nul, et aucun réglage de seuil ne permet de la récupérer — une forme déjà signalée dans issue #662. Le cas des paragraphes courts est une porte minContentLength qui écarte chaque paragraphe avant le scoring, donc dix paragraphes pourtant substantiels ne totalisent rien ; baisser cette valeur corrige le problème, ajuster minScore ne change rien. Le cas du paragraphe unique est purement arithmétique : le score est sqrt(408 − 140) ≈ 16,4, en dessous du minScore par défaut de 20 — un paragraphe isolé doit atteindre 140 + 20² = 540 caractères pour franchir seul la barre.
Le README avertit bien que le prédicteur produit des faux négatifs. Ce que j’ajouterais, c’est la règle pratique : ne l’utilisez pas comme unique gardien. Si une page compte, parsez-la puis vérifiez la longueur du résultat. Le parse n’est pas si coûteux par rapport à la construction de jsdom que vous avez déjà payée.
Même octets, deux extracteurs
Exécuter trafilatura 2.1.0 sur les mêmes fixtures donne une lecture plus propre que d’aligner deux nombres mesurés sur deux bancs d’essai différents, puisque l’entrée est identique octet par octet :
| Mesure (11 fixtures mixtes) | @mozilla/readability | trafilatura |
|---|---|---|
| Rappel des blocs article | 1.000 | 1.000 |
| Blocs boilerplate conservés | 5/17 (0.294) | 1/17 (0.059) |
| Token F1 (micro) | 0.948 | 0.969 |
| Rappel du non-prose | 8/8 | 7/8 |
| Article très court (120 caractères), token F1 | 0.800 | 0.571 |
Aucun des deux ne domine ces fixtures. Trafilatura a conservé moins de blocs voisins, tandis que Readability a mieux retenu les contenus courts et non narratifs. La précision absolue des tokens des deux outils est tirée vers le bas par des titres non étiquetés, donc le comptage des fuites au niveau bloc est le signal direct le plus clair. Le benchmark public sur pages réelles les ordonne à peu près de la même façon en word-F1, mais les corpus et les métriques diffèrent et cela ne constitue pas une validation croisée.
Robustesse, rapidement : j’ai exécuté une variante volontairement mal formée de la page canonique (<p> non fermé, <b>/<i> imbriqués de travers, </div> en trop) et le rappel était 3/3 avec zéro fuite, identique à la version bien formée. Le mérite revient ici à jsdom, dont le constructeur d’arbre HTML5 répare le désordre avant même que Readability ne le voie. Aucune fixture n’a fait planter le parseur.
Avantages et inconvénients
Avantages
- Le rappel sur les articles est son point fort : 74/74 blocs étiquetés récupérés sur 22 fixtures synthétiques, rappel token 1.000 sur l’ensemble mixte.
- Le décor de page classé par regex est supprimé de manière fiable — navigation, bannière pub, sidebar, commentaires et footer ont tous disparu sur la page réaliste (5 sur 6).
- Le contenu non narratif est intégralement conservé : tableaux, code
<pre>, légendes de figures et lignes de moins de 25 caractères ont tous survécu (8/8), là où trafilatura a supprimé une légende. - Aucune dépendance au balisage sémantique — un article en
<div>neutre a obtenu le même score que la version en<article>/<main>. - Les articles courts ne sont pas rejetés à tort : contenu propre récupéré jusqu’à 120 caractères, résultats identiques avec
charThreshold200/500/1000. - Totalement déterministe : les 22 fixtures ont renvoyé exactement le même texte sur trois exécutions.
- Installation en deux minutes, licence Apache-2.0, et la version sur npm est bien celle que j’ai testée (0.6.0), donc rien ici n’est obsolète.
Inconvénients
- La règle d’ajout des frères est exploitable : une prose promotionnelle longue, peu liée et sans classe marquée est indiscernable du texte d’article et se retrouve incluse dès que
linkDensity < 0.25. - Sur les pages pauvres en contenu, il renvoie du boilerplate comme article plutôt que
null— la fixture quasi vide est revenue avec sa barre de navigation comme corps. isProbablyReaderableproduit des faux négatifs sur trois formes de pages différentes, dont une qu’aucun réglage ne corrige.- Nécessite un DOM complet au runtime — le discours « zéro dépendance » masque le coût de jsdom, qui domine la boucle.
parse()modifie le document d’entrée, il faut donc reconstruire le DOM pour chaque page.- Pas de récupération, pas de rendu JavaScript, pas de sortie structurée. C’est une étape d’un pipeline, pas le pipeline.
- Les échecs sur pages réelles signalés dans le tracker (pertes du premier paragraphe et avant tableau) ne se sont pas reproduits sur mes fixtures, donc je ne peux pas vous dire s’ils sont rares ou si mes pages n’ont simplement jamais atteint ces cas.
Qui devrait l’utiliser, et qui devrait passer son chemin
Utilisez Readability si vous avez déjà le HTML sous la main et voulez en extraire l’article en JavaScript pur, dans un service Node où ajouter une dépendance Python serait pénible. (Je n’ai pas collecté des timings comme une vraie distribution, donc je ne fais aucune promesse de vitesse au-delà de « la construction jsdom domine la boucle, pas l’extraction ».) Les fonctionnalités de mode lecture, l’archivage d’articles hors ligne, les newsletters email, les boutons de « vue propre », les extensions de navigateur, les pipelines de documentation avec blocs de code et légendes — c’est son terrain, et les chiffres de rappel montrent qu’il s’y débrouille bien. Son comportement est aussi lisible directement dans le code source, ce qui vaut plus qu’il n’y paraît quand il faut expliquer à un collègue pourquoi un bloc précis est passé.
Passez votre chemin si la précision de suppression du boilerplate est votre critère principal, surtout si vous alimentez un index pour LLM où un paragraphe promo oublié devient un chunk récupérable. Passez votre chemin si vos pages rendent le contenu côté client, car il lit simplement le DOM qu’on lui donne et n’exécute pas JavaScript. Passez votre chemin si vous avez besoin de {title, price, sku} plutôt que de prose — aucune configuration ne transforme un extracteur de contenu en extracteur piloté par schéma. Et si vous traitez des pages où la vraie question est « y avait-il un article sur cette page ? », ne faites pas confiance à une réponse non nulle.
Alternatives, et où se situe la stack Thunderbit
Rien ici n’est une critique d’une bibliothèque gratuite Apache-2.0 maintenue par Mozilla — Readability fait partie de l’infrastructure, il est embarqué dans Firefox depuis des années, et pour l’extraction en mode lecture c’est la référence pour une bonne raison. Si vous voulez voir le champ plus large, je tiens une comparaison continue dans le récapitulatif des scrapers open source et une étude plus générale dans les meilleurs outils de web scraping.
Pour les mêmes fixtures sur les six extracteurs, voir la comparaison des six bibliothèques d’extraction d’articles.
Note de l’auteur : Thunderbit est notre option managée pour le rendu et l’extraction à partir d’une URL. Il n’a pas été exécuté sur ces fixtures, donc aucune affirmation de qualité équivalente n’est sous-entendue. La vraie frontière est de savoir si vous avez déjà un DOM et souhaitez une extraction locale d’article, ou si vous voulez la récupération/le rendu et une sortie structurée opérés comme un service. L’auto-hébergement évite des frais d’usage éditeur, mais conserve les coûts d’infrastructure et de maintenance.
Le compromis honnête : Readability est gratuit, transparent et à vous — vous pouvez lire la règle exacte qui a décidé votre sortie, ce qu’une API managée ne permet pas. Une stack managée coûte de l’argent et cache le mécanisme, mais elle couvre les étapes récupération-rendu-structure que vous devriez sinon assembler vous-même. Si vous voulez le versant assisté par IA de ce spectre, j’ai aussi écrit sur scraper n’importe quel site avec l’IA et sur les crawlers IA. Choisissez selon les étapes que vous voulez réellement posséder.
Essayer Thunderbit pour l’extraction de données web
Verdict
Readability est un bon candidat si vous avez déjà un DOM, que vous travaillez en JavaScript/Node, et que vous préférez parfois un peu de contenu voisin en trop à des omissions agressives. Dans ce pack de fixtures, il a récupéré les 74 blocs d’article étiquetés et conservé tableaux, code et légendes. Ce résultat est limité à des pages synthétiques à colonne unique ; le rappel public sur pages réelles est de 0,982, les pertes connues près du début de l’article ou des tableaux ne se sont pas reproduites, et les pages pauvres en contenu peuvent renvoyer du boilerplate comme article.
Il faut simplement bien dimensionner sa faiblesse. Son principal point de rupture dans ces fixtures est la précision, et il se situe à une ligne source précise et documentée : un frère de plus de 80 caractères avec une densité de liens inférieure à 0,25 est ajouté à votre article, qu’il en fasse partie ou non. J’ai vu ce basculement entre 0,143 et 0,278 sur un texte identique. Le benchmark sur pages réelles indique une précision de 0,914 et un rappel de 0,982. Si vous injectez le texte extrait dans un index qu’un modèle citera plus tard, inspectez à la fois le boilerplate conservé et le corps omis au lieu de supposer qu’une des deux catégories d’erreur est absente.
Essayer Thunderbit pour l’extraction de données web Get Started Free
FAQ
Mozilla Readability supprime-t-il tout le boilerplate ?
Non, et le chiffre dépend énormément de ce que vous mesurez. Sur le benchmark public de pages réelles, readability_js 0.6.0 obtient une précision de 0,914 — donc environ 8,6 % de ce qu’il renvoie n’est pas du corps d’article. Sur une page de test réaliste que j’ai construite, il a supprimé 5 des 6 blocs de décor (navigation, bannière pub, sidebar, commentaires, footer tous partis), ne gardant qu’un paragraphe promotionnel sans classe marquée. Sur un jeu de fixtures que j’ai volontairement pondéré avec des blocs conçus pour tromper l’heuristique, il en a gardé 5 sur 17 — ce dernier chiffre est un test de stress, pas un taux réel en production.
Ai-je besoin de jsdom pour utiliser readability.js dans Node ?
Oui, ou une autre implémentation DOM. Readability est en JavaScript pur, mais il fonctionne sur un objet document vivant, donc sous Node vous fournissez vous-même le DOM — jsdom 29.1.1 dans mon environnement. La description « sans dépendances » concerne l’algorithme, pas le runtime. Notez aussi que parse() modifie le document qui lui est donné, donc reconstruisez un DOM propre pour chaque page au lieu d’en réutiliser un seul.
À quoi sert vraiment l’option charThreshold ?
Pas à ce que beaucoup imaginent. Elle ne fait pas qu’un article court renvoie null — j’ai récupéré des articles propres jusqu’à 120 caractères, avec une longueur extraite identique pour charThreshold à 200, 500 et 1000. Le seuil contrôle simplement si le parseur relance sa récupération sans certains drapeaux de nettoyage ; sur une page propre, il n’y a rien à enlever, donc la sortie reste la même. Le vrai cas null, c’est une page sans texte exploitable du tout, et même une page avec seulement la navigation est revenue non nulle, avec la nav comme article.
Dois-je appeler isProbablyReaderable avant parse() ?
Utilisez-le comme indice, pas comme verrou. Il a renvoyé false sur trois formes de pages où parse() a ensuite réussi : contenu dans des éléments <li>, dix paragraphes de moins de 140 caractères chacun, et un seul paragraphe de 408 caractères. Le cas <li> ne se corrige pas par réglage, parce que le prédicteur ne score que les nœuds p, pre et article ; le cas des paragraphes courts nécessite de baisser minContentLength ; le cas du paragraphe unique exige de baisser minScore, puisqu’un seul paragraphe doit atteindre 540 caractères pour dépasser la barre par défaut. Si une page compte, parsez-la et vérifiez le résultat.
Readability ou trafilatura pour l’extraction d’articles ?
Sur des octets identiques de fixture, trafilatura a conservé moins de boilerplate (1/17 blocs contre 5/17), tandis que Readability récupérait mieux le contenu court et conservait un <figcaption> que trafilatura supprimait. Choisissez selon votre tolérance à l’erreur et votre contrainte de runtime. Le benchmark public est un contexte séparé, pas une validation de ce résultat de fixture.


