Mozilla's Readability 是 Firefox Reader View 背后提取器的独立 JavaScript 移植版。它以 Apache-2.0 包 @mozilla/readability 的形式发布,会从实时 DOM 里挑出文章内容。所以在 Node 环境下,你需要像 jsdom 这样的 DOM 实现。它不会抓取网页、不会执行网页里的 JavaScript,也不会做 schema 提取。
这次测试用的是 npm latest 的 0.6.0 版本,这个版本于 2025 年 3 月 3 日发布。到我在 2026 年 7 月 27 日查看时,仓库已经有 11,361 个 star(最后一次 push 是 2026 年 7 月 9 日,所以 main 比已发布版本领先不少)。我在 Node v22.22.3、jsdom 29.1.1、macOS arm64 上,针对 22 个专门构建的带标签 HTML fixture 做了测试,本文里的所有数据都来自这套环境。实际用下来,它是这个类别里最省心的工具:安装两分钟,不需要二进制文件,不用把浏览器缓存起来,每次重跑输出都一样。它真正有意思的地方不在“怎么操作”,而在于它的失败模式几乎能从源码里的几个常量推出来,其中一个常量的影响甚至比文档写的还大。
在 22 个受控的合成 fixture 里,Readability 成功找回了全部 74 个标注文章块。这个有边界的结果并不代表它从不丢正文:公开的真实网页 benchmark 里,召回率是 0.982,而这里已知的一些问题形态并没有复现。最明显的 fixture 失败,是源码级别的链接密度门槛 0.25 把额外的兄弟内容带了进来。它看起来有多严重,取决于测试环境。
readability.js 到底是什么,以及它不是什么
Readability 本质上是一个基于规则的 DOM 评分流程。它会遍历候选元素,给每个元素分配内容分数,再把这些分数向上传给祖先节点,选出得分最高的子树,最后做一轮清理,把看起来像页面边角料的内容剔掉。这就是它全部的文章提取策略——没有模型、没有训练数据、没有按站点定制规则。正因为这个设计,它才能在从没见过的页面上工作;也正因为如此,它的失败模式可以从源码中预测出来,这才是最有意思的部分。
它不是下面这三样,而且这三点最容易让人搞混:
- 不是抓取器。 它接收的是
document,不是 URL。抓取、重试、反爬、请求头都得你自己处理。 - 不是渲染器。 不执行 JavaScript。你传给它的 DOM 里有什么,它看到的就是什么。
- 不是结构化提取器。 它给你的只有
title、byline、excerpt、content(HTML)、textContent、length、siteName。没有 schema,没有类型化行,也不会返回{name, price}这种结构。
4 个常量干了大部分活

看一遍 node_modules 里的 Readability.js,你会比看任何文档页面都更了解它的行为。这个库的大部分表现,主要由四个机制决定:
- 每个被评分段落的内容分数:
1 + (commaCount + 1) + min(floor(len / 100), 3)。少于 25 个字符的段落根本不计分。分数会按层级传给祖先节点:父节点拿满分,祖父节点拿一半,更深一层的祖先按level · 3处理。 DEFAULT_CHAR_THRESHOLD = 500——“成功解析”的最小文章长度。低于这个值时,会重新跑一遍抓取流程,并减少清理步骤。unlikelyCandidates正则 ——会匹配 class 和 id 里带有comment、footer、menu、related、sidebar、social、sponsor等子串的节点。命中的节点在评分前就会被移除。grabArticle里的兄弟节点追加门槛 ——选出顶级候选节点后,会考虑把它的兄弟节点也加进来。如果兄弟节点自己的分数超过阈值,或者nodeLength > 80 && linkDensity < 0.25,或者nodeLength < 80 && nodeLength > 0 && linkDensity === 0 && 它包含句号,就会被附带进去。
链接密度的计算方式是 Σ(linkText.length · coef) / textLength,其中裸 # href 的 coef = 0.3,其他情况是 1。本文测到的兄弟内容泄漏,就是这个门槛导致的;但它并不是整个提取算法的全部。
环境配置,以及宣传里没讲的依赖
执行 npm install @mozilla/readability jsdom 之后就能跑了。两分钟,不需要二进制文件,不需要安装后下载,也不需要把浏览器塞进缓存。就安装体验而言,这个工具几乎可以算理想。
但“零依赖”说的是算法,不是运行时。Readability 运行在一个真实的 document 上,而在 Node 下,这意味着你得自己提供 DOM 实现——这里用的是 jsdom 29.1.1。jsdom 并不小,对大多数流水线来说,它才是循环里最大的成本,而不是提取本身。这个开销要提前算进去。
还有一个让我白跑了一次的坑:Readability.parse() 会修改传入的 DOM。 如果同一个 jsdom document 调两次,第二次看到的就是被第一次拆过的文档。我的测试程序里,每次解析都会重新生成一个全新的 jsdom。要是你为了省时间在循环里复用 document 对象,那你接下来要排查的就是这个 bug。
我是怎么测试的
我没有把它丢到真实新闻站点上测。真实网页虽然能给你一个分数,但你看不出为什么;而对启发式算法来说,“为什么”才是核心价值。于是我生成了 22 个 HTML fixture,共 91 个带标签块(74 个文章块、17 个样板内容块),并且给每个块里的每个词都加上了该块独有的标识前缀。不同块之间的词汇是互相隔离的,所以提取出的 token 能唯一对应到某一个块,“找回”或“泄漏”都能做精确的成员测试,而不是模糊匹配。
提取步骤和评分步骤是刻意分开的。Node 运行器只输出原始提取文本、isProbablyReaderable 的布尔值,以及测得的链接密度。所有 precision 和 recall 都是在事后根据这些原始文本和标签,用单独脚本计算出来的。这个流程里没有任何指标常量是手工写死的——只有这样,我才相信自己的数据。
然后我把完全相同的字节也交给 trafilatura 2.1.0 跑了一遍,用来做同环境对比。每个 fixture 都解析了三次;22 个全部都在每次运行中返回了字节级一致的文本。
你可以逐步检查每个阶段,而不是只看总表。tests/build_fixtures.mjs 负责生成带标注的 HTML 和 ground truth;tests/run_readability.mjs 记录提取结果和预测器输出;tests/metrics.py 在事后对这些记录打分。原始 Readability 输出、计算后的指标,以及相同输入的对比结果,都保存在 artifacts/raw/。这种分离很重要:一旦结果看起来可疑,你就能判断到底是解析器返回了意外文本、标签集本身有问题,还是评分代码误判了。这个包上的复现只是在检查本文主张是否成立,但它仍然只是测试 harness 级别的验证——不能证明线上真实页面混合后的失败分布也一样。
这里有个必须强调的范围限制:这些是受控的合成页面,不是真实语料库。 权威的真实网页数据来自公开的 article-extraction-benchmark,它对这里测试的完全同一个版本 readability_js 0.6.0 给出的成绩是 word-F1 0.947 ± 0.005(precision 0.914 ± 0.008,recall 0.982 ± 0.003),样本约 181 个真实页面。这个数字我引用了,但不是我自己复现的。
这里使用的是当前版本的基准行,已被替代的历史行已排除。 这些受控 fixture 额外提供了按块拆解的分析,说明哪种内容形态会触发哪条规则,而不是拿它替换公开真实网页语料。
在合成 fixture 包里,召回率是完美的
74/74。22 个 fixture 全部算下来,Readability 没丢掉任何一个带标签的文章块——而在那 11 个同时包含文章和样板内容的干净合成 fixture 中,微平均 token recall 达到 1.000。没有一条文章句子漏掉。
不过这结论要加两个限制:
这些都是干净的、单栏的合成页面。真实文章往往层级更深,中间会插广告,有时还会因为评分伪影而丢掉开头段落——这类问题在 tracker 里是有记录的(#437、#901,以及 #922 里“内容在表格前丢失”的情况)。我的 fixture 没有触发任何一个,所以我不是在说这些问题已经修好了——我只是在说,我这次测试没覆盖到它们。在真实网页上,这个版本的 benchmark recall 是 0.982,而不是 1.000。
不过,结果的方向本身就很有价值。Readability 的问题不是会把你的文章扔掉,而是会把什么一起带进去。
精度数值,以及为什么它需要三个标签来讲清楚

一个数字最好讲,也最难自圆其说。在那 11 个混合 fixture 中,Readability 保留了 17 个样板内容块中的 5 个——泄漏率 0.294。
但这不是真实世界的泄漏率。三种不同设置测的是三件不同的事,只有其中一种能代表普通页面:
| 数字在衡量什么 | 结果 |
|---|---|
| 对抗性加权 fixture 集——11 个混合页面里有 6 个是专门设计来对抗兄弟节点门槛的 | 17 个样板块保留 5 个(0.294) |
那一页更接近真实的页面——一个 \<article\> 主体,外面包着导航、广告横幅、侧边栏、评论、页脚,再加一个 class 中性化的推广块 | 6 个页面 chrome 块中剔除了 5 个,留下 1 个 |
| 约 181 个真实页面,公开 benchmark(不是我跑的) | precision 0.914,recall 0.982,word-F1 0.947 —— readability_js 0.6.0 |
第一行应该理解成压力测试,不是预测。Readability 在真实世界里并不会泄漏 29% 的样板内容。 在那个更接近真实的页面里,凡是带有 unlikelyCandidates 正则能匹配到的 class 的内容——nav-menu、ad-banner、sidebar、comments、site-footer——都被干净地删掉了,5 个全中。唯一幸存的是我故意设计成能绕过这条正则的那个块。
0.25 门槛:样板内容从这里开始止步
兄弟节点追加规则在源码里是写明的。但据我所知,没人测过它到底在哪儿翻转。所以我做了一个梯度实验:把一个 class 中性的 <p class="teaser-block"> 放在 <article> 外面,确保一个四段正文文章稳稳拿到顶级候选位置,然后只改变推广块的长度和链接密度。密度按 Readability 自己的公式计算,且是在运行时测得,而不是先验假设:
| 推广块 | 内文长度 | 是否超过 80 字符 | 实测链接密度 | 结果 |
|---|---|---|---|---|
| 完全无链接 | 126 | 是 | 0.000 | 保留 |
| 一个短链接 | 126 | 是 | 0.143 | 保留 |
| 一个更长的链接 | 126 | 是 | 0.278 | 丢弃 |
| 一半文本带链接 | 126 | 是 | 0.476 | 丢弃 |
| 单句,句尾有句号 | 60 | 否 | 0.000 | 保留 |
| 同样内容,但没有句号 | 59 | 否 | 0.000 | 丢弃 |
源码条件用的是 0.25 门槛;我测到的样本正好把它夹住了,0.143 被保留,0.278 被丢弃。另一条分支则保留了一个 60 字符、且以句号结尾的句子,同时丢弃了一个没有句号的 59 字符版本。文章召回在两组里都是 4/4,所以这些样本只是在隔离精度影响。
放到测试环境之外,这条规则等于在说:紧挨着文章的、长而低链接密度、语气中性的正文,也算文章。 这会把很多不属于正文的东西一起带进去——比如用完整句子写成的“相关阅读”简介、newsletter 推广、编辑备注,或者为了跟踪而把链接去掉后的赞助短文。
如果把它放进 RAG 索引,低链接密度的推广段落可能会被抽成一个 chunk,进而让检索或生成把它当成文章内容。源码规则让这种失败模式完全说得通;但这次评测没有做端到端的检索或模型引用评估。
如果你需要站点级的硬过滤,可以先过滤已知的源 DOM 容器,保留源码节点的祖先关系用于序列化前比对,或者在之后用经过严格验证的文本模式过滤。单看返回的 HTML,可能已经无法判断一个节点最初是不是位于主容器之外。这个兄弟节点门槛不能通过公开参数去调整。
这个 fixture 推翻了 3 个假设
这些 fixture 推翻了三个假设:charThreshold 会拒绝短文章、语义标签是必须的,以及短非散文内容会被丢掉。下面的证据才是关键部分,不需要事先注册什么假设也能成立。
charThreshold = 500 不是悬崖边
坊间常见的理解是,少于 500 字符的文章会直接返回 null。其实不是。我把正文长度从 120 字符扫到 1500 字符,并分别测试了 charThreshold 为 200、500、1000 的情况:
| 正文长度 | 所有阈值下都成功解析 | 提取长度 |
|---|---|---|
| 120 | 是 | 161 |
| 300 | 是 | 342 |
| 460 | 是 | 509 |
| 520 | 是 | 569 |
| 800 | 是 | 841 |
| 1500 | 是 | 1555 |
结果很平。三种阈值设置下,每个正文长度的提取结果都完全一致。这个阈值不是在控制返回值,而是在决定要不要把抓取流程再跑一遍、并移除一些清理标志;在干净页面上没什么可删的,所以两种方式结果都一样。真正返回 null 的边界,是“完全没有可提取文本”。
而这也带来了这里真正的失败,而且比假 null 更糟。我喂给它一个几乎空白的页面——只有一个导航栏和四个词的简介。它返回成功了,而且它返回的“文章”把导航栏也包含进去了。也就是说,在没有真正文章的情况下,Readability 会把样板内容当文章给你。如果你在大规模抓取时把非空结果理解成“这个页面有内容”,那这个假设就是错的。
语义标签并不是关键
我原本以为,去掉语义骨架后召回率会下降。结果是同一篇文章,两种外壳:一种是 <main><article><h1>,带描述性 class;另一种是 <div class="x1">,段落也只是裸 <div>。结果:两种情况下都找回了 4/4 个文章块,也都没有泄漏样板内容。当文章显然是页面里最密集的文本块时,基于长度和逗号的评分就能把它找出来,不需要语义标签帮忙。“Readability 需要 <article> 标签”只是传言。
这个结论也有它诚实的边界:我的页面里只有一个明显的内容块。语义标签真正可能发挥作用的地方,是页面里有两个都很密集、彼此竞争的子树,而这一点我没有测试。
非散文内容会被原样保留
“少于 25 字符的段落不计分”这条规则,让我原本以为表格和图注会有损失。结果还是不对——这条规则影响的是候选节点的评分,不是保留。一旦容器胜出,里面的内容就会一起被带出来:
| 文章内的内容类型 | Readability | trafilatura |
|---|---|---|
| 散文段落(×2) | 保留 | 保留 |
| 数据表格单元格(×2) | 保留 | 保留 |
\<pre\> 代码块 | 保留 | 保留 |
少于 25 字符的单行 \<p\>(×2) | 保留 | 保留 |
\<figcaption\> | 保留 | 丢弃 |
| 总计 | 8/8 | 7/8 |
这正是更强硬的清理器会输掉的一条维度。如果你的页面是文档、教程,或者包含代码块和图注之类内容,Readability 这种“胜出子树全保留”的策略反而是优点。
isProbablyReaderable 说不行,parse() 却说可以

README 建议先调用 isProbablyReaderable(doc) 做一个便宜的预检,再决定要不要完整解析。但在我的测试里,这个门槛拒绝了 3 种不同的页面形态,而 parse() 都能正常处理:
| 页面形态 | 预测器结果 | parse() | 哪个参数能修正 |
|---|---|---|---|
内容只在 \<li\> 元素中 | false | 成功 | 没有——无论 minScore 取 1–80 还是 minContentLength 取 40–200,都还是 false |
| 10 个段落,每个都短于 140 字符 | false | 成功 | minContentLength ≤ 100(minScore 不起作用) |
| 只有 1 段 408 字符的正文 | false | 成功 | minScore ≤ 10(分数约为 16.4) |
| 正常文章(对照组) | true | 成功 | — |
这 3 种失败各有各的原因,而且只有其中两种可以调。<li> 这类是结构性问题:预测器只给 p、pre、article 节点(以及 div > br 的父节点)打分,所以如果页面内容都在列表项里,它就匹配不到任何东西,分数为零,怎么调阈值都救不回来——这个形态在 issue #662 里已经有人报过。多个短段落的情况是 minContentLength 门槛导致每个段落在评分前就被跳过,所以 10 段加起来也是零;把这个值调低就能修好,调 minScore 没用。单段超长的情况则是纯算术:分数是 sqrt(408 − 140) ≈ 16.4,低于默认 minScore 20——一个孤立段落要自己超过门槛,得有 540 个字符。
README 的确提醒过预测器会有假阴性。我想补一句更实用的规则:别把它当成唯一的门卫。 如果某个页面很重要,就直接解析,再检查结果长度。相对于你已经付出的 jsdom 构建成本,parse 并不算贵。
同样的字节,两个提取器
把完全相同的 fixture 交给 trafilatura 2.1.0 跑一遍,比起把两个不同测试床上测出的数字放在一起,读起来更清楚,因为输入是逐字节相同的:
| 指标(11 个混合 fixture) | @mozilla/readability | trafilatura |
|---|---|---|
| 文章块召回率 | 1.000 | 1.000 |
| 保留的样板块 | 5/17(0.294) | 1/17(0.059) |
| Token F1(微平均) | 0.948 | 0.969 |
| 非散文召回率 | 8/8 | 7/8 |
| 超短文章(120 字符)Token F1 | 0.800 | 0.571 |
这两个工具在这些 fixture 上都不能说全面胜出。Trafilatura 保留的兄弟块更少,而 Readability 保留了更多短内容和非散文内容。两者的绝对 token precision 都会被未标注的标题文本拉低,所以按块统计的泄漏数才是更直接的信号。公开的真实网页 benchmark 恰好也把它们的 word-F1 排得差不多,但语料和指标都不同,因此这不能算跨测试床验证。
鲁棒性方面,简单说一下:我还跑了一个故意破坏结构的“孪生页”(未闭合 <p>、错位嵌套的 <b>/<i>,以及一个多余的 </div>),结果召回率依然是 3/3,零泄漏,和结构正确的版本一致。这个功劳主要要归给 jsdom 的 HTML5 tree builder,它会在 Readability 看到页面之前先把乱掉的结构修好。没有任何 fixture 让解析器崩掉。
优点和缺点
优点
- 文章召回能力很强:22 个合成 fixture 里找回了 74/74 个带标签块,混合集上的 token recall 是 1.000。
- 带
unlikelyCandidates类名的页面边角内容剔除很稳定——真实页面上导航、广告横幅、侧边栏、评论和页脚都被清掉了(6 个里去掉 5 个)。 - 非散文内容会被完整保留:表格、
<pre>代码、图注以及少于 25 字符的短行都保住了(8/8),而 trafilatura 丢掉了一个图注。 - 不依赖语义标记——一个被中性化的
<div>文章块,得分和<article>/<main>版本完全一样。 - 不会把短文章误判为无效:干净内容在 120 字符时仍能正常提取,且在
charThreshold200/500/1000 下结果完全一致。 - 完全确定性:22 个 fixture 三次运行返回的文本都一模一样。
- 安装只要两分钟、Apache-2.0 授权,而且 npm 上的版本就是我测试的版本(0.6.0),所以本文没有过时问题。
缺点
- 兄弟节点追加门槛可以被“蹭进去”:长、低链接密度、class 中性的推广文案和文章文本几乎分不出来,只要
linkDensity < 0.25就会被带上。 - 在内容稀薄的页面上,它会把样板内容当成文章返回,而不是
null——我那个几乎空白的 fixture 返回时,导航栏都成了正文。 isProbablyReaderable会在三种不同页面形态上给出假阴性,其中一种怎么调都没用。- 运行时需要完整 DOM——“零依赖”的说法会掩盖 jsdom 的成本,而这个成本才是循环里最大的那部分。
parse()会修改输入文档,所以每个页面都得重新构建 DOM。- 不负责抓取、不负责 JavaScript 渲染,也不输出结构化结果。它只是流水线中的一环,不是整条流水线。
- tracker 里报告的真实网页漏判(开头段落和表格前内容丢失)没有在我的 fixture 中复现,所以我没法判断它们到底是罕见,还是只是我的页面没碰到。
适合谁,不适合谁
如果你手里已经有 HTML,想在纯 JavaScript 里把文章抽出来,而且运行环境是 Node,不想再额外引入 Python 依赖,那就用 Readability。(我没有做正式分布的耗时统计,所以不敢给出速度结论,只能说“真正的循环成本主要是 jsdom 构建,不是提取本身”。)阅读模式、离线文章归档、邮件简报、“简洁视图”按钮、浏览器扩展、带代码块和图注的文档流水线——这些都是它的适用场景,而召回结果说明它确实做得不错。更重要的是,它的行为能直接从源码里读出来;当你需要向同事解释“为什么只有这个块被保留下来”时,这一点非常值钱。
如果你的考核指标是样板内容剔除精度,就别选它;尤其是当你把结果送进 LLM 索引,而一段多余的推广文案可能会变成可检索 chunk 时,更要避开它。如果你的页面内容是客户端渲染的,也别选它,因为它只会读取你交给它的 DOM,不会执行 JavaScript。如果你要的是 {title, price, sku} 这类结构化数据,而不是散文内容,也别选它——没有任何配置能把内容提取器变成 schema 驱动提取器。还有,如果你要处理的页面里,“这个页面到底有没有文章”本身就是个问题,那就不要把非空返回当成答案。
替代方案,以及 Thunderbit 体系在这里的位置
这并不是在否定 Mozilla 维护的一个免费 Apache-2.0 库——Readability 本来就是基础设施,Firefox 里也已经用了很多年,对于 reader-mode 提取来说,它之所以是参考实现,是有原因的。如果你想看更广的工具版图,我在 开源 scraper 总览 里持续更新比较,也在 最佳网页抓取工具 里做了更全面的梳理。
如果想看同一组 fixture 下六个提取器的完整对比,可以看 六库提取比较。
作者说明:Thunderbit 是我们提供的托管方案,负责 URL 级别的渲染与提取。它没有用于这些 fixture,因此这里不包含匹配质量的类比结论。真正要比较的是:你手头是否已经有 DOM,只想做本地文章提取;还是你需要把抓取、渲染和结构化输出作为服务来运行。自托管可以省掉供应商使用费,但仍然要承担基础设施和维护成本。
坦白说,取舍就是这样:Readability 是免费的、透明的,而且完全由你控制——你能直接读到决定输出的那条规则,而托管 API 通常不给你这个可见性。托管栈要花钱,也会把机制藏起来,但它把抓取、渲染、结构化这些你本来要自己搭的环节都包了。如果你想看这个光谱上更偏 AI 的一端,我之前写过 用 AI 抓取任意网站 和 AI 爬虫。到底选哪边,就看你真正想掌握哪些环节。
结论
如果你已经有 DOM,运行环境是 JavaScript/Node,并且更能接受偶尔多带一点兄弟内容,而不是过度删减,那 Readability 就是个值得考虑的选项。在这组 fixture 中,它找回了全部 74 个带标签文章块,还保留了表格、代码和图注。这个结果的边界是合成的单栏页面;公开真实网页的召回率是 0.982,已知的开头/表格邻近漏判没有复现,而且内容稀薄的页面会把样板内容当成文章返回。
关键是要准确理解它的弱点。在这些 fixture 里,它最大的失误面是精度,而且这个问题落在源码里一条明确、写得很清楚的规则上:只要兄弟节点长度超过 80 个字符、链接密度低于 0.25,就会被并入文章,不管它是不是真的正文。我亲眼看着同样的文本在 0.143 和 0.278 之间切换。真实网页 benchmark 给出的 precision 是 0.914,recall 是 0.982。如果你把提取文本送进一个后续还会引用它的索引里,最好同时检查保留下来的样板内容和被漏掉的正文,而不是假设其中一种错误不会发生。
试用 Thunderbit 进行网页数据提取 Get Started Free
常见问题
Mozilla Readability 会把所有样板内容都清掉吗?
不会,而且结果会随你衡量的对象而变化。在公开真实网页 benchmark 中,readability_js 0.6.0 的 precision 是 0.914——也就是说,它返回的内容里大约 8.6% 不是正文。在我那个更接近真实的测试页上,它成功剔除了 6 个页面 chrome 块中的 5 个(导航、广告、侧栏、评论和页脚都没了),只保留了一个 class 中性的推广段落。在我故意加权、专门设计来击穿启发式规则的 fixture 集里,它保留了 17 个中的 5 个——不过这个数字只是压力测试,不是现实世界比例。
在 Node 里用 readability.js 需要 jsdom 吗?
需要,或者需要其他 DOM 实现。Readability 本身是纯 JavaScript,但它运行在一个实时 document 对象上,所以在 Node 下,你得自己提供 DOM——我这边用的是 jsdom 29.1.1。“没有依赖”的说法指的是算法本身,不是运行时。另外要注意,parse() 会修改传入的 document,所以每个页面都应该构建一个新的 DOM,而不是复用同一个。
charThreshold 这个选项到底是干什么的?
它和大多数人以为的不一样。它不会让短文章直接变成 null——我在 120 字符时仍然成功提取到了干净文章,而且在 charThreshold 为 200、500、1000 时,提取长度完全一致。这个阈值控制的是:解析器要不要在去掉清理标志后再重新跑一遍抓取;在干净页面里没什么要删,所以两种结果都一样。真正返回 null 的情况,是页面根本没有可提取文本;而即使是只有导航栏的页面,也仍然返回了非空结果,只不过正文变成了导航栏。
我应该在 parse() 前先调 isProbablyReaderable 吗?
把它当提示,不要当门槛。它在 3 种页面形态上返回了 false,但 parse() 随后都成功了:内容只在 <li> 元素中、10 个都短于 140 字符的段落,以及单个 408 字符段落。<li> 这种情况没法靠调参修复,因为预测器只给 p、pre、article 节点打分;很多短段落的问题需要调低 minContentLength;单个长段落则需要调低 minScore,因为一个孤立段落必须达到 540 个字符才能超过默认阈值。如果某个页面重要,就直接解析,再看结果。
文章提取该选 Readability 还是 trafilatura?
在完全相同的 fixture 字节上,trafilatura 保留的样板内容更少(1/17 块 vs 5/17 块),而 Readability 找回了更多短内容,也保留了 trafilatura 会丢掉的 <figcaption>。怎么选取决于你能容忍哪类错误,以及运行环境。公开 benchmark 是另外一个上下文,不能用来验证这个 fixture 的结果。


