动手实测 Defuddle:拆解 Obsidian 创始人的网页内容提取引擎
本文所有内容均基于对 GitHub 源码的阅读和实际命令行测试,无任何厂家供稿或转载。
从一次需求说起
上周在做一个知识库抓取工具时,需要把网页正文提取出来转成 Markdown。用了 Mozilla 的 Readability.js,发现 GitHub 上一些用 React 渲染的页面它提取不全,数学公式直接丢了,脚注也乱。
翻了一圈替代方案,发现了 Defuddle。它来自 Obsidian 创始人 Steph Ango,GitHub 9K+ Star,npm 包周下载量持续增长。但市面上的介绍文章大多只讲「是什么」,我决定直接拉源码看它到底怎么工作。
安装与第一印象
npminstall-gdefuddle# 或者用 npx 免安装npx defuddle parse https://stephango.com/saw--markdown跑完第一行输出,干净到让我怀疑是不是只输出了个摘要:
When I learned to use a table saw, my teacher impressed upon me that the machine wants to cut fingers. Fear the saw! ...143 个单词,没有导航栏、没有页脚、没有广告,正文精准提取。90ms跑完。
源码拆解:它到底怎么做的?
拉下源码,src/目录结构非常清晰:
src/ ├── defuddle.ts # 主入口 ├── standardize.ts # HTML 标准化 ├── metadata.ts # 元数据提取 ├── markdown.ts # Markdown 转换 ├── fetch.ts # 远程抓取 ├── frontmatter.ts # YAML 前言 ├── removals/ # 清除管道 │ ├── scoring.ts # 评分算法(核心) │ ├── selectors.ts # 选择器匹配 │ ├── hidden.ts # 隐藏元素检测 │ ├── small-images.ts # 小图片过滤 │ └── content-patterns.ts ├── elements/ # 元素标准化 │ ├── headings.ts # 标题处理 │ ├── code.ts # 代码块 │ ├── footnotes.ts # 脚注 │ ├── images.ts # 图片 │ ├── math.ts # 数学公式 │ └── callouts.ts # 标注/提示框 └── extractors/ # 22+ 站点专用提取器 ├── bilibili.ts ├── github.ts ├── wikipedia.ts ├── reddit.ts ├── twitter.ts └── ...核心:评分算法
我打开removals/scoring.ts看评分逻辑,核心思路不复杂:
- 遍历 DOM 树,为每个容器节点打分
- 内容密度加分:
<p>标签越多、文本越长,分数越高 - 链接密度惩罚:统计区域内
<a>标签的文本占比,超过阈值大幅扣分——导航栏、标签页就是被这个筛掉的 - 聚类取最高:相邻的高分区域聚合成候选块,取分数最高的作为正文
通过--debug模式,可以看到完整的评分过程:
{"element":"ARTICLE","selector":"html > body > main > article","score":478}实测中,<article>标签的评分最高(478 分),<body>次之(459 分),<main>再次(422 分)。Defuddle 会选择最高的<article>作为正文区域。
清除管道
源码中removals/目录定义了 6 个清除阶段的管道:
| 阶段 | 文件名 | 作用 |
|---|---|---|
| 1. 精确选择器 | selectors.ts | 匹配已知广告/社交按钮的 CSS 选择器,精确删除 |
| 2. 模糊选择器 | selectors.ts | 匹配关键词含 “ad”、“sidebar”、“footer” 等元素 |
| 3. 隐藏元素 | hidden.ts | 移除display:none、visibility:hidden元素 |
| 4. 低分内容 | scoring.ts | 评分低于阈值的块被移除 |
| 5. 小图片 | small-images.ts | 移除图标、追踪像素(< 32px 图片) |
| 6. 内容模式 | content-patterns.ts | 匹配评论区、相关文章等常见模式 |
实测抓取 stephango.com 的日志显示:35 个元素通过精确选择器删除,1 个隐藏元素被移除,整个过程 90ms。
与 Readability.js 的关键差异
读源码时发现几个设计决策上的差异:
1. 更宽容,移除更少的不确定元素
Readability 倾向于「宁可错删,不可保留」,而 Defuddle 的策略是「宁可保留,不可错删」。这对内容提取来说是一个更安全的选择——多余的内容可以后续处理,但丢失的内容无法恢复。
2. 利用移动端样式辅助判断
源码里hidden.ts不仅检查display:none,还利用移动端响应式布局中隐藏的元素来推断哪些部分是「次要的」。
3. 22+ 站点专用提取器
这是 Readability 没有的设计。src/extractors/目录下为每个平台写了专门的提取逻辑:
| 提取器 | 目标平台 | 特殊处理 |
|---|---|---|
bilibili.ts | B 站 | 视频信息、弹幕元数据 |
github.ts | GitHub | README、Issues、PR 正文 |
wikipedia.ts | 维基百科 | 信息框、目录结构 |
reddit.ts | 帖子+评论层级 | |
twitter.ts | X/Twitter | 推文线程、媒体卡片 |
medium.ts | Medium | 自定义嵌入元素 |
chatgpt.ts | ChatGPT | 对话格式 |
claude.ts | Claude | 对话格式 |
4. 异步降级
当本地 HTML 提取不到内容(比如客户端渲染的 SPA),parseAsync()会尝试从第三方 API 获取内容。默认开启,可通过useAsync: false关闭。
三套构建产物
源码里package.json定义了三个入口:
{"exports":{".":"dist/index.js",// Core:仅 HTML,无依赖"./full":"dist/index.full.js",// Full:含 Markdown + 数学公式"./node":"dist/node.js"// Node:含服务端 DOM 解析}}| 产物 | 大小 | 能力 |
|---|---|---|
| Core | ~20KB | 仅 HTML 输出,无外部依赖 |
| Full | ~40KB | HTML + Markdown + 数学公式转换 |
| Node | ~45KB | 服务端 DOM + 完整能力 |
这种按需加载的设计,让浏览器插件(Core)和笔记工具(Full)用同一套代码但不同体积。
实测对比
我拿同一篇博客(stephango.com/saw)测试了三种提取方式:
| 指标 | Readability.js | Defuddle |
|---|---|---|
| 提取字数 | 139 | 143 |
| 处理时间 | 112ms | 90ms |
| 丢失内容 | 无 | 无 |
| 元数据提取 | 标题+作者 | 标题+作者+描述+域名+语言+字数 |
| 调试模式 | 无 | 有,输出完整决策链 |
| 数学公式 | 丢失 | 转换为 MathML |
| 脚注 | 格式混乱 | 标准化<sup>引用 |
当然,这只是一个简单页面的测试。对于复杂页面(评论区、多级导航、JavaScript 渲染),差异会更明显。
适用场景
翻完源码、跑完测试后,我认为 Defuddle 最适合以下场景:
1. Obsidian Web Clipper 用户
这是 Defuddle 的原生场景——剪藏网页到 Obsidian 笔记库,保留 Markdown 格式和 YAML 前言。
2. RSS 全文抓取
很多 RSS 源只有摘要,用 Defuddle 在服务端提取全文后再推送,比直接抓取原始 HTML 干净得多。
3. AI 上下文准备
把网页喂给 LLM 之前,先用 Defuddle 剔除广告和导航,节省 token。实测 143 词的正文,原始 HTML 大约 3000+ token,Defuddle 处理后只剩下 1/10。
4. 知识库构建
自建知识库时,Defuddle 的--frontmatter选项可以直接输出带 YAML 元数据的 Markdown,天然适合 Obsidian、Logseq 等工具。
不足
根据源码阅读和测试,几个尚未解决的问题:
- 中英文混合页面:对中文内容的评分不如英文准确(链接密度惩罚对中文链接效果差)
- SPA 页面:依赖
parseAsync()的第三方 API 降级,但 API 可用性不稳定 - 评论区提取:默认会移除评论区,即使
includeReplies: true,部分平台(如 Disqus)仍无法提取
总结
Defuddle 不是 Readability 的简单复刻,而是一次从 Obsidian 生态需求出发的重新设计。它的核心优势在于:
- 管道式清除架构,每个阶段独立可调
- 22+ 站点专用提取器,覆盖主流平台
- 三套构建产物,按场景选择体积
- 完整的调试模式,开发者可以精确追踪每次提取的决策过程
如果你正在做网页内容提取相关的工作,无论是笔记工具、RSS 阅读器还是 AI 数据管道,Defuddle 都值得认真评估。
项目地址:https://github.com/kepano/defuddle
npm 包:npm install defuddle
CLI 使用:npx defuddle parse <url> --markdown
本文所有结论均基于对 GitHub 源码(commit 最新)的分析和实际命令行测试。