En un documento cuyo párrafo más largo mide 151 caracteres, jusText, con su configuración por defecto, devuelve cero caracteres. No es una extracción parcial: es una cadena vacía. Si bajas un umbral para que solo un párrafo lo supere, ese mismo documento pasa a devolver 832 caracteres. Si lo bajas todavía más, el resultado sigue siendo 832.
En este fixture, el comportamiento no es una pendiente, sino un precipicio. Que el valor por defecto esté del lado equivocado o no depende de la longitud de los párrafos y de cómo se distribuye el contenido irrelevante en el corpus objetivo.
Qué es jusText y por qué importa la densidad léxica
Comparado con los demás extractores de este conjunto de pruebas, jusText depende de forma inusualmente fuerte de la densidad de stopwords específica de cada idioma. Un bloque con una alta proporción de palabras funcionales — the, and, of, was — tiene más probabilidades de ser prosa. Esa señal léxica no es el clasificador completo: también influyen la longitud del párrafo, la densidad de enlaces, los límites de bloque derivados del HTML, la distancia a encabezados, las clases vecinas y un segundo pase sensible al contexto.
Referencia oficial: repositorio oficial de jusText.

Como el clasificador usa listas de stopwords por idioma, jusText incluye 100. Esa capa léxica específica por idioma es su rasgo más distintivo en esta comparación, aunque no se probó la calidad en varios idiomas.
Versión probada: 3.0.2, BSD 2-Clause, 822 estrellas en GitHub. Python 3.14.2.
El precipicio, medido

El clasificador de jusText funciona en dos pasadas. En una primera pasada, sin contexto, cada párrafo se etiqueta como good, bad, short o neargood. Luego, una segunda pasada, sensible al contexto, promociona neargood a good, pero solo cuando está junto a un bloque que ya es good. Y un párrafo obtiene good por sí solo únicamente cuando supera length_high, cuyo valor por defecto es 200 caracteres.
En un documento donde ninguno de los párrafos supera los 200, no hay nada que sirva de semilla para la promoción, y todos los bloques neargood acaban degradados a contenido accesorio. La página completa vuelve vacía.
Recorrí el umbral en un fixture cuyo párrafo más largo tiene 151 caracteres:
length_high | Párrafos clasificados como good | Caracteres devueltos |
|---|---|---|
| 200 (por defecto) | 0 | 0 |
| 150 | 8 | 832 |
| 120 | 8 | 832 |
| 100 | 8 | 832 |
| 80 | 8 | 832 |
justext-length-threshold.json.
En este fixture de umbral, con que un solo párrafo cruzara la línea bastó para que los ocho párrafos objetivo empezaran a salir; relajar aún más el umbral no añadió nada. El rastro observado encaja con una pasada de contexto que promociona vecinos neargood elegibles alrededor de un bloque good existente; no significa que cualquier vecino se promocione sin condiciones en cualquier página.
Antes de atribuir el cambio a length_high, recorrí length_low en cuatro valores cruzados con max_link_density en dos: ocho combinaciones, todas devolvieron cero. Cambiar cualquiera de esos dos ajustes no recuperó contenido en este fixture.
En todo el conjunto de 22 fixtures, el patrón se mantiene: 2 de 22 fixtures devolvieron contenido con length_high=200, 9 de 22 con 150, 15 de 22 con 120.
Dos cosas que esto no significa. No significa 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, porque un párrafo periodístico real suele superar los 200 caracteres a la primera. Y tampoco significa que el valor por defecto esté mal; significa que asume párrafos largos, y conviene comprobar si tu corpus los tiene antes de lanzar nada.
La otra cara de este conjunto: más fuga con la configuración por defecto
En este conjunto de fixtures etiquetados y con los valores por defecto, jusText presenta la mayor fuga de contenido irrelevante medida.
| Biblioteca | Recall de artículo (las 22) | Fuga de contenido irrelevante | Precisión en tokens de contenido | Tokens contaminantes |
|---|---|---|---|---|
| Readability | 1.0000 | 0.2353 | 0.9109 | 35 |
| trafilatura | 0.9865 | 0.0588 | 0.9411 | 4 |
| newspaper4k | 0.9865 | 0.0000 | 0.9452 | 0 |
| resiliparse | 0.9054 | 0.0588 | 0.9381 | 7 |
| jusText | 0.8378 | 0.4706 | 0.8760 | 74 |
| goose3 | 0.8243 | 0.0000 | 1.0000 | 0 |
sixway-scores.json. Un único conjunto de fixtures, un único evaluador, y cada unidad etiquetada con un centinela único para que la recuperación sea una coincidencia exacta por subcadena.

47% de fuga de contenido irrelevante y 74 tokens contaminantes: el doble que Readability en fuga y más del doble en contaminación en este conjunto sintético etiquetado. Este es un resultado diagnóstico con la configuración por defecto, no una clasificación estable válida para todo el producto.
La fuga observada encaja con esa misma pasada de contexto implicada en el precipicio. Los bloques vecinos neargood pueden promocionarse cuando las clases de bloque circundantes y las reglas de distancia lo permiten; por eso un texto de tipo prosa o un comentario junto al artículo puede cruzar el límite. El resultado del fixture muestra qué bloques etiquetados se filtraron, mientras que el mecanismo simplificado sigue dependiendo de las reglas exactas de contexto de jusText.
Su recall es 0.8378, el tercero por la cola, y toda la pérdida viene del umbral: no recuperó nada en un artículo de 129 caracteres, nada en una página con diez párrafos cortos y nada en el documento casi vacío.
Inventario de listas de stopwords por idioma
Noventa y nueve listas de stopwords más.
justext.get_stoplists() devuelve 100 idiomas. El clasificador está parametrizado por idioma por diseño, no por una simple traducción de una heurística en inglés, y cambiar de idioma es un único argumento:
import justext
paragraphs = justext.justext(html, justext.get_stoplist("Czech"))
text = "\n".join(p.text for p in paragraphs if not p.is_boilerplate)
trafilatura y goose3 también exponen comportamiento relacionado con el idioma, pero en esta revisión no se evaluó ningún extractor con verdad de terreno en otros idiomas. Las 100 stoplists incluidas de jusText lo convierten en un candidato claro para una evaluación multilingüe; el inventario por sí solo no demuestra calidad de extracción en esos idiomas ni prueba que los competidores los cubran peor.
La API son dos funciones y nueve constantes ajustables — length_low 70, length_high 200, stopwords_low 0.30, stopwords_high 0.32, max_link_density 0.20, max_heading_distance 200, además del manejo de codificación. Pequeña, legible y completamente documentada en la firma.
Instalación y velocidad
pip install justext descarga 3 paquetes — el mínimo de la comparación — y 22.4 MiB, en menos de dos segundos.
Referencia oficial: jusText en PyPI.
| Biblioteca | Paquetes | site-packages | Importación en frío | Extracción p50 |
|---|---|---|---|---|
| resiliparse | 5 | 21.0 MiB | 0.015 s | 0.06 ms |
| jusText | 3 | 22.4 MiB | 0.777 s | 0.56 ms |
| goose3 | 16 | 44.3 MiB | 2.181 s | 1.85 ms |
| newspaper4k | 22 | 47.5 MiB | 2.812 s | 2.69 ms |
| trafilatura | 17 | 69.9 MiB | 1.584 s | 0.51 ms |
install-and-import.json. Cada biblioteca en un entorno virtual vacío distinto.
Tres paquetes y 22.4 MiB es una dependencia pequeña dentro de esta comparación, y una extracción mediana de 0.56 ms está bastante cerca de los 0.51 ms de trafilatura en este entorno. La medición del entorno no desglosa el tamaño en disco por tipo de archivo, así que no puede decir cuánto del peso corresponde a las stoplists.
La cuestión del mantenimiento, respondida con cuidado
Las métricas de mantenimiento con fecha usadas aquí son el último push en el repositorio y la última versión en PyPI, ambas 2025-02-25. Eso fue diecisiete meses antes de las pruebas. Ocho versiones en total, 91 forks, 9 incidencias abiertas, no archivado.
Los clasificadores de PyPI indican compatibilidad con Python hasta 3.9. Yo lo ejecuté en 3.14.2 y se instaló, se importó en 0.777 s y extrajo contenido en 19 de 22 fixtures sin lanzar ni una excepción.
Así que los metadatos van cinco versiones de Python por detrás de la realidad, y la realidad es que funciona. Esa es la distinción útil: un repositorio silencioso dice algo sobre el soporte, no necesariamente sobre el funcionamiento. Para una biblioteca cuyo algoritmo entero es un método publicado en 2011 más un conjunto de listas de palabras, “terminado” es un estado plausible: no queda mucho por cambiar, y las stoplists no se degradan como sí lo haría una solución improvisada contra bots.
Lo que sí implica un repositorio silencioso es que, si aparece un fallo, tendrás que arreglarlo tú o hacer un fork. Eso hay que ponerlo en la balanza junto con las nueve incidencias abiertas, que no es precisamente el perfil de una biblioteca desbordada por problemas sin resolver.
Memoria y qué le hace el HTML roto
Aquí se miden por separado dos cuestiones operativas.
El contexto más amplio de la prueba de estrés está en la comparación de memoria y HTML malformado en diez bibliotecas.
Memoria residente máxima, mediante /usr/bin/time -l, un proceso nuevo por celda: el piso de importación es lo que cuesta la biblioteca cargada e inactiva; los picos incluyen el documento.
| Biblioteca | Entorno | Piso de importación | Pico 226 KB | Pico 10 MB |
|---|---|---|---|---|
| html2text | python3.14 | 18.7 | 19.9 | 71.2 |
| pyquery | python3.14 | 30.3 | 33.9 | 172.5 |
| resiliparse | python3.14 | 20.5 | 25.1 | 225.1 |
| markdownify | python3.14 | 23.9 | 28.9 | 278.5 |
| goose3 | python3.14 | 44.1 | 52.4 | 398.5 |
| cheerio | node22 | 66.8 | 76.5 | 398.5 |
| justext | python3.14 | 30.3 | 36.6 | 431.2 |
| newspaper4k | python3.14 | 52.6 | 61.8 | 668.5 |
| trafilatura | python3.14 | 52.5 | 64.8 | 927.1 |
| turndown | node22 | 47.8 | 68.4 | 2947.1 |
memory-results.json. Los valores de referencia de Python y Node no son comparables entre sí; en ambos casos el intérprete forma parte de la medición.
El piso de jusText es 30.3 MiB y su pico en este fixture de 10 MB es 431.2 MiB, unas 14.2 veces el piso de importación y 43.1 veces el tamaño de entrada en RSS absoluta. Un fixture y un proceso no bastan para establecer una curva de escalado general; además, las bases de Python y Node siguen sin ser comparables.
HTML roto. Doce documentos, cada uno rompiendo exactamente una cosa — etiquetas sin cerrar, elementos inline mal anidados, atributos sin comillas con espacios, cierres sueltos, ausencia total de <html>, atributos duplicados, un documento truncado a mitad de etiqueta, entidades incorrectas, un <script> sin cerrar, una declaración de charset falsa, un comentario que contiene marcado, y 600 niveles de anidamiento — más dos controles bien formados de tamaño equivalente, porque “no devolvió nada” solo dice algo sobre la malformación si la biblioteca tampoco devuelve nada en un documento limpio del mismo tamaño.
justext lanzó excepción en 0 de 14 y no devolvió nada en 13, recuperando 0/33 centinelas puntuados en los fixtures rotos (malformed-results.json). El control corto bien formado también devolvió 0 caracteres; el control largo bien formado fue el único resultado no vacío, con 1.333 caracteres. Los doce fixtures malformados se quedaron vacíos, y el caso del <script> sin cerrar queda excluido de la puntuación de supervivencia de centinelas. Por tanto, este lote muestra tolerancia del parser — no hubo excepción —, pero no permite atribuir el silencio a la malformación en lugar del umbral de tamaño ya medido.
Pros y contras
A su favor. 100 listas de stopwords por idioma, y un clasificador realmente construido alrededor de ellas en vez de traducido a ellas. El menor número de dependencias de la comparación, con 3 paquetes. Extracción mediana de 0.56 ms. Nueve constantes de ajuste documentadas y legibles. BSD 2-Clause. Funciona correctamente en Python 3.14 pese a que los clasificadores de compatibilidad solo llegan a 3.9.
En contra. La mayor fuga de contenido irrelevante en esta prueba sintética con valores por defecto: 47%, con 74 tokens contaminantes. El fixture de párrafos cortos devolvió una cadena vacía cuando ningún párrafo superó length_high. El recall fue 0.8378 en este conjunto. El último push registrado y la última versión datan de diecisiete meses antes de las pruebas, así que la titularidad del mantenimiento merece atención.
Quién debería usarlo y quién no
Evalúa jusText si trabajas con corpus multilingües, porque su superficie de stoplists específicas por idioma es explícita y amplia. Esta prueba inventarió 100 stoplists, pero no midió la calidad en otros idiomas. También es candidato cuando una dependencia de tres paquetes y controles explícitos de umbral encajan con el despliegue.
Omítelo si vas a alimentar un modelo token a token, donde 74 tokens contaminantes frente a cero en goose3 y newspaper4k tiene un coste directo. Omítelo también en páginas de párrafos cortos — textos promocionales, listados, changelogs, entradas de FAQ — salvo que hayas ajustado length_high a propósito. Y omítelo si necesitas una dependencia activamente mantenida por motivos de compliance o compras, que es una restricción real incluso cuando el código funciona.
Si decides usarlo, calibra length_high con una muestra de validación etiquetada en lugar de copiar el valor por defecto o una regla de percentil. Prueba varios umbrales plausibles y mide tanto el recall de artículo como la precisión frente a contenido accesorio; bajar la barrera puede recuperar prosa corta, pero también promocionar bloques vecinos no deseados.
Dónde encaja una API gestionada
jusText trabaja con HTML que ya tienes, igual que todo en esta comparación. Ninguna de estas bibliotecas descarga una página, renderiza JavaScript ni gestiona una capa anti-bots.
Para ver los mismos fixtures en los seis extractores, consulta la comparación de extracción entre seis bibliotecas.
Un servicio alojado de descarga/renderizado/extracción, incluido nuestro propio Thunderbit, cubre otra frontera de responsabilidad. Thunderbit no se evaluó aquí. La distinción es entre clasificación de prosa a partir de HTML ya suministrado y un servicio que obtiene y procesa una URL; este artículo no ofrece una comparación de calidad con la misma métrica.
La lectura honesta: las stoplists multilingües de jusText son una capacidad real y gratuita. Si tu problema es obtener páginas en muchos idiomas más que clasificar la prosa dentro de ellas, hablamos de una compra distinta.
Para el panorama general, nuestro resumen de APIs de web scraping cubre opciones alojadas y el pilar de scrapers de código abierto cubre alternativas autogestionadas. Si la salida va a un modelo, convertir HTML a Markdown en Python es donde más fidelidad suele perderse.
Prueba Thunderbit para extraer datos web
¿Deberías usar jusText?
Es una buena opción si el corpus es multilingüe o si su distribución de longitudes de párrafo se beneficia de un ajuste explícito de umbrales. Valida ambas cosas antes de desplegarlo.
Las 100 listas de stopwords incluidas son una característica real de diseño, pero no se probó la precisión multilingüe. El precipicio del umbral se puede ajustar; si conviene bajarlo o no depende del recall y de la precisión frente a contenido accesorio medidos sobre una muestra etiquetada.
En este conjunto sintético en inglés y con los valores por defecto, newspaper4k no filtró ninguna unidad etiquetada de contenido irrelevante y recuperó 0.9865 de las unidades de artículo. Eso lo convierte en un candidato para esta carga de trabajo, no en una recomendación universal de sustitución.
Prueba Thunderbit para extraer datos web Get Started Free
Preguntas frecuentes
¿Por qué jusText devuelve una cadena vacía en vez de una extracción parcial?
Porque su clasificador trabaja en dos pasadas. Un párrafo solo obtiene la clase good por encima de length_high (200 caracteres por defecto); la segunda pasada luego promociona bloques vecinos neargood. Si ningún párrafo supera el umbral no hay semilla, así que todos los candidatos se degradan a contenido accesorio y el resultado queda vacío. Está diseñado como un sistema de todo o nada, no como un fallo al buscar una respuesta parcial.
¿jusText está abandonado? El último push en el repositorio y la última versión en PyPI fueron ambos el 2025-02-25, es decir, diecisiete meses antes de las pruebas, y los clasificadores de PyPI se detienen en Python 3.9. Pero se instaló y funcionó en Python 3.14.2 sin excepciones, y 9 incidencias abiertas no representan una cola enorme. Lee esas fechas como riesgo de mantenimiento, no como prueba de comportamiento defectuoso; el riesgo práctico es que quizá tengas que arreglar tus propios errores.
¿Por qué filtra más contenido irrelevante que Readability? En este conjunto de fixtures, los bloques vecinos con apariencia de prosa cruzaron el límite de salida bajo las reglas de contexto por defecto. La promoción depende de la clase del bloque, la distancia y el contexto, no ocurre automáticamente con cualquier vecino. El resultado medido fue una fuga del 47% en unidades de contenido irrelevante y 74 tokens contaminantes con los valores por defecto.
¿Cómo lo uso para otro idioma?
justext.justext(html, justext.get_stoplist("German")). get_stoplists() devuelve las 100 disponibles. La stoplist es la entrada léxica específica del idioma para un clasificador que también usa longitud del párrafo, densidad de enlaces, segmentación derivada del HTML, distancia a encabezados y contexto de bloques vecinos. Eso cambia la entrada lingüística; por sí solo no valida la calidad de extracción en alemán.
¿Qué no se probó aquí?
Nada de páginas reales: todo esto son fixtures controlados con unidades etiquetadas. La capacidad multilingüe, que es el principal argumento de la biblioteca, se inventarió con 100 stoplists, pero no se puntuó sobre texto no inglés. Los casos límite de codificación, aunque jusText expone los parámetros encoding, default_encoding y enc_errors. Y max_heading_distance y los dos umbrales de proporción de stopwords se dejaron en sus valores por defecto durante toda la prueba.


