SingleFile 原本是个浏览器扩展,一键把当前网页存成单个 HTML 文件。single-file-cli 是它的命令行版:给一个网址,它调用 Chrome 真实渲染页面,再把样式、图片、字体全部内联进一个文件。
实测三个不同复杂度的页面:极简页面 7.25 秒存成 2777 字节,本站文章页 10.15 秒存成 669714 字节,维基百科条目 11.47 秒存成 404328 字节。三份存档里的外部图片引用都是 0 个。
npm 装完就能用,但 Node 22 上有个坑
npm install single-file-cli
node --import ./sf_polyfill.mjs node_modules/single-file-cli/single-file-node.js \
--browser-executable-path "C:\Program Files\Google\Chrome\Application\chrome.exe" \
--browser-wait-delay 2000 https://example.com out.html
如果 Node 版本是 22 或 24,直接跑会报错 CloseEvent is not defined 并退出。原因是它依赖的 puppeteer 假设 Node 有全局 CloseEvent,而这两个版本都没有。
解决办法是启动前先补一个:
if (typeof globalThis.CloseEvent === "undefined") {
globalThis.CloseEvent = class CloseEvent extends Event {
constructor(type, init = {}) {
super(type, init);
this.code = init.code || 0;
this.reason = init.reason || "";
}
};
}
把这段存成 sf_polyfill.mjs,用 node --import 指向它启动就行。路径要写成 file:/// 开头的绝对地址,相对路径在 ESM 里会报 ERR_UNSUPPORTED_ESM_URL_SCHEME。
它运行时 0 行输出,跑完直接在指定位置生成文件。确认成功只能看目标文件在不在、字节数对不对。
三个页面,7 到 11 秒
| 页面 | 耗时 | 存档体积 | 原始 HTML | 倍数 |
|---|---|---|---|---|
| example.com | 7.25 秒 | 2777 字节 | 577 字节 | 4.8 倍 |
| 本站文章页 | 10.15 秒 | 669714 字节 | 25694 字节 | 26.1 倍 |
| 维基百科条目 | 11.47 秒 | 404328 字节 | — | — |
| 等待延迟参数 | 2000 毫秒 | — | — | — |
7.25 秒这个底线时间基本是 Chrome 启动加页面渲染的固定成本。参数里给了 2000 毫秒的等待延迟,所以真正的抓取动作大概占一半。
页面越复杂,多出来的时间主要花在下载和编码内联资源上。维基条目有 67 个内联图片,比本站文章页的 11 个多得多,但两者耗时只差 1.3 秒,因为图片本身都不大。
体积是原始 HTML 的 26 倍
本站文章页的原始 HTML 只有 25694 字节,存下来变成 669714 字节。多出来的 640 KB 全是内联进去的图片和样式:
| 指标 | 本站文章页 | 维基条目 |
|---|---|---|
| img 标签数 | 10 个 | 9 个 |
| data URI 数 | 11 个 | 67 个 |
| 指向外部的 img src | 0 个 | 0 个 |
| style 标签 | 4 个 | 16 个 |
| script 标签 | 1 个 | 1 个 |
| 存档体积 | 669714 字节 | 404328 字节 |
26 倍这个数字看着夸张,但它换来的是「不依赖任何外部资源」。图片被 base64 编码进 HTML,体积天生会涨约三分之一,再加上原本压缩传输的图片被解开后重新编码,膨胀更明显。
同样的内容如果只存 HTML 不存图,文件只有 25 KB 左右,但打开一片空白,图全裂。多出来的 640 KB 买的就是这些图。
断网打开,8 张图全在
把维基条目的存档用 file:// 打开,不联外网,9 个 img 标签里有 8 个正常显示。第 9 个的 src 是空的 data:,,它本身就不是图片节点。
title: Web scraping - Wikipedia
images loaded: 8 / 9 (第 9 个 src 是 data:, 空占位)
<img src="data:image/webp;base64,UklGRjwNAABXRUJQV...">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz...">
第一次打开时只显示 4 张,滚动到底部后变成 8 张,原因是页面本身用了懒加载,图片要进入视口才渲染。不是存档漏了资源,是原页面的行为被一起保存下来了。
和 monolith 的分工
站内另有一篇 monolith,同样是「把网页存成一个文件」。两者的区别在渲染方式:monolith 自己解析 HTML 再抓资源,速度快但不执行 JavaScript;SingleFile 走真实 Chrome 渲染,页面上的动态内容能存下来,代价是要启动浏览器、慢一些。
需要存「JavaScript 渲染出来的内容」,选 SingleFile。只是存一个静态文档、想快,monolith 更合适,它没有浏览器启动那 7 秒的固定开销。
留档的忠实度也不同:SingleFile 存渲染后的 DOM,页面长什么样就存什么样;monolith 存原始 HTML 加资源。
什么时候值得用
需要长期留存的资料页,尤其是那些过几个月就改版或消失的文档、公告、论坛帖。存成单个 HTML 之后,不用管对方网站还在不在,双击就能看。
另一个场景是取证:页面上有什么、什么时候存的,669714 字节的单个文件就是全部证据。
不适合批量抓取:每次起一个 Chrome,7 秒起步,100 个页面就是 12 分钟以上。









