Mozilla Readability Review: Recall perfecto, filtraciones de contenido vecino y falsos negativos del predictor

Última actualización: August 17, 2026
Mozilla Readability Review: Recall perfecto, filtraciones de contenido vecino y falsos negativos del predictor
Resumen con IA
Mozilla's Readability es la versión independiente en JavaScript del extractor detrás de Reader View de Firefox. Publicado como el paquete Apache-2.0 @mozilla/readability, selecciona contenido de artículos a partir de un DOM en vivo. En Node, por tanto, necesita una implementación de DOM como jsdom. No descarga páginas, no ejecuta su JavaScript ni realiza extracción de esquema. La versión evaluada es la de npm latest en 0.6.0, publicada el 3 de marzo de 2025, con 11,361 estrellas en el repositorio cuando la revisé el 27 de julio de 2026 (la última actualización fue el 9 de julio de 2026, así que main va bastante por delante del paquete publicado).

Mozilla's Readability es la versión independiente en JavaScript del extractor que da vida a Reader View de Firefox. Se publica como el paquete Apache-2.0 @mozilla/readability, y toma el contenido de un artículo a partir de un DOM en vivo. En Node, por eso mismo, necesita una implementación de DOM como jsdom. No descarga páginas, no ejecuta JavaScript ni hace extracción de esquema.

La versión evaluada es la de npm latest en 0.6.0, publicada el 3 de marzo de 2025, con 11,361 estrellas en el repositorio cuando la revisé el 27 de julio de 2026 (la última actualización fue el 9 de julio de 2026, así que main va bastante por delante del paquete publicado). Lo ejecuté con jsdom 29.1.1 sobre Node v22.22.3, en macOS arm64, contra 22 fixtures HTML etiquetados creados para este propósito, y todas las mediciones de este texto salen de ese entorno. En la práctica, es la herramienta menos exigente de esta categoría: dos minutos de instalación, sin binarios, sin navegador en caché, y una salida idéntica en cada ejecución. Lo interesante no es usarla, sino que sus fallos se pueden anticipar a partir de unas pocas constantes del código fuente, y una de esas constantes pesa más de lo que deja ver la documentación.

En 22 fixtures sintéticos controlados, Readability recuperó los 74 bloques de artículo etiquetados. Ese resultado acotado no significa que nunca pierda texto: el benchmark público sobre páginas reales reporta un recall de 0.982, y aquí no se reprodujeron ciertos patrones de fallo conocidos. La pérdida más visible fue contenido de hermanos adicionales admitido por una regla de densidad de enlaces en el código fuente fijada en 0.25. Su gravedad aparente cambia según el entorno de prueba.

Qué es realmente readability.js, y tres cosas que no es

Readability es un paso de puntuación basado en reglas sobre un DOM. Recorre elementos candidatos, asigna a cada uno una puntuación de contenido, propaga esas puntuaciones a los ancestros, elige el subárbol con mayor puntuación y luego aplica limpiezas para quitar lo que parece estructura de página. Esa es toda la estrategia de extracción de artículos: sin modelo, sin datos de entrenamiento, sin reglas por sitio. Ese diseño explica por qué funciona en páginas que nunca ha visto —y también por qué sus fallos son predecibles desde el código fuente, que es justamente lo interesante.

Tres cosas que no es, y las tres suelen confundir a la gente:

  • No es un fetcher. Recibe un document, no una URL. La descarga, los reintentos, la anti-bot y las cabeceras corren por tu cuenta.
  • No es un renderizador. No ejecuta JavaScript. Lo único que ve es el DOM que le entregas.
  • No es un extractor estructurado. Obtienes title, byline, excerpt, content (HTML), textContent, length, siteName. Nada de esquema, nada de filas tipadas, nada de {name, price}.

Cuatro constantes hacen la mayor parte del trabajo

System diagram: Four constants do most of the work

Leer Readability.js en node_modules te dice más sobre su comportamiento que cualquier página de documentación. Cuatro piezas de mecánica explican la mayor parte de lo que hace la librería:

  • Puntuación de contenido por párrafo evaluado: 1 + (commaCount + 1) + min(floor(len / 100), 3). Los párrafos de menos de 25 caracteres no se cuentan. Las puntuaciones se propagan a los ancestros con divisores: el padre recibe la puntuación completa, el abuelo la mitad y los ancestros más profundos level · 3.
  • DEFAULT_CHAR_THRESHOLD = 500 — la longitud mínima del artículo para que un análisis se considere “exitoso”. Si queda por debajo, se vuelve a ejecutar la extracción con menos pasos de limpieza.
  • La regex de unlikelyCandidates — coincide con fragmentos de class e id como comment, footer, menu, related, sidebar, social, sponsor. Los nodos que coinciden se descartan antes de puntuar.
  • La regla de anexado de hermanos en grabArticle — una vez elegido el candidato principal, se consideran sus hermanos para incluirlos. Un hermano se arrastra si su propia puntuación supera el umbral, o nodeLength > 80 && linkDensity < 0.25, o nodeLength < 80 && nodeLength > 0 && linkDensity === 0 && contiene un punto.

La densidad de enlaces es Σ(linkText.length · coef) / textLength, con coef = 0.3 para hrefs desnudos # y 1 en caso contrario. Esa regla explica las filtraciones de hermanos medidas aquí; no es todo el algoritmo de extracción.

Configuración, y la dependencia que no aparece en la promesa

npm install @mozilla/readability jsdom y ya está funcionando. Dos minutos, sin binarios, sin descargas post-instalación, sin navegador ocupando caché. En el eje de instalación, esto está muy cerca de ser ideal.

Pero “sin dependencias” es una afirmación sobre el algoritmo, no sobre el runtime. Readability opera sobre un document vivo, y en Node eso significa que tú aportas la implementación de DOM —aquí jsdom, en la versión 29.1.1. jsdom no es pequeño, y para la mayoría de pipelines es el coste dominante del bucle, no la extracción. Hay que tenerlo en cuenta.

Un detalle más que me obligó a repetir una ejecución: Readability.parse() modifica el DOM que recibe. Si analizas dos veces el mismo documento de jsdom, la segunda llamada ya ve un documento que la primera ha desarmado. Cada parseo de mi harness crea un jsdom nuevo. Si estás recorriendo páginas y reutilizando el mismo objeto document para ahorrar tiempo, ese es el bug que vas a terminar reportando.

Cómo lo probé

No lo apunté a sitios reales de noticias. Las páginas reales te dan una puntuación sin explicarte por qué; y en un heurístico, el “por qué” es justo lo valioso. En su lugar generé 22 fixtures HTML con 91 bloques etiquetados (74 de artículo, 17 de boilerplate), donde cada palabra de un bloque va prefijada por la cadena centinela única de ese bloque. Los vocabularios entre bloques son disjuntos, así que cada token extraído se puede atribuir a un único bloque, y “recuperado” o “filtrado” se convierte en una prueba exacta de pertenencia, no en una coincidencia difusa.

La extracción y la evaluación se mantienen deliberadamente separadas. El runner de Node emite solo el texto extraído en bruto, los booleanos de isProbablyReaderable y las densidades de enlace medidas. Luego, una vez terminado todo, un script separado calcula precisión y recall a partir de ese texto bruto y las etiquetas. No hay ninguna constante de métrica escrita a mano en el harness, que es la única forma en que confío en mis propias cifras.

Después alimenté los mismos bytes a trafilatura 2.1.0 para comparar en el mismo entorno. Cada fixture se analizó tres veces; los 22 devolvieron texto idéntico a nivel de bytes en cada ejecución.

Puedes revisar cada etapa en lugar de confiar en la tabla resumen. tests/build_fixtures.mjs crea el HTML anotado y la verdad de referencia; tests/run_readability.mjs registra la extracción y la salida del predictor; y tests/metrics.py puntúa esos registros después. La salida en bruto de Readability, las métricas calculadas y la comparación con la misma entrada se conservan en artifacts/raw/. Esa separación importa cuando un resultado parece sospechoso: así puedes saber si el parser devolvió texto inesperado, si el conjunto de etiquetas era incorrecto o si el código de evaluación lo clasificó mal. Reproducir este pack verifica las afirmaciones de aquí, pero sigue siendo una comprobación del harness, no evidencia de que un despliegue real tenga la misma distribución de fallos.

El límite de alcance es real y relevante: estas son páginas sintéticas controladas, no un corpus real. Las cifras autorizadas sobre páginas reales vienen del público article-extraction-benchmark, que evalúa readability_js 0.6.0 —la misma versión probada aquí— en word-F1 0.947 ± 0.005 (precision 0.914 ± 0.008, recall 0.982 ± 0.003) sobre unas 181 páginas reales. Cito eso; no lo reproduje.

Aquí se usan filas del benchmark de la versión actual; se excluyeron filas históricas ya superadas. Los fixtures controlados añaden una descomposición por bloque que muestra qué forma de contenido activa qué regla, en vez de sustituir el corpus real público.

El recall fue perfecto en el pack sintético de fixtures

74 de 74. En los 22 fixtures, Readability no perdió ni un solo bloque de artículo etiquetado —y en los once fixtures sintéticos limpios que mezclaban artículo y boilerplate, el recall micropromediado de tokens quedó en 1.000. Ni una frase del artículo desapareció.

A eso le acompaña una doble advertencia:

Son páginas sintéticas limpias de una sola columna. Los artículos reales tienen más profundidad, intercalan anuncios en mitad del cuerpo y a veces pierden el primer párrafo por un artefacto de puntuación; esa clase de fallo aparece en el tracker (#437, #901 y pérdidas de contenido antes de una tabla en #922). Mis fixtures no activaron ninguno, así que no estoy diciendo que estén corregidos —estoy diciendo que mi prueba no llegó a ellos. En páginas reales, el recall del benchmark para esta versión es 0.982, no 1.000.

Aun así, la dirección del resultado es lo útil. El problema de Readability no es que te tire el artículo. Es lo que se lleva con él.

La cifra de precisión, y por qué necesita tres etiquetas

Measured results chart: Three scopes behind the precision story

Un solo número es fácil de citar y difícil de defender. En los once fixtures mixtos, Readability conservó 5 de 17 bloques de boilerplate — una tasa de filtración de 0.294.

Eso no es una tasa real de filtración en producción. Tres configuraciones distintas miden tres cosas distintas, y solo una describe páginas normales:

Qué está midiendo la cifraResultado
Conjunto de fixtures ponderado de forma adversarial — 6 de las 11 páginas mixtas se construyeron para derrotar la regla de hermanos5 de 17 bloques de boilerplate conservados (0.294)
La única página realista — cuerpo de <article> rodeado de nav, banner de anuncios, sidebar, comentarios, footer y una promo con clase neutral5 de 6 bloques de interfaz eliminados; 1 conservado
~181 páginas reales, benchmark público (no mi ejecución)precision 0.914, recall 0.982, word-F1 0.947readability_js 0.6.0

Lee la primera fila como una prueba de estrés, no como una predicción. Readability no filtra un 29% del boilerplate en el mundo real. En la página realista, todo lo que llevaba una clase que la regex unlikelyCandidates reconoce —nav-menu, ad-banner, sidebar, comments, site-footer— se eliminó correctamente, los cinco. El único superviviente fue el bloque que diseñé para esquivar esa regex.

La regla 0.25: dónde se detiene la eliminación de boilerplate

La regla de anexado de hermanos está documentada en el código fuente. Lo que nadie había medido, que yo haya encontrado, es el punto exacto en que cambia de comportamiento. Así que construí una gradiente: un <p class="teaser-block"> con clase neutral fuera del <article>, un artículo decisivo de cuatro párrafos garantizado como candidato principal, y solo variando la longitud y la densidad de enlaces de la promo. Las densidades se calculan con la fórmula propia de Readability, medidas en tiempo de ejecución y no asumidas:

Bloque promoLongitud del texto interno¿Más de 80 caracteres?Densidad de enlace medidaResultado
Sin enlaces1260.000conservado
Un enlace corto1260.143conservado
Un enlace más largo1260.278eliminado
Mitad del texto enlazado1260.476eliminado
Una sola frase, termina en punto60no0.000conservado
El mismo texto, sin punto59no0.000eliminado

La condición del código usa un umbral de 0.25; las muestras medidas lo acotaron, con 0.143 conservado y 0.278 eliminado. Una rama separada mantuvo una frase de 60 caracteres que terminaba en punto y descartó la versión de 59 caracteres sin ese punto. El recall del artículo se mantuvo en 4/4 en cada rama, así que estas muestras aíslan un efecto de precisión.

Fuera de un harness de pruebas, esa regla viene a decir: la prosa larga, con pocos enlaces y clase neutral junto al artículo es artículo. Y eso describe muchas cosas que no son el artículo: un texto de “lecturas relacionadas” escrito como párrafo, una propuesta de newsletter, una nota del editor, un teaser patrocinado redactado en frases completas con el enlace eliminado por motivos de tracking.

En un índice RAG, un párrafo promocional con baja densidad de enlaces puede convertirse en un fragmento extraído y crear el riesgo de que la recuperación o la generación lo trate como contenido del artículo. La regla del código hace plausible ese modo de fallo; esta revisión no ejecutó una evaluación de recuperación o de citas por parte de un modelo de extremo a extremo.

Para un filtro duro específico de un sitio, prefiltra los contenedores conocidos del DOM de origen, conserva la ascendencia del nodo origen para compararla antes de serializar o aplica después un filtrado cuidadosamente validado por patrones de texto. El HTML devuelto por sí solo puede ya no preservar si un nodo estaba originalmente fuera del contenedor principal. La regla de hermanos no se puede ajustar mediante las opciones públicas.

Tres supuestos que los fixtures contradijeron

Los fixtures contradijeron tres supuestos: que charThreshold rechaza artículos cortos, que hacen falta etiquetas semánticas y que los contenidos no narrativos cortos se eliminan. La evidencia de abajo es la parte relevante; no hace falta ninguna afirmación de preregistro.

charThreshold = 500 no es un precipicio

La lectura habitual dice que un artículo por debajo de 500 caracteres devuelve null. No es así. Recorrí longitudes de cuerpo desde 120 hasta 1500 caracteres con valores de charThreshold de 200, 500 y 1000:

Longitud del cuerpoParseo correcto en todos los umbralesLongitud extraída
120161
300342
460509
520569
800841
15001555

Plano. La longitud extraída es idéntica en las tres configuraciones del umbral, en todos los tamaños de cuerpo. El umbral no bloquea el valor devuelto: decide si se repite la extracción con las banderas de limpieza retiradas, y en una página limpia no hay nada que quitar, así que el tamiz devuelve el mismo contenido en cualquier caso. El verdadero límite para devolver null es “no hay texto extraíble en absoluto”.

Y eso produce aquí el fallo real, que es más incómodo que un falso null. Le pasé una página casi vacía —una barra de navegación y una descripción de cuatro palabras—. Respondió con éxito, y el “artículo” que devolvió incluía la navegación. Si no hay un artículo real, Readability te devuelve boilerplate etiquetado como artículo. Si rastreas a escala y tratas un resultado no nulo como “esta página tenía contenido”, esa suposición es incorrecta.

Las etiquetas semánticas no están haciendo el trabajo

Esperaba perder recall al quitar la estructura. Mismo texto de artículo, dos envoltorios: uno con <main><article><h1> y nombres de clase descriptivos, otro con <div class="x1"> y párrafos como simples <div>. Resultado: 4 de 4 bloques de artículo recuperados en ambos, cero boilerplate filtrado en ambos. Cuando el artículo es claramente el bloque de texto más denso de la página, la puntuación por longitud y comas lo encuentra sin ayuda semántica. “Readability necesita etiquetas <article>” es una leyenda urbana.

El límite honesto de esa afirmación: mi página tenía un único bloque de contenido obvio. Donde las etiquetas semánticas sí podrían marcar la diferencia es en una página con dos subárboles densos en competencia, y eso no lo probé.

El contenido no narrativo sobrevive intacto

La regla de que “los párrafos de menos de 25 caracteres no cuentan” me hizo pensar que perdería tablas y pies de figura. Otra vez, estaba equivocado: esa regla afecta a la puntuación del candidato, no a la retención. Una vez que el contenedor gana, todo lo que contiene se queda:

Tipo de contenido dentro del artículoReadabilitytrafilatura
Párrafos de texto (×2)conservadoconservado
Celdas de tabla de datos (×2)conservadoconservado
Bloque de código <pre>conservadoconservado
Párrafos de una línea de menos de 25 caracteres (×2)conservadoconservado
<figcaption>conservadoeliminado
Total8/87/8

Ese es un eje en el que el limpiador más agresivo pierde. Si tus páginas son documentación, tutoriales o cualquier cosa con bloques de código y figuras con pie de foto, el comportamiento de Readability de conservar todo el subárbol ganador es una ventaja.

isProbablyReaderable dice no cuando parse() dice sí

System diagram: isProbablyReaderable says no when parse() says yes

El README sugiere llamar a isProbablyReaderable(doc) como una comprobación previa barata antes de comprometerse con un parseo completo. En mis pruebas, esa puerta rechazó tres formas de página distintas que parse() sí procesó bien:

Forma de la páginaVeredicto del predictorparse()Qué palanca lo corrige
Contenido solo en elementos <li>falsesucedeninguna — da false con cualquier minScore de 1–80 y cualquier minContentLength de 40–200
Diez párrafos, cada uno de menos de 140 caracteresfalsesucedeminContentLength ≤ 100 (minScore no hace nada)
Un párrafo de 408 caracteresfalsesucedeminScore ≤ 10 (la puntuación es ≈16.4)
Artículo normal (control)truesucceed

Los tres fallos tienen causas distintas, y solo dos se pueden ajustar. El caso de <li> es estructural: el predictor solo puntúa nodos p, pre y article (además de padres de div > br), así que una página cuyo contenido vive en elementos de lista no coincide con nada, obtiene cero puntos y ningún ajuste de umbral la recupera —una forma ya reportada en issue #662. El caso de muchos párrafos cortos es una barrera de minContentLength que impide puntuar cada párrafo, así que diez párrafos sustanciales suman cero; bajar ese valor lo corrige, y tocar minScore no. El caso del párrafo único es aritmético: la puntuación es sqrt(408 − 140) ≈ 16.4, por debajo del minScore predeterminado de 20 —un párrafo aislado necesita 140 + 20² = 540 caracteres para superar la barrera por sí solo.

El README sí avisa de que el predictor produce falsos negativos. Yo añadiría la regla práctica: no lo uses como único guardián. Si una página importa, analízala y comprueba la longitud del resultado. El parseo no es tan costoso frente a la construcción de jsdom que ya has pagado.

Los mismos bytes, dos extractores

Ejecutar trafilatura 2.1.0 sobre los mismos fixtures da una lectura más limpia que alinear dos números medidos sobre dos entornos distintos, porque la entrada es idéntica byte por byte:

Medida (11 fixtures mixtos)@mozilla/readabilitytrafilatura
Recall de bloques de artículo1.0001.000
Bloques de boilerplate conservados5/17 (0.294)1/17 (0.059)
Token F1 (micro)0.9480.969
Recall de contenido no narrativo8/87/8
Artículo muy corto (120 chars), token F10.8000.571

Ninguno domina estos fixtures. Trafilatura conservó menos bloques hermanos, mientras que Readability retuvo más contenido corto y no narrativo. La precisión absoluta de token de ambas herramientas se ve rebajada por texto de encabezados no etiquetado, así que el recuento de filtraciones a nivel de bloque es la señal directa más limpia. El benchmark público sobre páginas reales ordena sus word-F1 de forma parecida, pero los corpus y las métricas son distintos y esto no es una validación entre entornos.

Robustez, en breve: ejecuté un gemelo deliberadamente mal formado de la página canónica (un <p> sin cerrar, <b>/<i> mal anidados, y un </div> suelto) y el recall fue 3/3 sin filtraciones, igual que en la versión bien formada. El mérito ahí es de jsdom, cuyo constructor HTML5 repara el desorden antes de que Readability lo vea. Ningún fixture rompió el parser.

Pros y contras

Pros

  • El recall de artículo es su eje fuerte: 74/74 bloques etiquetados recuperados en 22 fixtures sintéticos, recall de tokens 1.000 en el conjunto mixto.
  • El contenido de interfaz con clases reconocibles por regex se elimina de forma fiable: nav, banner de anuncios, sidebar, comentarios y footer desaparecieron en la página realista (5 de 6).
  • El contenido no narrativo se conserva por completo: tablas, código en <pre>, pies de figura y líneas de menos de 25 caracteres sobrevivieron (8/8), mientras trafilatura eliminó un pie.
  • No depende de marcado semántico: un artículo en <div> neutralizado obtuvo la misma puntuación que la versión con <article>/<main>.
  • Los artículos cortos no se rechazan por error: el contenido limpio se recuperó hasta 120 caracteres, igual en charThreshold 200/500/1000.
  • Totalmente determinista: los 22 fixtures devolvieron texto idéntico en tres ejecuciones.
  • Instalación de dos minutos, Apache-2.0, y la versión de npm es la misma que probé (0.6.0), así que nada de esto está desfasado.

Contras

  • La regla de anexado de hermanos se puede explotar: una prosa promocional larga, con pocos enlaces y clase neutral, es indistinguible del texto del artículo y se cuela con linkDensity < 0.25.
  • En páginas pobres en contenido devuelve boilerplate como si fuera el artículo, en lugar de null — el fixture casi vacío volvió con su barra de navegación como cuerpo.
  • isProbablyReaderable produce falsos negativos en tres formas distintas de página, y una de ellas no se corrige con ningún ajuste.
  • Requiere un DOM completo en tiempo de ejecución — el encuadre de “sin dependencias” oculta el coste de jsdom, que domina el bucle.
  • parse() modifica el documento de entrada, así que debes reconstruir el DOM por página.
  • Sin descarga, sin renderizado JavaScript, sin salida estructurada. Es una etapa de la canalización, no la canalización completa.
  • Los fallos en páginas reales reportados en el tracker (pérdida del primer párrafo y contenido antes de tablas) no se reprodujeron en mis fixtures, así que no puedo decir si son raros o si simplemente mis páginas no llegaron a ese punto.

Quién debería usarlo, y quién debería evitarlo

Elige Readability si ya tienes HTML y quieres extraer el artículo en JavaScript puro, dentro de un servicio Node donde añadir una dependencia de Python sería incómodo. (No recogí tiempos como distribución formal, así que no hago ninguna afirmación de velocidad más allá de “la construcción de jsdom domina el bucle, no la extracción”.) Funciones de modo lectura, archivado offline de artículos, newsletters por email, botones de “vista limpia”, extensiones del navegador, pipelines de documentación con bloques de código y pies de figura: ese es su terreno, y las cifras de recall dicen que lo hace bien. Además, su comportamiento se puede leer directamente del código fuente, lo que vale más de lo que parece cuando tienes que explicar a un compañero por qué pasó exactamente un bloque y no otro.

Evítalo si lo que te evalúan es la precisión en la eliminación de boilerplate, y especialmente si alimentas un índice para LLM donde un párrafo promocional suelto puede convertirse en un fragmento recuperable. Evítalo si tus páginas renderizan contenido en el cliente, porque lee el DOM que le entregues y no ejecuta JavaScript. Evítalo si lo que necesitas es {title, price, sku} en lugar de prosa: ninguna configuración convierte un extractor de contenido en uno basado en esquema. Y si procesas páginas donde “¿esta página tenía un artículo de verdad?” es una pregunta real, no confíes en una respuesta no nula como respuesta definitiva.

Alternativas, y dónde encaja el stack de Thunderbit

Nada de esto es un reproche a una librería gratis bajo Apache-2.0 mantenida por Mozilla — Readability es infraestructura, lleva años dentro de Firefox, y para extracción en modo lector es la referencia por una razón. Si quieres ver el panorama más amplio, mantengo una comparación continua en el resumen de scrapers de código abierto y una visión más general en las mejores herramientas de web scraping.

Para los mismos fixtures a través de los seis extractores, consulta la comparativa de extracción entre seis librerías.

Nota del autor: Thunderbit es nuestra opción gestionada para renderizado y extracción a partir de una URL. No se ejecutó sobre estos fixtures, así que no implica una comparación de calidad emparejada. El límite relevante es si ya tienes un DOM y quieres extracción local de artículos, o si prefieres que la descarga, el renderizado y la salida estructurada se operen como servicio. Autoalojarlo evita pagar al proveedor por uso, pero sigue conlleva costes de infraestructura y mantenimiento.

La compensación honesta: Readability es gratis, transparente y te pertenece — puedes leer la regla exacta que decidió tu salida, y eso no es algo que una API gestionada te dé. Un stack gestionado cuesta dinero y oculta el mecanismo, pero cubre las etapas de descarga, renderizado y estructuración que de otro modo tendrías que montar tú. Si te interesa el extremo asistido por IA de ese espectro, he escrito sobre cómo extraer cualquier sitio web con IA y sobre crawlers con IA en otros textos. Elige según las etapas de las que realmente quieras hacerte cargo.

Prueba Thunderbit para extracción de datos web

Veredicto

Readability es una buena candidata cuando ya tienes un DOM, trabajas con JavaScript/Node y prefieres algún contenido hermano de más antes que omitir demasiado. En este pack de fixtures recuperó los 74 bloques de artículo etiquetados y conservó tablas, código y pies de figura. Ese resultado está limitado por páginas sintéticas de una sola columna; el recall real en páginas públicas es 0.982, no se reprodujeron los fallos conocidos cerca del primer párrafo o de tablas, y las páginas pobres en contenido pueden devolver boilerplate como si fuera el artículo.

Solo hay que dimensionar bien su debilidad. Su principal superficie de fallo en estos fixtures es la precisión, y está situada en una línea concreta y documentada del código fuente: un hermano de más de 80 caracteres con densidad de enlace por debajo de 0.25 se añade a tu artículo, forme parte de él o no. Vi ese cambio pasar de 0.143 a 0.278 con texto idéntico. El benchmark real sobre páginas públicas reporta precision 0.914 y recall 0.982. Si vas a volcar el texto extraído en un índice que luego citará un modelo, revisa tanto el boilerplate retenido como el cuerpo omitido en lugar de asumir que ninguna de las dos clases de error existe.

Prueba Thunderbit para extracción de datos web Get Started Free

Preguntas frecuentes

¿Mozilla Readability elimina todo el boilerplate? No, y la cifra depende mucho de qué estés midiendo. En el benchmark público sobre páginas reales, readability_js 0.6.0 obtiene una precision de 0.914, así que aproximadamente el 8.6% de lo que devuelve no es cuerpo del artículo. En una página de prueba realista mía eliminó 5 de 6 bloques de interfaz (nav, anuncio, sidebar, comentarios y footer desaparecieron), dejando solo un párrafo promocional con clase neutral. En un conjunto de fixtures que ponderé a propósito con bloques diseñados para derrotar el heurístico, conservó 5 de 17; esa última cifra es una prueba de estrés, no una tasa real.

¿Necesito jsdom para usar readability.js en Node? Sí, o cualquier otra implementación de DOM. Readability está escrito en JavaScript puro, pero trabaja sobre un objeto document vivo, así que en Node tú tienes que aportar el DOM —en mi caso, jsdom 29.1.1. La descripción de “sin dependencias” se refiere al algoritmo, no al runtime. Además, recuerda que parse() modifica el documento que recibe, así que conviene construir un DOM nuevo para cada página en lugar de reutilizar uno.

¿Qué hace realmente la opción charThreshold? No hace lo que la mayoría piensa. No hace que los artículos cortos devuelvan null: recuperé artículos limpios hasta 120 caracteres, con longitud extraída idéntica con charThreshold en 200, 500 y 1000. El umbral controla si el parser repite la extracción con las banderas de limpieza retiradas; en una página limpia no hay nada que quitar, así que la salida es la misma de todas formas. El caso genuino de null es una página sin texto extraíble, y hasta una página solo con navegación volvió no nula, devolviendo la navegación como artículo.

¿Conviene llamar a isProbablyReaderable antes de parse()? Úsalo como pista, no como puerta de entrada. Devolvió false en tres formas de página en las que luego parse() sí tuvo éxito: contenido dentro de elementos <li>, diez párrafos de menos de 140 caracteres cada uno y un único párrafo de 408 caracteres. El caso de <li> no se puede corregir con ajustes, porque el predictor solo puntúa nodos p, pre y article; el caso de muchos párrafos cortos necesita un minContentLength más bajo; el caso del párrafo largo único necesita un minScore más bajo, ya que un párrafo solo debe alcanzar 540 caracteres para superar el valor predeterminado. Si una página importa, analízala y comprueba el resultado.

¿Readability o trafilatura para extraer artículos? Con bytes idénticos de fixtures, trafilatura conservó menos boilerplate (1/17 bloques frente a 5/17), mientras que Readability recuperó más contenido corto y conservó un <figcaption> que trafilatura eliminó. Elige según la tolerancia al error y el runtime. El benchmark público es un contexto aparte, no una validación de este resultado de fixtures.

Ke
Ke
CTO en Thunderbit | Científico de datos sénior y experto en ML Con casi una década de experiencia en aprendizaje automático y ciencia de datos, Ke Shen es exalumno de la Universidad de Columbia y antiguo científico de datos sénior en Walmart Labs. Con una sólida experiencia, reconocida por sus pares, en Python, R, Java y estadística, comparte conocimientos probados en el campo sobre cómo llevar algoritmos complejos de IA desde la teoría hasta una arquitectura lista para producción.
Tabla de contenidos
Thunderbit · Agente de datos web con IA

Extrae datos de cualquier página en 1 clic

Con la confianza de más de 250,000 usuarios
plan gratuito disponible
De la página web a la hoja de cálculo
Describe lo que necesitas: el agente de IA de Thunderbit lo extrae y lo exporta a Excel, Google Sheets, Airtable o Notion. Empieza gratis.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week