El extractor que filtró más texto repetitivo no se dejó ningún contenido fuera en este conjunto de pruebas

Última actualización: August 14, 2026
El extractor que filtró más texto repetitivo no se dejó ningún contenido fuera en este conjunto de pruebas
Resumen con IA
Seis bibliotecas, un conjunto de fixtures anotado y un solo sistema de puntuación. En estos 22 fixtures sintéticos, la biblioteca con mayor fuga de boilerplate — Readability de Mozilla, con un 23,5%— fue también la única que recuperó todas las unidades de artículo etiquetadas. Esa es la compensación en una sola frase, y la mayoría de los artículos sobre este tema nunca la muestran, porque suelen medir precisión y quedarse ahí. ¿Vas a alimentar un modelo y pagas por token? newspaper4k o goose3. Ambos filtraron cero unidades de boilerplate y cero tokens contaminantes. newspaper4k si quieres una respuesta en todas las páginas; goose3 si prefieres silencio antes que una suposición, y tus páginas tienen párrafos.

Seis bibliotecas, un conjunto de fixtures anotado y un solo sistema de puntuación. En estos 22 fixtures sintéticos, la biblioteca con mayor fuga de boilerplate — Readability de Mozilla, con un 23,5%— fue también la única que recuperó todas las unidades de artículo etiquetadas.

Esa es la compensación en una sola frase, y la mayoría de los artículos sobre este tema nunca la muestran, porque suelen medir precisión y quedarse ahí.

Qué se midió realmente

Cada fixture de este conjunto incluye verdad terreno por unidad. Cada bloque de la página — los párrafos del artículo, la navegación, los anuncios, la barra lateral, el hilo de comentarios, la promoción — está etiquetado como article o boilerplate y marcado con un token centinela único. Así que “¿el extractor recuperó esta unidad?” no se decide por similitud, sino por pertenencia exacta de subcadena. Un centinela aparece en la salida o no aparece.

Veintidós fixtures, 91 unidades. Seis extractores: Mozilla Readability 0.6.0 (vía jsdom 30.0.1), trafilatura 2.2.0, resiliparse 1.0.9, newspaper4k 0.9.6, goose3 3.1.22 y jusText 3.0.2. Python 3.14.2 y Node 22 se ejecutaron en la misma máquina. El artículo no conserva el sistema operativo, la CPU, las invocaciones exactas, el número de repeticiones ni la política de calentamiento, así que la columna de tiempo es una observación local y no un benchmark portátil.

Antes de ejecutar nada me impuse dos reglas. Cada biblioteca de Python se instaló en su propio virtualenv vacío, de modo que su huella es la suya y no hereda dependencias que haya traído otra. Y ningún runner calcula métricas: cada uno vuelca texto bruto extraído, y un único evaluador produce todos los números, para comparar seis herramientas con la misma aritmética y no con seis definiciones parecidas de “precisión”.

La tabla principal

BibliotecaRecall de artículo (22 en total)Fuga de boilerplatePrecisión sobre tokens de contenidoFixtures con respuesta en precisiónTokens contaminantes
Readability1.00000.23530.910911/1135
trafilatura0.98650.05880.941111/114
newspaper4k0.98650.00000.945211/110
resiliparse0.90540.05880.938111/117
jusText0.83780.47060.876010/1174
goose30.82430.00001.000010/110

El recall se agrega sobre los 22 fixtures. La tasa de fuga, la precisión agregada sobre tokens de contenido y la contaminación usan los 11 fixtures que contienen tanto unidades de artículo como de boilerplate; “answered” muestra cuántos de esos devolvieron salida. Los números completos por fixture están en sixway-scores.json.

Hay una fila de esa tabla que no es un valor por defecto. extract_plain_text de resiliparse usa main_content=False por defecto, y yo lo llamé con main_content=True. La diferencia no es pequeña: con los valores por defecto filtra 17 de 17 unidades de boilerplate en todo el conjunto — cada nav, anuncio, barra lateral, hilo de comentarios y promo — frente a 1 de 17 con la opción activada. Las demás bibliotecas de arriba se ejecutaron con sus valores por defecto. Así que la tasa de fuga de 0.0588 de resiliparse es lo que hace cuando se le pide contenido principal, y extract_plain_text(html) a secas es un producto distinto (default-vs-main-content.json).

Lee la primera columna y la segunda en conjunto, porque mirar solo una de ellas te lleva a elegir la herramienta equivocada.

Readability no falla nunca. Recall perfecto en los 22 fixtures, y es el único que lo logra. Lo paga: se filtraron 4 de 17 unidades de boilerplate, 35 tokens contaminantes, cuatro veces la tasa de fuga de trafilatura. Tres de sus cuatro fugas tienen la misma forma: un bloque promocional de clase neutra al lado del artículo, que su heurística de anexar hermanos se traga. Si alimentas su salida a un modelo, estás pagando por esos tokens y el modelo los está leyendo como si fueran artículo.

newspaper4k es el más equilibrado. Cero fuga, cero tokens contaminantes, 0.9865 de recall y respuesta en los 22 fixtures. Si tuviera que escoger una sin conocer la carga de trabajo, sería esta, y no es la que más gente suele elegir.

goose3 tiene precisión perfecta y el peor recall de la prueba. Cada palabra de contenido que devolvió era realmente contenido del artículo. También se quedó sin recuperar nada en dos fixtures y no devolvió salida en esos mismos dos. La precisión perfecta sale barata si te permites no responder.

El número de precisión que favoreció a dos bibliotecas

Ese último punto merece concretarse, porque es una trampa en la que casi caigo al publicar.

La precisión y el F1 aquí dependen de que exista salida. Una biblioteca que devuelve una cadena vacía en un fixture no suma ni al numerador ni al denominador; es decir, no responder sale gratis, y la precisión de un extractor conservador parece mejor que la de uno más completo por una razón puramente silenciosa.

goose3 tuvo una precisión agregada de 1.0000 en los 10 fixtures puntuados en los que sí devolvió salida. La de jusText fue 0.8760 sobre 10 de 11. Readability, trafilatura, resiliparse y newspaper4k respondieron en 11 de 11. La tabla ahora muestra ese denominador junto a la precisión para que la abstención no desaparezca detrás de un ratio halagador.

Hubo una versión peor de esto. Mi primer evaluador promediaba el recall de artículo sobre el mismo conjunto de 11 fixtures de fidelidad de contenido — el conjunto que excluye fixtures sin boilerplate, que es lo correcto para medir fuga. Ahí resiliparse daba 1.0000 de recall. Sobre los 22 fixtures completos está en 0.9054, porque en el fixture cuyo artículo vive por completo dentro de elementos <li> y sin ningún <p>, sí devuelve salida pero recupera 0 de 6 unidades de artículo. Ese fixture no tiene boilerplate, así que quedó fuera del promedio, y un fallo real quedó escondido tras una puntuación perfecta.

Dónde falla realmente cada uno

FixtureQué pruebaQuién no recupera nada
Artículo entero dentro de <li>, sin <p>supuestos de estructuraresiliparse (0/6), goose3 (sin salida)
Una sola unidad de artículo de 129 caracteresumbral de contenido cortojusText
Diez párrafos cortos, sin ninguno largoumbral de contenido cortojusText
Documento casi vacíoel verdadero límite nulogoose3, jusText

Cada uno de estos casos es un comportamiento concreto y reproducible, no un genérico “extrae peor”:

  • resiliparse y goose3 asumen párrafos. Si los apuntas a una página cuyo cuerpo es una lista — un changelog, una especificación, una FAQ, una receta — resiliparse devuelve texto sin ninguna de las listas, mientras goose3 no devuelve nada. resiliparse es el más peligroso de los dos, porque devolver algo parece un éxito.
  • jusText tiene un precipicio de longitud, y es brusco. Más abajo lo explico.
  • El documento casi vacío es el único caso en el que no devolver nada es discutiblemente correcto, así que no se lo reprocharía a ninguna de las dos bibliotecas.

jusText: un precipicio, no una pendiente

jusText produjo salida en 19 de 22 fixtures y filtró el 47% del boilerplate — la cifra más alta de la prueba, justo al contrario de su reputación. Pero el número interesante es el que me hizo repetir toda la prueba.

jusText clasifica cada bloque por densidad de stopwords frente a una lista de parada del idioma, y luego hace una pasada sensible al contexto que promueve un bloque neargood a good solo cuando está junto a un bloque good ya existente. Un bloque llega a good por sí solo solo si supera length_high, que por defecto es 200 caracteres. En un documento donde nada cruza esa línea, nada inicia la promoción y toda la página se degrada a boilerplate.

Lo barrí sobre un documento cuyo párrafo más largo tiene 151 caracteres:

length_highPárrafos buenosCaracteres devueltos
200 (por defecto)00
1508832
1208832
1008832
808832

De cero a 832 caracteres cuando exactamente un párrafo supera el umbral, y después nada cambia aunque lo bajes aún más. Un solo párrafo por encima de la línea desbloquea todo el documento.

Antes de concluir eso, también barrí length_low en cuatro valores y max_link_density en dos: ocho combinaciones, todas devolviendo cero. La regla de este proyecto es que una afirmación sobre capacidad negativa necesita al menos tres formas de parámetro exploradas o el propio nombre del campo en un error del proveedor, y un parámetro improductivo no basta para concluir nada sobre la biblioteca. Los números están en justext-length-threshold.json.

Nada de esto dice que jusText extraiga mal. En una página real de lenguaje natural, con los valores por defecto, devolvió 1.190 caracteres de texto limpio del artículo. Lo que dice es que jusText tiene un ajuste documentado que se comporta como un interruptor, y que la posición por defecto de ese interruptor es mala para documentos con párrafos cortos.

Qué instalas y cuánto cuesta importarlo

Measured results chart: Install footprint vs cold import

Mismos fixtures, misma máquina, cada biblioteca en su propio virtualenv vacío.

BibliotecaPaquetessite-packagesImportación en fríoP50 de extracción
resiliparse521.0 MiB0.015 s0.06 ms
jusText322.4 MiB0.777 s0.56 ms
goose31644.3 MiB2.181 s1.85 ms
newspaper4k2247.5 MiB2.812 s2.69 ms
trafilatura1769.9 MiB1.584 s0.51 ms
Readability + jsdom32 (npm)26 MiB0.473 s6.37 ms

En esta ejecución, resiliparse tuvo los valores más bajos tanto en importación en frío como en extracción mediana: 15 ms y 0.06 ms. Las proporciones exactas entre runtimes exagerarían lo que este protocolo incompleto puede sostener, sobre todo porque su extracción individual más lenta fue de 1.098 ms. Antes de usar estos números para dimensionar serverless hacen falta distribuciones separadas de arranque, primera llamada y estado estable.

trafilatura y resiliparse empatan prácticamente en calidad — 0.9697 frente a 0.9681 en F1 de tokens de contenido, misma tasa de fuga de 0.0588 — y no voy a declarar ganador por una diferencia tan pequeña. En huella no están cerca: 21.0 MiB frente a 69.9 MiB, 5 paquetes frente a 17. La compensación real es la ceguera de resiliparse a listas frente a las tres dependencias extra de trafilatura.

Dos fallos en mi propio entorno de pruebas, detectados antes de publicar

La comparación de arriba casi no llega a existir, y la razón vale más que cualquier fila individual.

El conjunto de fixtures no podía ver dos de las seis bibliotecas. Los fixtures originales escribían cada unidad como una secuencia de tokens inventados y únicos — zzart01vf64 zzart01v56i —, justo lo que hace que el recall sea exacto. Pero eso también significa que no contienen palabras funcionales en inglés. Readability, trafilatura y resiliparse deciden de forma estructural, a partir del DOM, así que no se vieron afectados. goose3 y jusText deciden léxicamente, contando stopwords, y no había ninguna que contar: ambas devolvieron una cadena vacía en los 22 fixtures.

Una tabla con dos bibliotecas puntuadas en cero habría parecido autoritaria y no habría significado nada. Lo comprobé antes de escribirla, en una página real: goose3 devolvió 1.017 caracteres y jusText 1.190. Las bibliotecas estaban bien. El entorno de pruebas no podía representarlas.

Así que los fixtures se reconstruyeron con prosa en inglés llevando los centinelas — misma estructura, mismas clases, mismas posiciones en el DOM, mismos límites de unidad, mismos centinelas, 1.568 tokens cambiados uno por uno. goose3 pasó de 0 a 20 de 22.

Luego la reconstrucción rompió dos cosas por sí sola, y ambas fueron culpa mía. Una palabra en inglés mide unos seis caracteres; zzart01vf64 mide unos doce. Reemplazar uno por uno redujo todas las unidades a la mitad — 21.646 caracteres de texto de unidad pasaron a 10.986, y la unidad más larga cayó de 1.513 a 622. Eso reescribió sin ruido los fixtures cuyo único propósito era medir longitud. jusText, cuyo comportamiento es un precipicio de longitud, pasó de 19 de 22 a 6 de 22 solo por eso. Si hubiera publicado la versión reducida a la mitad, el número de jusText habría estado mal por un factor de tres, además en la dirección que lo hace parecer peor de lo que es.

La segunda: sacar todas las unidades de un corpus compartido restableció la densidad de stopwords, pero destruyó la propiedad de la que depende la puntuación a nivel de token. El vocabulario de artículo y el de boilerplate deben ser disjuntos, o “tokens extraídos que son tokens de boilerplate” acaba contando la palabra the. Diez de los 22 fixtures terminaron con vocabularios solapados, frente a cero en los originales. La solución fue añadir sufijos a las palabras de contenido por unidad y dejar intactas las palabras funcionales: stopwords reales para que las cuenten las bibliotecas léxicas, vocabulario de contenido disjunto para el evaluador.

Por eso también las columnas a nivel de token aquí se llaman content_token_* y no reutilizan las cifras publicadas en la comparación Readability vs trafilatura. Son otra magnitud, medida solo sobre palabras de contenido, y citar una como si fuera la otra estaría mal.

Mientras reconstruía, apareció además algo que no era culpa mía: tres fixtures de densidad de enlaces colocan </a> dentro de una palabra<a href="/x">zzsibp015qlhf zzsi</a>bp015qbht — porque el ancla se posicionó por desplazamiento de caracteres para alcanzar una proporción exacta. El texto renderizado no cambia, así que la puntuación original no lo detectó, pero cualquier extractor que trabaje por elemento y no por fragmento de texto ve dos trozos donde los demás ven una sola palabra. Corregido, y el delta de caracteres enlazados quedó registrado en lugar de absorberse en silencio.

Quién debería usar qué

¿Vas a alimentar un modelo y pagas por token? newspaper4k o goose3. Ambos filtraron cero unidades de boilerplate y cero tokens contaminantes. newspaper4k si quieres una respuesta en todas las páginas; goose3 si prefieres silencio antes que una suposición, y tus páginas tienen párrafos.

¿Quieres optimizar una ruta Python sensible a la latencia? Incluye resiliparse en la prueba. Aquí tuvo la importación y la extracción mediana más bajas, y empató muy de cerca con trafilatura en calidad, pero solo con main_content=True, que no es el valor por defecto. Revisa primero los diseños con muchas listas y no conviertas estos tiempos locales en una proporción exacta entre runtimes.

¿Archivado, o cualquier caso en el que perder contenido sea peor que añadir contenido de más? Readability. Es el único que recuperó todas las unidades de artículo en todos los fixtures, y 35 tokens sueltos son un precio barato si la alternativa es perder un párrafo.

¿Trabajo multilingüe? jusText es candidato porque trae listas de stopwords por idioma. Este estudio no probó extracción multilingüe, así que esa función es un motivo para evaluarlo, no una prueba de que gane. Prueba length_high contra longitudes de párrafo representativas.

¿Cualquier cosa que no sea un artículo? Ninguna de estas. Todas parten de la idea de que una página tiene un cuerpo principal de texto, y una ficha de producto, una página de resultados o un panel rompen ese supuesto de formas que ningún parámetro arregla.

Dónde encaja una API gestionada

Todo lo anterior son bibliotecas que ejecutas tú: les das HTML y recibes texto. Sus modos de fallo observados varían según la forma de la página, así que valida los valores por defecto elegidos contra tu propio corpus. La extracción de campos estructurados y el fetch/render quedan fuera de esta comparación.

Nota del autor: Thunderbit es nuestro servicio gestionado para flujos con URL y salida estructurada. No se ejecutó con estos fixtures, así que no implica ninguna comparación de calidad. La decisión relevante es si ya tienes el HTML y quieres un extractor local de texto, o si quieres que un servicio se ocupe del fetch/render y de la operación.

La formulación honesta es esta: si ya tienes el HTML y quieres texto, una de estas seis opciones es gratis y buena, y esta tabla te dice cuál. Si estás capturando páginas a escala, o quieres filas en lugar de prosa, eso es otra compra distinta.

Si en cambio estás eligiendo entre fetchers gestionados, nuestro resumen de APIs de web scraping cubre ese terreno, y nuestra comparativa de coste de APIs SEO y de datos cubre lo que cobran. En la parte autogestionada, el pilar de scrapers open source ofrece una visión más amplia, y si lo que realmente necesitas es Markdown y no texto plano, convertir HTML a Markdown en Python es donde más pérdida suele haber.

Prueba Thunderbit para extraer datos web

Veredicto

No hay ganador, y una tabla que nombrara uno estaría falseando una compensación real.

Construye primero un pequeño corpus de aceptación: incluye artículos hechos solo de listas, párrafos cortos, promos hermanas, una página casi vacía y ejemplos en los que no responder sea preferible a contaminar. Puntúa por separado la recuperación de contenido, la fuga de boilerplate y la abstención. En estos fixtures, Readability favoreció el recall, newspaper4k ofreció la fila más equilibrada y resiliparse fue una candidata para baja latencia con un punto ciego en contenido de listas; esas etiquetas no deben salir del tipo de páginas probado sin validación.

Lo que de verdad te diría es aún más concreto: ejecuta los fixtures sobre tus propias formas de página antes de elegir. Dos de las seis no podían ver el entorno de pruebas que empecé usando, y una de ellas obtuvo un recall perfecto que escondía un fallo total. Una tabla comparativa es un punto de partida para eso, no un sustituto.

Prueba Thunderbit para extraer datos web Get Started Free

Preguntas frecuentes

¿Son comparables estos números con los benchmarks publicados para estas bibliotecas? No, y no los citaría así. Son fixtures controlados con unidades sintéticas pero etiquetadas, así que las seis vieron los mismos bytes y la comparación entre ellas es justa. Las cifras publicadas, como el benchmark de extracción de artículos de scrapinghub, usan corpus reales, que miden algo distinto y más difícil. Usa esta tabla para comparar estas seis entre sí, no contra un número sacado de un paper.

¿Por qué la tasa de fuga de Readability es bastante más alta que la de trafilatura si ambas son basadas en DOM? Por dónde traza cada una el límite. Tres de las cuatro fugas de Readability son bloques promocionales de clase neutra situados como hermanos del artículo, que su heurística de anexar hermanos incorpora con la idea de que el contenido largo y poco enlazado junto al artículo probablemente forma parte de la historia. A menudo lo hace. En estos fixtures era una promo. trafilatura es más estricta al adjuntar y filtró una de esas mismas unidades.

¿Debo confiar en los números de precisión de goose3 y jusText? Solo junto con el tamaño de muestra. Ambos se puntuaron sobre 10 de los 11 fixtures que contienen tanto unidades de artículo como de boilerplate, porque no devolvieron nada en uno de ellos, y un fixture sin salida no contribuye a ningún lado del ratio. La precisión 1.0000 de goose3 es real para las páginas donde respondió; su recall 0.8243 sobre los 22 fixtures es la otra mitad del mismo hecho.

¿Importa el umbral de longitud de jusText en páginas reales? Depende por completo de la longitud de tus párrafos. Un artículo de noticias con párrafos de 300 caracteres cruzará length_high en el primero y funcionará con normalidad; por eso jusText devolvió 1.190 caracteres limpios en una página real con los valores por defecto. Una página de párrafos cortos, elementos de lista o descripciones de producto puede no cruzarlo nunca, y entonces jusText devuelve una cadena vacía en lugar de una respuesta parcial. Ajusta eso explícitamente en vez de descubrirlo en producción.

¿Qué no se probó aquí? Páginas reales, ninguna. Extracción multilingüe, pese a que las listas de stopwords son el principal argumento de jusText. Memoria bajo carga. Cualquier página que no sea un artículo: ni listados de productos, ni resultados de búsqueda, ni paneles. Casos límite de codificación. Y los ecosistemas de Node y Python se compararon por comportamiento de biblioteca, no por rendimiento del runtime, así que las cifras en milisegundos a ambos lados de esa frontera deben leerse como órdenes de magnitud y no como ratios precisos.

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