goose3 没有返回任何标记过的模板内容,而且还两次空手而归

最后更新于 August 17, 2026
goose3 没有返回任何标记过的模板内容,而且还两次空手而归
AI 摘要
在那些连“漏抓”都能明确界定的测试样本中,goose3 的内容 token 精确率达到 1.0000,而且没有混入任何污染 token。导航、广告、侧边栏、评论、推广文案,一个词都没混进去。六个库对比里,没有哪个能做到这一点。它也拿到了全场最差的文章召回率——在全部 22 个测试样本中只有 0.8243,而 Mozilla Readability 是 1.0000——原因是其中两个样本它直接返回了空字符串。这两个指标会被同一条评分规则绑在一起:空结果不会给条件精确率贡献任何值,而召回率会把漏掉的样本记下来。

在那些连“漏抓”都能明确界定的测试样本中,goose3 的内容 token 精确率达到 1.0000,而且没有混入任何污染 token。导航、广告、侧边栏、评论、推广文案,一个词都没混进去。六个库对比里,没有哪个能做到这一点。

但它也拿到了全场最差的文章召回率——在全部 22 个测试样本中只有 0.8243,而 Mozilla Readability 是 1.0000——原因是其中两个样本它直接返回了空字符串。

这两个指标会被同一条评分规则绑在一起:空结果不会给条件精确率贡献任何值,而召回率会把漏掉的样本记下来。

什么是 goose3

goose3 是 Python 3 版本的延续,最早可追溯到 Gravity Labs 在 Scala 里开发的 Goose,后来又经过 python-goose 这一支。它是一个带元数据的文章提取器,不是纯文本转储工具:你创建一个 Goose,调用 extract(),就会拿到一个 Article 对象,里面大约有二十八个可访问字段——清理后的正文、标题、作者、发布时间、头图、meta 描述、标签、链接、推文等等。

官方参考:goose3 官方仓库

System diagram: From HTML to Article Fields

测试版本:3.1.22,Apache 许可,912 个 GitHub stars,最后一次推送时间是 2026-07-23——在测试时仍在积极维护。Python 3.14.2。

这个 API 只需要两步半:

from goose3 import Goose
g = Goose()
try:
    article = g.extract(raw_html=html)
    text = article.cleaned_text
finally:
    g.close()          # 使用后要显式关闭

这里特别要提一下 close(),因为它太容易忘了,而且不会有任何提醒。这次评测没有跑循环探测去量化如果不关闭会不会留下会话、连接或内存,所以直接说“资源泄漏”证据还不够硬。就这段 API 用法来看,显式关闭应当算是它的生命周期要求。

取舍到底是什么,数据说了算

六个提取器,一个标注过的样本集,一个评分器。每个样本里的每个区块都被标成 articleboilerplate,并带有独一无二的标记 token,所以“有没有恢复这个单元”判断起来是精确的子串匹配,而不是相似度分数。

文章召回率(全部 22 个)模板内容泄漏率内容 token 精确率污染 token 数是否产出结果
Readability1.00000.23530.91093522/22
trafilatura0.98650.05880.9411422/22
newspaper4k0.98650.00000.9452022/22
resiliparse0.90540.05880.9381722/22
jusText0.83780.47060.87607419/22
goose30.82430.00001.0000020/22

sixway-scores.json。召回率是按全部 22 个样本统计;泄漏率和精确率则只看同时包含文章单元和模板单元的 11 个样本。

读精确率这一列时,一定要和最后一列一起看。这里的精确率是以“有输出”为前提的——某个库如果在某个样本上返回空字符串,它对分子和分母都不贡献任何东西,所以在这个平均值里“拒答”是免费动作。分子是提取出来的非停用词 token 与标注文章 token 的多重集合重叠;分母是所有提取出来的非停用词 token。“污染 token 数”统计范围更窄:只计算与标注模板 token 的重叠。某个额外抽出来的 token 如果既不属于标注文章,也不属于标注模板,会拉低精确率,但不会增加这个污染计数;超过文章多重集合数量的重复 token 也会造成同样效果。这就是为什么 newspaper4k 可以显示 0 个污染 token,但精确率仍然低于 1.0000。goose3 的 1.0000 是在 11 个样本里的 10 个上打出来的;Readability、trafilatura、newspaper4k 和 resiliparse 则是在 11/11 个样本上都参与了评分。

不过在这个合成样本集里,1.0000 仍然有意义。在那 10 个被评分的样本里,goose3 一点标注过的模板 token 都没吐出来;Readability 在同样的页面上吐出了 35 个。如果模型要消费这些输出,那就意味着这 10 个样本里它没有在模板内容上浪费 token。但这并不能证明真实网页上也绝对零浪费,而且空结果还可能在后续流程里引入兜底或重试成本。

那两次沉默,分别意味着什么

goose3 恰好在两个样本上什么都没返回。其中一个是可以理解的,另一个则是实打实的限制。

几乎空白的文档。 一个页面只有一个 32 字符的文章单元。goose3 选择不处理。jusText 也是如此。在这组样本里,Readability 在全部 22 个页面上都产出了结果,所以它的表现不能拿来支持 goose3 在这里“沉默”是合理的。是否应该拒绝这种极小文档,取决于调用方对最小内容长度的要求。

System diagram: Treat Empty Output as Failure

整篇文章全由 <li> 元素构成。 六个文章单元,没有一个放在 <p> 标签里。goose3 直接返回空字符串。

第二个结果让我挺意外,因为 goose3 默认配置里明明写着 parse_lists=True。所以我又做了三组配置对照,再加一个正常控制组——因为一次没跑通,并不能直接证明库有问题:

配置仅列表页面<p> 控制组
默认值0 字符937 字符
strict=False0 字符937 字符
parse_lists=True(显式设置)0 字符937 字符

list-only-probe.json

三种配置下全都是 0,而控制组三种情况下都能返回 937 个字符。也就是说,parse_lists=True 的作用,是决定列表能不能保留在已经找到的文章内部——它并不能让候选评分器把“列表本身”当成文章。goose3 的节点评分逻辑需要类似段落的块,才能把正文定位出来;如果一整页正文就是列表,它就找不到可用的正文块。

能被支持的结论更窄一些:像这个合成样本一样的正文——六个文章单元,全是 <li>,没有任何段落型候选——会返回空字符串。把变更日志、API 文档、食谱、FAQ 页面和对比类文章当成真实回放样本是合理的,因为它们可能列表很多;但单靠这一个样本,不能证明这些页面类型普遍都会失败。

只有当调用方检查“非空输出”时,空字符串才是机器可检测的失败。它比“看起来像文本、但其实一个字都没提到正文”更容易拦截,但如果监控只看异常,它依然属于静默失败。生产环境里,调用方必须做最小输出检查,并配一个兜底或明确的失败页记录。

安装和运行环境的真实情况

pip install goose3 会拉下 16 个包,占用 44.3 MiB,耗时大约 6 到 9 秒。冷启动导入在一个全新的子进程里测得:2.181 秒

官方参考:PyPI 上的 goose3

包数量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。每个库都放在独立的空虚拟环境里,所以不会继承别的库的占用。

体积和速度都属于中游。它能在 Python 3.14.2 上干净安装并正常导入,这在这个类别里并不算理所当然。

上线前,先记住三个默认值

System diagram: Three defaults worth knowing before you deploy

直接看随包附带的 Configuration 对象,而不是看文档,会发现一共有 19 个设置项。其中有三个,挺容易让人踩坑。

它会暴露自己。 browser_user_agent 的默认值是 Goose/3.1.22。如果让 goose3 自己去抓网页,所有被访问的服务器都会记录下库名和精确版本号。这很诚实,但也等于留了指纹。最好自己显式设置,或者自己先抓 HTML,再把 raw_html 传进去。

它默认指向 MacPorts 的二进制。 imagemagick_convert_path 默认是 /opt/local/bin/convertimagemagick_identify_path 默认是 /opt/local/bin/identify。在我机器上这两个都不存在——/opt/local 属于 MacPorts,而大多数人并没有装;Homebrew 的二进制通常在 /opt/homebrew。默认值在不开启图片抓取时是无害的(enable_image_fetching 默认也是 False,这个设定很合理),但如果你以为一开就能做头图提取,那这里会悄悄失效。

它默认以英语为中心。 target_language 默认是 en,并且 use_meta_language=True,所以页面如果自己声明了语言,它会跟随;如果没有声明,就回退到英语。做英文内容没问题,处理其他语言时最好显式设置。

其余参数都算正常:parser_class 用的是 lxml,http_timeout 是 30 秒,strict 开着,log_level 是 ERROR,parse_headerskeep_footnotes 也都开着,images_min_bytes 是 4,000。

内存,以及坏 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 的基线彼此不能直接比较;两边都包含了解释器本身。

goose3 的基线是 44.1 MiB,在 10 MB 文档上峰值到 398.5 MiB。放在这里展示的 Python 库里,它的导入基线排第三高,这对冷启动敏感的部署来说值得注意。

畸形 HTML。 十二个文档,每个都只破坏一件事——未闭合标签、嵌套错乱的行内元素、带空格却没加引号的属性、乱入的闭合标签、完全没有 <html>、重复属性、在标签中间截断的文档、错误实体、未闭合的 <script>、撒谎的 charset 声明、包含标记的注释,以及 600 层嵌套——再加上 两个同尺寸的正常控制样本。因为“它什么都没返回”只有在库对同样大小的干净文档也不响应时,才能说明它对畸形 HTML 的处理真的有问题。

goose3 在 14 个样本里0 个报错,在 10 个里返回空结果,在这些畸形样本上总共恢复了 2/33 个标记物(malformed-results.json)。有一个样本不计入这个统计:按照 HTML5 规范,未闭合 <script> 后面的内容都算脚本内容,所以在那里丢掉它是正确的,反而把它恢复出来才算偏差。

优缺点总结

优点。 在 10 个内容保真样本里,只要它有输出,就没有一个标注过的模板 token 混进去。虽然大约二十八个文章字段都能拿到,但准确率只是做了盘点,没有单独打分。图片抓取默认关闭,这个设计合理。Python 3.14 可干净安装。仍在维护。Apache-2.0 许可。如果调用方明确检查,空结果很容易拦下。

缺点。 全场最低的文章召回率,只有 0.8243,而且原因全是“完全没返回”,不是“返回错了”。文章是列表的页面,不管什么配置都会输出空字符串。44.3 MiB 和 2.2 秒冷启动放在 resiliparse 的 21.0 MiB 和 15 毫秒旁边就显得很重。需要手动 close()。两个默认项指向了多数机器上都不对的路径。

适合谁,不适合谁

适合用 goose3 的场景,是把提取出的正文送进模型或数据库,而且标注过的模板内容代价很高,页面又是常规的段落型文章。这个样本集里,只要它回答了,就没有吐出标注过的模板 token。元数据层是有的,但这里没有验证;标题、作者、日期和头图的准确率,还需要单独的真值样本来判断,不能直接当成选型优势。

不适合用它 的场景,是你的语料里列表型内容很多——那你会拿到空字符串,而且没有任何解释。冷启动成本重要的话也别选它,resiliparse 的导入速度快了 145 倍。还有,如果你要求每一页都必须有结果,那它也不合适,因为“没有答案”在这里就是一个真实结果:22 个样本里有 2 个,而且都没有报错,只是静默返回了空字符串。

值得试的搭配方式: 用 goose3 做主提取器,当 cleaned_text 为空,或者低于你设定的最小内容阈值时,再走 fallback。Readability 在这 22 个样本里把所有文章单元都恢复出来了,包括 goose3 那两个空结果样本。这个合成结果支持的是这种架构模式,而不是承诺 fallback 在真实网页上永不漏抓。

托管 API 适合放在哪里

这次评测测试的是 goose3 的 raw_html 提取路径:也就是说,HTML 在 goose3 看到之前就已经先拿到了。goose3 也有自己的网络抓取路径,从它的 User-Agent 设置就能看出来,但这里没有测。JavaScript 渲染和反爬行为也都没测。

如果想看六个提取器在同样样本上的对比,可以看这里:六库正文提取对比

像我们自己的 Thunderbit 这样的托管抓取/渲染/提取服务,解决的是另一层责任边界。Thunderbit 这次没有参加基准测试。关键区别在于:一个是给定 HTML 做文章提取,另一个是由托管服务去抓 URL 并处理页面;本文并没有提供同指标下的性能或质量对比。

更公平的说法是:goose3 的字段集合是固定的,而且就是为文章形态设计的——当你的页面本来就是文章时,这正合适;当你的页面是商品列表时,就不对了。如果你手里已经有 HTML,而且页面确实是文章,goose3 便宜,而且很干净。

如果你看的是托管方案,完整视角可以参考我们的 网页抓取 API 盘点;如果你想看自托管替代品,可以看 开源爬虫总览。如果文本最终要喂给模型,用 Python 把 HTML 转成 Markdown 会更能看清楚 fidelity 到底损失在哪一层。

试用 Thunderbit 进行网页数据提取

所以,goose3 值不值得用?

值得,如果你的工作负载本来就是段落型文章,而且调用方会把空输出视为提取失败,而不是成功。

在这组样本里,goose3 只要有回答,就没有混入任何标注过的模板 token,但也返回了两个空字符串:一个是几乎空白的页面,一个是纯列表正文。这反映的是精确率和覆盖率之间的取舍,不代表它在产品层面就一定是这个脾气。

如果召回更重要,那就配一个带明确最小输出判断的 fallback。Readability 在这 22 个样本里恢复了所有文章单元;newspaper4k 则表现为 0 个标注模板单元泄漏、0.9865 召回率,并且在全部 22 个样本上都有输出。这些结果说明的是,这个合成样本集下它们的默认表现,不是对未知生产流量的承诺。

goose3 的价值,在于“错一个词”的代价高于“少一页”的代价。

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

常见问题

goose3 的完美精确率是真的吗,还是因为它有时直接不答? 两者都有,而且可以拆开看。它在 11 个包含模板内容的样本里只参与了 10 个的评分,所以平均值里少了一个样本——这部分确实是“拒答”造成的。但在那 10 个样本里,它面对的是专门设计得很刁钻的模板内容,结果一个污染 token 都没吐出来,而 Readability 漏出了 35 个。对它有回答的页面来说,这个精确率是真的;召回率那一列才体现出它的拒答行为。

为什么 goose3 在文章是列表的页面上会什么都不返回? 它的候选评分需要段落形状的块来定位正文,而纯 <li> 页面根本没有这种块。默认的 parse_lists=True 并不会改变这一点——我专门试过,再加上 strict=False,三个配置都只得到 0 个字符;而以 <p> 为基础的控制组在三个配置下都返回了 937 个字符。parse_lists 只是决定列表能不能保留在“已经找到的文章”里。

我必须调用 close() 吗? 是的,最好像上面那样用 try/finally 显式关闭。这个评测没有测不关闭时到底会积累什么,所以我不主张把它说成“已量化的循环泄漏”;但它确实说明了 Goose 有一个需要调用方自己管理的生命周期。

goose3 默认发出的 User-Agent 是什么? 默认是 Goose/3.1.22——也就是库名加精确版本号。这个只在它自己去抓网页时才会生效;如果你传的是 raw_html,就完全绕开了这件事。如果你真要让它抓网页,最好自己显式设置 User-Agent;默认值会把你在访问的每台服务器上暴露得很清楚。

这次评测没有测试什么? 完全没有测真实网页——这里都是受控样本。虽然 target_language 是一等配置,但多语言提取没测。元数据字段(标题、作者、日期、头图)只是盘点了,并没有做准确率评分。图片抓取也没测,它默认关闭,而且 ImageMagick 路径指向的是多数机器并没有的包管理器位置。并发或持续负载下的内存行为也没测,吞吐量在高负载下如何同样没测;内存表只测了一个全新进程处理一篇文档。

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