jusText 在短段落测试样本上的阈值断崖

最后更新于 August 17, 2026
jusText 在短段落测试样本上的阈值断崖
AI 摘要
On a document whose longest paragraph is 151 characters, jusText at its defaults returns zero characters. Not a partial extraction — an empty string. Lower one threshold so that exactly one paragraph crosses it, and the same document yields 832 characters. Lower it further and you still get 832. On this fixture, the behavior is a cliff rather than a slope. Whether the default is on the wrong side depends on the paragraph lengths and boilerplate distribution in the target corpus. Evaluate jusText for multilingual corpora because the language-specific stoplist surface is explicit and broad.

在一份最长段落只有 151 个字符的文档里,jusText 用默认设置时会直接返回 0 个字符。这不是只截掉一部分,而是整段空字符串。只要把其中一个阈值调低到刚好让一段文字跨过门槛,同一份文档就能输出 832 个字符。再继续调低,结果还是 832。

在这个测试样本里,它的表现更像一道断崖,而不是缓坡。默认值到底合不合适,关键还是看你的目标语料里段落长度和模板内容的分布。

什么是 jusText,以及为什么词汇密度很重要

和这组测试里的其他抽取器相比,jusText 对 语言专属停用词密度 的依赖特别强。一个由大量功能词组成的文本块——比如 theandofwas——更有可能是正文。这个词汇信号不是唯一判断依据:段落长度、链接密度、基于 HTML 的块边界、标题距离、相邻块类别,以及一次上下文感知处理,都会影响最终结果。

官方参考:jusText 官方仓库

System diagram: Two-Pass Paragraph Classification

因为分类器需要语言停用词表,jusText 随包提供了 100 种。在这次对比里,这种明确的语言级词汇特征是它最突出的差异点,不过我们并没有测试多语言质量。

测试版本:3.0.2,BSD 2-Clause,822 个 GitHub stars。Python 3.14.2。

断崖现象,怎么量化

Measured results chart: Characters returned as length_high changes

jusText 的分类器分两轮跑。第一轮是上下文无关判断,把每个段落标成 goodbadshortneargood。第二轮是上下文敏感判断:只有当 neargood 紧挨着一个已经是 good 的块时,才会被提升为 good。而段落要想自己单独拿到 good,必须超过 length_high,默认是 200 个字符

在一份没有任何段落超过 200 字符的文档里,就不会出现“种子”块来触发提升,于是所有 neargood 块都会被当成模板内容,整页结果就空了。

我在这份最长段落为 151 字符的样本上扫了这个阈值:

length_high被判定为 good 的段落数返回字符数
200(默认)00
1508832
1208832
1008832
808832

justext-length-threshold.json

在这个阈值样本里,只要有一段文字跨过门槛,8 个目标段落就会全部开始输出;继续放宽阈值,也不会再增加任何内容。这个轨迹说明,上下文轮会围绕已有的 good 块去提升相邻的 neargood;但这并不意味着在任意页面上,所有邻居都会无条件被提升。

在把结论归因给 length_high 之前,我还把 length_low 与四个值交叉测试,并与 max_link_density 的两个值组合:8 种组合,全部返回 0。改这两个参数都没能在这个样本里恢复输出。

放到整个 22 个样本的集合里,这个规律也成立:当 length_high=200 时,22 个里只有 2 个有输出;降到 150 时是 22 个里 9 个;降到 120 时是 22 个里 15 个

这里有两点不要误读。第一,它并不是说 jusText 抽取效果差——在一页真实自然语言内容上,默认设置下它返回了 1,190 个字符的干净正文,因为真实新闻段落第一次就能超过 200 字符。第二,它也不是说默认值本身就是错的;它只是说明默认值假设段落够长,而你在运行前应该先判断自己的语料是否符合这个假设。

这组测试的另一面:默认设置下模板泄漏更高

在这组带标注的样本里,使用默认设置时,jusText 的模板内容泄漏率是最高的。

全部 22 个样本的正文召回率模板泄漏率内容 token 精确率污染 token 数
Readability1.00000.23530.910935
trafilatura0.98650.05880.94114
newspaper4k0.98650.00000.94520
resiliparse0.90540.05880.93817
jusText0.83780.47060.876074
goose30.82430.00001.00000

sixway-scores.json。同一个样本集、同一个评分器,每个单位都用唯一标记标注,因此恢复结果时可以精确到子串匹配。

System diagram: Leakage and Silence Share a Boundary

47% 的模板泄漏和 74 个污染 token——泄漏率是 Readability 的两倍,污染量更是它的两倍多。这还是在这组带标注的合成样本、默认设置下的诊断结果,不是一个稳定的全局产品排名。

观察到的泄漏和前面提到的断崖现象里的上下文轮是一致的。符合条件的 neargood 块,在周围块类别和距离规则允许时可以被提升;因此,正文旁边像简介或评论这样的“像正文”的内容,也可能跨过边界。这个样本结果展示的是哪些标注块发生了泄漏,而简化后的机制仍然取决于 jusText 的具体上下文规则。

它的召回率是 0.8378,排在倒数第三,而且所有损失都来自阈值:在一篇 129 字符的短文章里什么都没拿到,在一个包含十个短段落的页面里也什么都没拿到,在接近空白的文档里同样什么都没有。

语言停用词表清单

还有 99 个停用词表。

justext.get_stoplists() 会返回 100 种语言。这个分类器从设计上就是按语言参数化的,而不是把英文启发式规则翻译一遍而已;切换语言只需要一个参数:

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 和 goose3 也提供和语言相关的行为,但这次评测并没有对任何抽取器做非英文真值评分。jusText 内置的 100 份停用词表让它非常适合作为多语言评测候选;但仅凭词表数量,并不能证明它在这些语言上的抽取质量,也不能证明竞争者就一定覆盖得更差。

它的 API 只有两个函数和九个可调常量——length_low 70、length_high 200、stopwords_low 0.30、stopwords_high 0.32、max_link_density 0.20、max_heading_distance 200,再加上编码处理。简单、清晰,而且这些内容都写在函数签名里。

安装与速度

pip install justext 会拉取 3 个包——在对比中是最少的——以及 22.4 MiB,而且不到两秒就能装完。

官方参考:PyPI 上的 jusText

包数量site-packages 占用冷启动导入提取 p50
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

install-and-import.json。每个库都运行在各自全新的空虚拟环境中。

3 个包和 22.4 MiB 在这组对比里算是很轻的依赖,而 0.56 ms 的中位提取时间在这个基准里也接近 trafilatura 的 0.51 ms。环境测量并没有按文件类型拆分包体积,所以不能判断磁盘占用里有多少来自停用词表。

维护情况,谨慎回答

这里使用的维护日期指标,是仓库最后一次提交和 PyPI 最后一次发布,二者都是 2025-02-25。那距离测试时已经过去 17 个月。总共 8 个 release,91 个 fork,9 个未关闭 issue,且仓库没有归档。

PyPI 的分类器只写到 Python 3.9。我在 3.14.2 上运行时,它仍然可以安装,导入耗时 0.777 秒,并且在 22 个样本中的 19 个上完成提取,没有抛出任何异常。

所以,元数据看起来比实际情况落后了 5 个 Python 版本,而现实情况是它能正常工作。这里真正有用的区别在于:安静的仓库说明的是 支持状态,并不自动等于 功能失效。对于一个算法本身只是公开的 2011 方法加上一组词表的库来说,“已经完成”本身就是合理状态——剩下可改的东西并不多,而且停用词表不像反爬策略那样容易腐化。

不过,安静仓库也意味着:如果你真碰到 bug,多半得自己修,或者 fork 一份。要把这一点和 9 个 open issue 一起看,因为这并不像是一个被大量未解决问题淹没的库。

内存,以及损坏 HTML 会带来什么影响

这里把两个运维层面的问题分开测量。

更大的压力测试背景见:十个库的内存与畸形 HTML 对比

峰值常驻内存通过 /usr/bin/time -l 测量,每个单元都启动一个全新进程——导入基线表示库在“加载后空转”时的成本,而峰值则包含了文档本身。

运行时导入基线226 KB 峰值10 MB 峰值
html2textpython3.1418.719.971.2
pyquerypython3.1430.333.9172.5
resiliparsepython3.1420.525.1225.1
markdownifypython3.1423.928.9278.5
goose3python3.1444.152.4398.5
cheerionode2266.876.5398.5
justextpython3.1430.336.6431.2
newspaper4kpython3.1452.661.8668.5
trafilaturapython3.1452.564.8927.1
turndownnode2247.868.42947.1

memory-results.json。Python 和 Node 的基线彼此不能直接比较;因为解释器本身就包含在两边。

jusText 的导入基线是 30.3 MiB,在这个 10 MB 样本上的峰值是 431.2 MiB,大约是导入基线的 14.2 倍,也相当于输入大小的 43.1 倍绝对 RSS。单个样本、单个进程并不能说明通用的扩展曲线;Python 和 Node 的基线也仍然不能直接互比。

损坏 HTML。 十二份文档各自只破坏一处——未闭合标签、错误嵌套的行内元素、带空格却没加引号的属性、孤立的闭合标签、完全没有 <html>、重复属性、在标签中间被截断的文档、错误实体、未闭合的 <script>、虚假的字符集声明、包含标记的注释,以及 600 层嵌套——再加上 两个同尺寸的正常对照样本,因为“它什么都没返回”只有在它面对同尺寸的正常文档时也同样沉默,才能说明和损坏程度有关。

justext 在 14 个样本里 0 个抛错,在 13 个样本里什么都没返回,并且在所有损坏样本中只恢复了 0/33 个带标记的 sentinel(malformed-results.json)。那个正常的短对照样本同样返回 0 字符;正常的长对照样本则是唯一一个非空结果,返回了 1,333 个字符。12 个损坏样本全部保持空白,而未闭合 <script> 的情况不纳入 sentinel 存活评分。因此,这一轮只能说明解析器容错性——没有异常——但不能把沉默归因于 HTML 损坏,而不是前面已经测出的尺寸阈值。

优缺点

优点。 100 份语言停用词表,而且分类器确实是围绕这些词表设计的,而不是把英文规则简单翻译过去。对比中依赖包数量最少,只有 3 个包。中位提取只要 0.56 ms。9 个文档化且清晰的调参常量。BSD 2-Clause。虽然 PyPI 分类器只写到 3.9,但在 Python 3.14 上也能正常跑通。

缺点。 在这组合成测试的默认设置下,模板泄漏率最高:47%,污染 token 达 74 个。短段落样本在没有任何段落跨过 length_high 时直接返回空字符串。在这组样本上的召回率是 0.8378。仓库最后一次提交和发布距测试时已过去 17 个月,因此维护归属需要认真考虑。

谁适合用,谁不适合

建议评估 jusText 的场景是多语言语料,因为它的语言专属停用词表暴露得非常明确,而且覆盖面很广。这次测试确实统计了 100 份停用词表,但并没有测多语言质量。若你的部署场景适合 3 个依赖包,并且需要明确的阈值控制,它也值得考虑。

不建议使用 的场景是你按 token 喂模型,而且 74 个污染 token 直接就是成本;或者你的页面大多是短段落——产品简介、列表页、更新日志、FAQ 条目——除非你明确调整过 length_high。如果你因为合规或采购原因需要一个持续活跃维护的依赖,也不建议选它,即使代码本身能用,这也是真实约束。

如果你要用它, 不要直接照搬默认值或百分位规则,而是拿一份带标注的验证样本去调 length_high。把可能的阈值都扫一遍,同时测正文召回率和模板精确率;降低门槛能救回短正文,但也可能把不想要的相邻块一起抬上来。

托管 API 适合放在哪一层

jusText 处理的是你已经拿到的 HTML,就和这次对比中的所有库一样。它们都不会自己抓页面、渲染 JavaScript,也不会处理反爬层。

如果想看同一批样本在 6 个抽取器上的对比,请参见:六个库的抽取对比

托管的抓取/渲染/抽取服务,包括我们自己的 Thunderbit,承担的是另一层责任边界。这里并没有对 Thunderbit 做基准测试。它和这篇文章里的库的区别在于:前者是对给定 HTML 做正文分类,后者是先获取 URL 再处理;本文并没有做同指标的质量对比。

实话实说,jusText 的多语言停用词表确实是一个真实而且免费的能力。如果你的问题是“如何抓取多语言页面”,而不是“如何在多语言页面里分类正文”,那你需要购买的是另一类方案。

更广泛的选型可以看我们的 网页抓取 API 总览开源爬虫总篇。如果输出最终要交给模型处理,在 Python 中把 HTML 转成 Markdown 往往是最先丢失细节的地方。

试用 Thunderbit 进行网页数据提取

要不要用 jusText?

如果你的语料是多语言的,或者段落长度分布适合显式调阈值,那它值得纳入候选。上线前先验证这两个条件。

内置的 100 份停用词表确实是它的设计亮点,但多语言准确率并没有在这次测试中验证。这个阈值断崖是可以调的;至于调低之后是否可接受,要看你在带标注样本上测出来的召回率和模板精确率。

在这组英文合成样本、默认设置下,newspaper4k 没有泄漏任何标注模板单元,并恢复了 0.9865 的正文单元。这让它成为这类工作负载的一个对比候选,但不代表它是通用替代方案。

试用 Thunderbit 进行网页数据提取 Get Started Free

常见问题

为什么 jusText 返回空字符串,而不是抽出一部分内容? 它的分类器分两轮工作。段落只有在超过 length_high(默认 200 字符)时,才能自己拿到 good;第二轮才会把相邻的 neargood 块往上提。如果没有任何段落跨过阈值,就没有“种子”块,所有候选都会退化成模板内容,于是结果就是空的。这是设计上的全有或全无,不是没找到部分答案。

jusText 是不是已经没人维护了? 最后一次仓库提交和最后一次 PyPI 发布都是 2025-02-25——距测试时 17 个月——而且 PyPI 分类器只写到 Python 3.9。但它确实能在 Python 3.14.2 上安装并运行,而且没有异常,9 个 open issue 也不算大量积压。把这些日期当作维护风险,而不是功能失效的证据;实际风险是,你可能需要自己修 bug。

为什么它比 Readability 泄漏更多模板内容? 在这组样本里,默认上下文规则把一些“像正文”的相邻块推过了输出边界。是否提升取决于块类别、距离和上下文,而不是见邻居就自动提。默认设置下测到的结果是 47% 的模板单位泄漏和 74 个污染 token。

如果要处理其他语言,怎么用? justext.justext(html, justext.get_stoplist("German"))get_stoplists() 会返回全部 100 种可用语言。停用词表是这个分类器的语言级词汇输入,但它同时还会用到段落长度、链接密度、HTML 切分、标题距离和相邻块上下文。改语言输入,并不等于自动验证了德语抽取质量。

这里没有测试什么? 完全没有测真实世界页面——这些都是带标注的受控样本。它最核心的卖点是多语言能力,但这里只统计了 100 份停用词表,并没有在非英文文本上评分。虽然 jusText 提供了 encodingdefault_encodingenc_errors 参数,但编码边界情况也没有测试。另外,max_heading_distance 和两个停用词比例阈值在整个测试中都保持默认值。

Ke
Ke
Thunderbit 首席技术官 | 高级数据科学家与机器学习专家 Ke Shen 拥有近十年的机器学习和数据科学经验,毕业于哥伦比亚大学,曾任 Walmart Labs 高级数据科学家。他在 Python、R、Java 和统计学方面拥有深厚且备受同行认可的专业能力,并分享如何将复杂的 AI 算法从理论落地到生产级架构的实战经验。
目录
Thunderbit · AI 网页数据代理

一键 内提取任意页面数据

深受 250,000+ 用户信赖
提供免费方案
从网页到表格
描述你的需求——Thunderbit 的 AI 代理会帮你抓取并导出到 Excel、Google Sheets、Airtable 或 Notion。可免费开始。
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week