kage:用无头浏览器“渲染后封印“网站,彻底告别 JS 幽灵依赖

📅 2026/7/31 23:25:59 👁️ 阅读次数 📝 编程学习
kage:用无头浏览器“渲染后封印“网站,彻底告别 JS 幽灵依赖

kage:用无头浏览器"渲染后封印"网站,彻底告别 JS 幽灵依赖

核心问题:你保存的网页,真的是你的吗?

每个人都遇到过这种情况:把某篇文章"另存为",几年后打开是一片空白。原因很清楚——现代网页本质上是一个薄客户端,内容由远端 JavaScript 在运行时注入。你存下来的只是一个壳,它需要不断向别人的服务器打电话,才能变成你看过的样子。这不是保存,是借阅。

kage(日语「影」,意为"影子")正面解决的就是这个问题。它由 Go 编写,MIT 开源,核心思路是:先让页面活一次,再把它钉死


关键机制:渲染快照 + 外科手术式去脚本

kage 的巧妙之处不在于抓取,而在于介入时机

传统工具(HTTrack、wget)在 HTTP 层抓取,拿到的是服务器吐出的原始 HTML——对于现代 SPA 或动态加载内容,这基本等于拿到了一个空骨架。而 kage 的流程是:

  1. 用真实的 Headless Chrome 打开页面;
  2. 等待页面完全渲染(包括 JS 执行后动态注入的 DOM);
  3. 快照此刻人类能看到的 DOM
  4. 外科手术式移除所有<script>标签、事件监听、javascript:URL
  5. 将 CSS、图片、字体等静态资源下载到本地路径。

这个"渲染完再截图"的思路,让 kage 相比传统爬虫有了质的差异:它保存的是渲染结果,而非渲染材料。


历史脉络与同类工具对比

工具抓取方式能处理动态内容?输出形式是否保留 JS
HTTrack / wgetHTTP 层,静态下载✗ 基本不行文件夹是(但 JS 无法执行)
浏览器"另存为"当前页面快照△ 当前帧可以单 HTML + 文件夹是(且有外链依赖)
Monolith(Rust)HTTP 层下载 + 内联✗ 无浏览器渲染单 HTML 文件是(全部内联)
SingleFile(浏览器扩展)浏览器内运行✅ 当前页有效单 HTML 文件是(内联)
kageHeadless Chrome 渲染✅ 等待 JS 执行完文件夹 / ZIM / 二进制删除

这里有一个根本的取舍:Monolith 和 SingleFile 把 JS保留并内联,而 kage 把 JS删除。Monolith 在 GitHub 有 13,000+ star,但它不使用 Headless Browser,官方文档明确注明动态内容是其局限。kage 用更重的方案(启动真实 Chrome)换来了渲染能力,但代价是:删掉 JS 之后,一切交互(搜索、登录、地图、路由、评论)也一起消失了。这是设计选择,不是缺陷,前提是你想要的是内容,不是功能


输出格式的分层设计:三个用途,三种形态

# 第一层:克隆为本地文件夹(可检查、可浏览) kage clone paulgraham.com --max-pages 50 --scroll # 第二层:打包成 ZIM 归档(开放格式,可用 Kiwix 生态打开) kage pack paulgraham.com kage open paulgraham.com.zim # 第三层:打包成独立可执行文件(无需任何依赖,双击即开) kage pack paulgraham.com --format binary -o paulgraham ./paulgraham # 跨平台构建:在 Mac 上生成 Windows 查看器 kage pack paulgraham.com --format binary --base kage-windows-amd64.exe

ZIM 是值得单独关注的格式选择。它是 Kiwix 生态的基础格式,支撑着 Wikipedia 离线版、Stack Overflow 离线版在船上、无网络教室里的使用。用开放标准格式意味着:你今天生成的.zim文件,10 年后依然能被任何 ZIM 阅读器打开,你不被 kage 锁定。这个设计决策比功能本身更值得注意。

自包含二进制更激进:把 kage 本身和站点内容打包成一个约 13 MiB + 站点大小的可执行文件,对方什么都不需要安装,运行即可。代价也直接:每个文件都带着一整个 kage,内容很小时极度浪费空间。


交叉验证

信源一:ic.work 独立分析(2026年6月)

ic.work 上的分析文章明确指出,kage 的价值"不是完整复制网站功能,而是冻结可读内容",并指出 ZIM 格式目前不支持全文搜索索引(原文也有此说明),以及跨平台二进制需针对目标平台单独编译——这些局限与原 README 一致,属于认同并补充细节,没有反驳。

信源二:dev.to 对 Monolith 的介绍(2025年6月,13,751 GitHub star)

Monolith 是 kage 最直接的横向竞品,用 Rust 编写,生成单个内联 HTML 文件,但不使用 Headless Browser,官方文档和第三方分析均注明其不处理动态内容。这从侧面验证了原文的立论:在 JS 驱动的现代网页面前,不经过真实浏览器渲染就无法获得完整内容。kage 选择用更重的方案解决这个问题,Monolith 选择不解决。两者适用场景不同,并非 kage 全面优于 Monolith,但在动态内容保存这个维度,原文的判断站得住脚。


诚实的边界

kage 被过度解读的风险在于"克隆整站"这个说法太诱人。以下场景它做不了:

  • 登录墙后的内容:kage 本身无法处理需要身份验证才能访问的页面(除非你自行配置 Cookie);
  • 前端路由 SPA:React/Vue 构建的单页应用,URL 靠 JS 路由管理,kage 的链接追踪会失效;
  • 无限滚动/分页内容--scroll可以触发懒加载,但对真正无限分页的平台(Twitter、Instagram)效果有限;
  • ZIM 全文搜索:打包后无法在 kage 的 ZIM 里做站内关键词搜索,这个功能目前缺失;
  • 动态交互完全丧失:删 JS 是设计目标,但站内搜索、过滤器、折叠/展开这类 UI 行为同样消失,可读性取决于站点的内容结构本身。

还有一个实际问题:kage 依赖 Chrome/Chromium,在 CI 环境或轻量服务器上这是一个不轻的依赖。Docker 镜像打包了 Chromium 解决了这个问题,但又引入了容器依赖。


个人启发:该如何实际应用

这个工具最直接的价值场景是技术文档归档:把你依赖的开源项目文档、API 文档、学习资料克隆下来,打成 ZIM。文档类网站通常是静态生成的,JS 交互少,kage 效果最好,且原始内容本身就值得长期保存。

具体可以做的动作:

  1. 立刻执行一次:选 3 个你最常用但担心消失的技术文档网站(比如go.dev/doc、某个库的旧版文档),运行kage clone [url] --scope-prefix /doc
  2. 选 ZIM 而非二进制:优先打包成.zim,因为格式开放,Kiwix 桌面/移动端都能读,而且你未来某天决定不用 kage 了,内容依然可访问;
  3. 不要对社交媒体站点抱期望:kage 对 SPA 重度依赖的站点效果差,用在内容型网站上。

推演:接下来会怎样

kage 目前处于工具链形成期,不是范式突破,而是把"Headless Browser + 去脚本 + 多格式输出"这条路走通的早期完整实现。可以预见:

  1. ZIM 全文索引会补上,因为这是 Kiwix 生态的标配,没有它与官方内容包的互操作性就差一截;
  2. SPA 站点支持会改善,Headless Chrome 的 CDP(Chrome DevTools Protocol)已经足够强大,可以等待特定路由渲染完成,这只是工程实现问题;
  3. 类似工具会整合 kage 的思路,"先渲染后封印"这条路径会逐渐成为网页归档工具的标准范式,就像 Puppeteer 出现后 SSR 快照方案被广泛采用一样。

延伸思考

  1. "内容归档"与"功能复制"的边界在哪里?kage 选择删 JS 是一个极端但清晰的立场。未来是否存在一种方案,能在删除追踪/网络调用的同时,保留纯前端交互逻辑(比如折叠/展开、本地过滤)?Service Worker 沙箱或许是一个方向。

  2. 网页的长期可访问性谁来负责?kage 是个人工具,Kiwix 是社区方案,Internet Archive 是机构方案。三者覆盖不同的时间尺度和内容规模——但对于个人依赖的小众技术文档,没有任何一个机构会主动帮你保存,这件事只能靠自己。

  3. Headless Browser 渲染作为"内容提取基础设施"的边界在哪?kage 用它做离线保存,其他工具用它做测试、截图、SEO 预渲染。当 Chrome 的 CDP 协议成为事实标准后,基于它的工具生态会走向何方——是被 Google 统一管控,还是成为真正的开放基础设施?


📚 参考来源

  1. GitHub - tamnd/kage: Shadow any website for offline viewing, with the JavaScript stripped out · GitHub