手把手用 monolith 一键离线归档网页

15 次阅读 0 点赞 0 评论 9 分钟原创技术教程

告别浏览器“另存为”的繁琐流程。本文带你实战 monolith,从安装到掌握单页保存、资源过滤、批量归档及动态页面预处理,10 分钟学会将任意网页打包为自包含 HTML,彻底解决离线备份与分享难题。

#CLI工具 #网页归档 #Rust #离线阅读 #效率工具 #开源
手把手用 monolith 一键离线归档网页

别再一个个「另存为」了

上周帮同事整理技术方案参考,对方甩过来十几条链接让我帮忙保存。常规操作是:打开浏览器 → 右键另存为 → 生成一堆 HTML 加资源文件夹 → 打包 zip 发给对方。解压后偶尔还打不开,相对路径乱了。

这个流程太笨了。今天介绍一个 CLI 小工具 monolith,15.2k Star,Rust 编写,零运行时依赖,能把网页连同图片、CSS、JS 全部内联到单个 .html 文件里,离线打开效果跟在线一模一样。

读完本文,你将完成:从安装到实战,把一个带图片、样式、外部脚本的网页完整归档为单文件 HTML,并掌握批量保存、动态页面处理、资源过滤等进阶用法。


前置条件

  • 操作系统:macOS / Linux / Windows 均可
  • 会终端基本操作,无需编程基础
  • 自行编译安装需提前装好 Cargo(Rust 包管理器)

快速安装

monolith 安装包管理几乎覆盖所有主流平台,挑适合你的:

macOS / Linux (Homebrew)

bash 复制代码
brew install monolith

Windows (Winget)

bash 复制代码
winget install --id=Y2Z.Monolith -e

跨平台安装(需 Rust 环境)

bash 复制代码
cargo install monolith

安装完成后验证:

bash 复制代码
monolith -V

看到版本号就准备就绪。首推包管理器安装,因为 monolith 是纯二进制工具,brewwinget 会自动下载预编译版本并放到 PATH,开箱即用,不需要处理编译依赖。内网环境或想用最新特性时,再考虑 cargo install 或从 GitHub Releases 下载预编译二进制。


核心用法:一行命令归档网页

最基础的用法只需要 URL 和输出路径:

bash 复制代码
monolith https://lyrics.github.io/db/P/Portishead/Dummy/Roads/ -o portishead.html

执行过程:

  1. 发起 HTTP 请求获取目标 HTML
  2. 解析 HTML,识别所有 <link> <img> <script> <style> 等引用
  3. 逐一下载资源,转换为 data URL(如 data:image/png;base64,...
  4. 把内联后的资源替换回 HTML 文档,写入输出文件

双击生成的文件,断网状态下打开,内容与在线时完全一致。这就是和浏览器「另存为」的本质区别:不依赖本地资源目录,所有内容打包在一个文件里。

常用选项速查

选项 作用 适用场景
-o FILE 输出到文件 必填
-i 去掉图片 只需文字内容时减小体积
-c 去掉 CSS 纯数据提取
-j 去掉 JavaScript 去除追踪脚本、广告
-I 隔离文档(强制内联所有资源) 最常用的归档模式
-t 30 设置超时 30 秒 网络较差时
-k 跳过 TLS 证书验证 内部测试环境证书不受信

不需要记住所有选项,-I 记住就够了。它表示彻底隔离,强制将 CSS、字体、图片全部转为内联 data URL,生成的 HTML 不依赖任何外部文件。


实战:归档技术文档并过滤广告追踪

假设要保存 Hacker News 首页供离线阅读,但不想带入 Google Analytics 追踪脚本和广告资源。

用域名黑名单过滤第三方资源

bash 复制代码
monolith -I \
  -B -d googleanalytics.com \
  -B -d .google.com \
  https://news.ycombinator.com/ \
  -o hn-offline.html

-B -d <domain> 表示黑名单指定域名,monolith 会跳过从这些域名获取的资源。反过来用 -d(白名单模式)可仅保留特定域名资源。

确认产物:

bash 复制代码
ls -lh hn-offline.html

打开文件,Google Analytics 的 <script> 标签消失了,Hacker News 内容和样式完好。归档体积变成了完全自包含的单个 HTML。


进阶场景

处理 SPA / 动态渲染页面

monolith 不含 JS 引擎,只处理服务端首次返回的 HTML。Vue/React 等单页应用(内容由 JS 渲染),直接用它会拿到空白骨架。

解决方案:让 headless Chrome 先渲染 DOM,再 pipe 给 monolith

bash 复制代码
chromium --headless \
  --window-size=1920,1080 \
  --run-all-compositor-stages-before-draw \
  --virtual-time-budget=9000 \
  --incognito \
  --dump-dom \
  https://github.com \
  | monolith - -I -b https://github.com -o github-rendered.html

这段命令完成了两步:

  1. 无头 Chromium 打开页面,等待 9 秒让 JS 渲染完成,输出最终 DOM(--dump-dom
  2. monolith 从标准输入读取 HTML(-),以指定地址为 base URL,内联所有资源

这是官方推荐的动态内容处理方案。将其写成 shell 函数加入 .bashrc,以后 archive-dynamic <url> 一行搞定。

批量归档多个页面

实际工作很少只存一个页面。用简单 shell 循环实现批量:

bash 复制代码
#!/bin/bash
## archive-list.sh: 逐行读取 urls.txt 并归档
while read -r url; do
  name=$(echo "$url" | md5sum | cut -d' ' -f1)
  echo "[归档中] $url"
  monolith "$url" -I -t 30 -o "archive/${name}.html"
done < urls.txt

文件名用 MD5 值是因为 URL 中可能包含 / ? 等不合法字符。-t 30 保证单次请求最长 30 秒,避免任务卡住。

带认证的页面

bash 复制代码
monolith https://username:password@internal.example.com/dashboard -o dashboard.html

支持 Basic Auth,URL 格式为标准 user:pass@host

走代理

monolith 直接读取环境变量,无需额外参数:

bash 复制代码
export https_proxy=http://proxy.corp.com:8080
monolith https://some.site/ -o saved.html

常见问题与踩坑

Q1: 生成的 HTML 文件很大?

正常。把所有图片、字体、视频全内联后体积膨胀是必然的。只需文字内容时,加 -i -c -j 去掉图片、CSS 和 JS 再试,通常能从几 MB 压缩到几十 KB。

Q2: 某些页面乱码?

-E utf-8 强制指定编码。部分老旧网站声明的 charset 和实际不符,可能解析出错。

Q3: 保存内容跟在线不一致?

大概率是 SPA 或异步加载内容。参考上方「动态页面处理」方案,用 headless Chrome 做预处理。

Q4: piped input 时 -I-b 必须配对

从标准输入读取 HTML(cat local.html | monolith - ...)时,monolith 不知道原始 URL,相对路径资源无法解析。必须用 -b <base-url> 指定 base URL。


总结回顾

今天完成了这些:

  1. 选择适合系统的安装方式,一行命令装好 monolith
  2. monolith <url> -o output.html 完成第一次网页归档
  3. -B -d <domain> 过滤不需要的第三方资源
  4. 用 headless Chrome + pipe 搞定动态页面
  5. 用 shell 循环实现批量归档

monolith 的价值不在功能花哨,而在于把一个常见但繁琐的操作浓缩成一行命令。源码采用 CC0 公共领域许可,可以放心集成到公司项目或个人工具链。

下一步建议:把常用操作封装成 alias 或 Makefile target;需要更复杂的页面调度(定时抓取、条件过滤)时,可参考 monolith 的 Apify Actor 模式,或将其作为下游工具编写 Go / Node 编排脚本。

有什么保存场景搞不定,评论区聊聊。

最后更新:2026-06-21T10:02:56

评论 (0)

发表评论

blog.comments.form.loading
0/500
加载评论中...