> [!NOTE] > 本文档由 WeHub 基于上游 README 翻译整理,属于社区翻译,非官方中文文档。 > [English](./README.en.md) · [原始项目](https://gitea-dev.autobee.pro/frostbyte_neo/kage) · [上游 README](https://gitea-dev.autobee.pro/frostbyte_neo/kage/blob/HEAD/README.md) > 原作者、版权与许可证归属以原始项目及本仓库 LICENSE 文件为准。 # kage [![ci](https://github.com/tamnd/kage/actions/workflows/ci.yml/badge.svg)](https://github.com/tamnd/kage/actions/workflows/ci.yml) [![Release](https://img.shields.io/github/v/release/tamnd/kage)](https://github.com/tamnd/kage/releases/latest) [![Go Reference](https://pkg.go.dev/badge/github.com/tamnd/kage.svg)](https://pkg.go.dev/github.com/tamnd/kage) [![Go Report Card](https://goreportcard.com/badge/github.com/tamnd/kage)](https://goreportcard.com/report/github.com/tamnd/kage) [![License](https://img.shields.io/github/license/tamnd/kage)](./LICENSE) **kage**(影,"shadow")会将网站克隆到一个可离线浏览的文件夹中,并剔除所有脚本。它会在真实的无头 Chrome(headless Chrome)中打开每个页面,等待页面稳定后,快照人类所见到的 DOM,然后删除所有 JavaScript,并将 CSS、图片和字体下载到本地路径。最终落盘的内容看起来与线上站点一致,且不会执行任何代码。 [安装](#install) • [快速开始](#quick-start) • [命令](#commands) • [克隆](#clone) • [打包](#pack-it-into-one-file) • [双击应用](#a-double-click-app) • [原生窗口](#a-real-window-not-a-browser-tab) • [工作原理](#how-it-works) ![kage 克隆 paulgraham.com、打包为单个文件并离线提供访问的演示](docs/static/demo.gif) 你早已熟悉这个问题。你对想保存的页面点击「另存为」,六个月后打开却发现一片空白、转圈不停,或者副本仍在尝试连接早已不存在的分析服务器。这页面从未真正属于你。它只是别人 JavaScript 的瘦客户端。 kage 走了另一条路。它驱动真实浏览器,让页面完成该做的一切,抓取最终结果,然后从中剥离所有脚本。无追踪、无网络调用、无意外。只有 `.html` 文件,你可以直接从磁盘打开、交给朋友,或打包成单个文件后十年都不用管。 完整文档与指南见 **[kage.tamnd.com](https://kage.tamnd.com)**. ## 安装 ```bash go install github.com/tamnd/kage/cmd/kage@latest ``` 更喜欢预编译二进制?可从 [releases](https://github.com/tamnd/kage/releases). 获取压缩包、`.deb`/`.rpm`/`.apk` 或校验和。也可以交给包管理器: ```bash # Homebrew (macOS) brew install --cask tamnd/tap/kage # Scoop (Windows) scoop bucket add tamnd https://github.com/tamnd/scoop-bucket scoop install kage # apt (Debian, Ubuntu) curl -fsSL https://tamnd.github.io/linux-repo/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/tamnd.gpg echo "deb [signed-by=/usr/share/keyrings/tamnd.gpg] https://tamnd.github.io/linux-repo/apt stable main" | sudo tee /etc/apt/sources.list.d/tamnd.list sudo apt update && sudo apt install kage # dnf (Fedora, RHEL) sudo dnf config-manager --add-repo https://tamnd.github.io/linux-repo/dnf/tamnd.repo sudo dnf install kage ``` 或者不必自行安装 Chrome,使用捆绑了 Chromium 的容器镜像: ```bash docker run --rm -v "$PWD/out:/out" ghcr.io/tamnd/kage clone paulgraham.com ``` kage 驱动真实浏览器,因此主机上需要 Chrome 或 Chromium。它会自动查找系统安装;也可用 `--chrome` 或 `KAGE_CHROME` 环境变量指向特定路径。容器无需额外配置。 Shell 补全已内置:`kage completion bash|zsh|fish|powershell`。 ## 快速开始 我们来镜像 Paul Graham 的文章,这样你可以在飞机上、没有 Wi-Fi 的笔记本上,或在站点终于改版后的 2050 年阅读它们: ```bash # 1. Clone the site into $HOME/data/kage/paulgraham.com/ kage clone paulgraham.com # 2. Read it back offline in your browser kage serve $HOME/data/kage/paulgraham.com # open http://127.0.0.1:8800 ``` 这就是完整流程。每篇文章、每张图片、每份样式表,都冻结在你的磁盘上,零网络即可运行。接下来两步可选但很方便:将整个镜像折叠成单个文件,并在独立窗口中打开。 ```bash # 3. Squeeze the mirror into a single shareable file kage pack paulgraham.com # -> paulgraham.com.zim kage open paulgraham.com.zim # 4. Or into one executable that *is* the site kage pack paulgraham.com --format binary -o paulgraham ./paulgraham # serves itself, needs nothing installed ``` ## 命令 | 命令 | 作用 | | --- | --- | | `kage clone ` | 在无头 Chrome 中渲染站点,并写入可浏览、无脚本的镜像 | | `kage serve [dir]` | 通过本地 HTTP 服务器预览已克隆的文件夹 | | `kage pack ` | 将镜像折叠为单个 ZIM 归档、自包含查看器二进制文件或双击应用 | | `kage open ` | 提供已打包 ZIM 的离线阅读服务 | ## 克隆 ```bash # The whole site, into $HOME/data/kage// kage clone https://paulgraham.com # Just the first 50 pages, two links deep, for a quick taste kage clone paulgraham.com --max-pages 50 --max-depth 2 # Only one section of a bigger site kage clone go.dev --scope-prefix /doc # Pull in subdomains too, and scroll each page to trip lazy-loaded images kage clone example.com --subdomains --scroll # Come back next month and re-render in place to catch new essays kage clone paulgraham.com --refresh ``` 克隆是一次礼貌的广度优先爬取。它读取 `robots.txt`,从 `sitemap.xml` 播种,除非你另行指定,否则始终停留在种子主机上。它还固执地保持幂等:每个页面以其写入的文件为键,因此同一篇文章无论通过 http 还是 https 访问、有无尾部斜杠,都只会抓取一次。按 Ctrl-C 会在退出时保存进度;再次运行会从停止处继续。`--refresh` 会原地重新渲染,`--force` 会清除该主机并从头开始。 你实际会用到的标志: | 标志 | 默认值 | 含义 | |------|---------|---------| | `-o, --out` | `$HOME/data/kage` | 输出根目录;镜像落在 `//` | | `-p, --max-pages` | `0` | 抓取 N 页后停止(0 = 无限制) | | `-d, --max-depth` | `0` | 跟随链接的深度(0 = 无限制) | | `--scope-prefix` | | 仅爬取以此前缀开头的路径 | | `--subdomains` | `false` | 将种子主机的子域视为在范围内 | | `--exclude` | | 要跳过的路径前缀(可重复) | | `--scroll` | `false` | 自动滚动每页以触发懒加载 | | `--workers` | `4` | 同时渲染的页面数 | | `--no-robots` | `false` | 忽略 `robots.txt`(礼貌爬取) | | `--crawl-delay` | `0s` | 覆盖 robots.txt;`Crawl-delay` 为页面启动间隔 | | `-f, --force` | `false` | 先删除该主机已有的镜像 | | `--chrome` | | Chrome/Chromium 二进制文件路径 | `kage clone --help` 包含其余选项,包括渲染时序、并发与资源大小调节。 ### 提供访问 `kage serve` 会在已克隆文件夹上运行轻量静态文件服务器,使链接与资源像真实主机上一样解析: ```bash kage serve $HOME/data/kage/paulgraham.com # open http://127.0.0.1:8800 ``` ## 打包成单个文件 镜像是文件夹,便于浏览,却不便于搬运。复制成千上万个小文件很慢,「给你这个目录」也不是好送的礼物。`kage pack` 将整个镜像折叠为一个制品,形状由你选择:开放的 ZIM 归档,或本身就是站点的单个可执行文件。 ### 单个 ZIM 文件 ```bash kage pack paulgraham.com # -> paulgraham.com.zim kage open paulgraham.com.zim ``` ZIM 是专为此而生的开放文件格式:将整个网站(或整本维基百科)压缩、索引后塞进一个只读文件。kage 将整个镜像写入其中,文本经 zstd 压缩,媒体原样存储。它是 [Kiwix](https://kiwix.org), 背后的格式——人们用这一离线内容项目把维基百科、Stack Overflow 和 Project Gutenberg 带上船、带进没有互联网的教室、装进手机度过长途飞行。由于该格式是 documented standard(有文档的标准),而非 kage 的发明,你今天制作的 `paulgraham.com.zim` 多年后仍可在任何 ZIM 阅读器中打开。 所以你并不被锁定在 kage 上。`kage open` 是重新接入的最快方式,但同一个文件也适用于更广泛的 Kiwix 生态系统: ```bash kage open paulgraham.com.zim # read it back with kage kiwix-serve paulgraham.com.zim # or serve it with Kiwix at http://localhost ``` 你也可以在 [Kiwix 桌面应用](https://kiwix.org/en/applications/), 中双击该文件,或在 [Android 或 iOS 版 Kiwix](https://kiwix.org/en/applications/) 上加载它,在手机上阅读你的镜像。有一点需要注意:kage 会写入结构合法、带有标准元数据的归档文件,但不会构建 Kiwix 官方包自带的全文搜索索引,因此浏览和点击在各处都能正常工作,但阅读器内的搜索功能会受限。 打包是确定性的。同一镜像始终会生成字节级完全相同的文件,归档 UUID 由内容派生而非随机生成,因此包可以安全地进行校验和与缓存。裸主机名会相对于默认输出目录解析,因此 `kage pack paulgraham.com` 在 `kage clone paulgraham.com` 之后就能直接使用。 ### 自包含二进制文件 `--format binary` 将归档粘接到 kage 的副本上,生成单个可执行文件,运行后即可离线提供该站点。你把它发给任何人都无需安装任何东西:不需要 kage,不需要 ZIM 阅读器,什么都不需要。 ```bash kage pack paulgraham.com --format binary -o paulgraham ./paulgraham ``` 附加的归档与平台无关;只有基础可执行文件携带架构信息。默认情况下 kage 会追加到自身,因此你会得到适用于运行它的那台机器的查看器。将 `--base` 指向为其他操作系统构建的 kage(从 [release](https://github.com/tamnd/kage/releases); 获取,每个平台都会提供)即可在你自己的机器上生成适用于该平台的查看器。kage 会读取基础可执行文件的头部以确定目标平台,因此 Windows 查看器会自动获得 `.exe` 名称: ```bash # Sitting on a Mac, build a Windows viewer kage pack paulgraham.com --format binary --base kage-windows-amd64.exe # -> paulgraham.exe ``` 代价是体积。该二进制文件包含完整的 kage,因此无论镜像多小,体积都约为 13 MiB 加上站点内容。如果你只需要内容,ZIM 要精简得多。 ### 双击即可运行的应用 裸二进制文件在终端中很好用,但在文件管理器中双击时体验不佳:macOS 会在站点后面打开一个 Terminal 窗口,Windows 则会在旁边闪现一个控制台。添加 `--app`,kage 会将同一查看器封装为正规的桌面应用,双击即可打开站点,无需终端,并使用镜像自身的 favicon 作为图标。 在 macOS 上你会得到一个真正的 `.app` 包: ```bash kage pack paulgraham.com --app # -> paulgraham.app open paulgraham.app # or double-click it in Finder ``` 在 Linux 上,将 `--base` 指向 Linux 版 kage,你会得到一个带 `.desktop` 启动器(`Terminal=false`,因此没有控制台)的 [AppImage](https://appimage.org)-style `.AppDir`。如果已安装 [`appimagetool`](https://github.com/AppImage/appimagetool),kage 会将其合并为单个可双击的 `.AppImage`: ```bash kage pack paulgraham.com --app --base kage-linux-amd64 # -> paulgraham.AppDir (+ .AppImage) ``` kage 会从镜像中提取 favicon 来查找图标(优先使用较大的 `apple-touch-icon.png`,否则回退到 `favicon.ico`);传入 `--icon some.png` 可覆盖它。将 `--app` 与 `webview` 基础(见下文)配合使用,双击会打开原生窗口而非浏览器,这才是完整的「像一个应用」效果。 Windows 不需要打包,因为单个 `.exe` 本身就是应用。问题在于控制台窗口。发布版提供了一个 `kage__windows-gui_.zip`,其二进制链接到 GUI 子系统,因此打包在其上的查看器打开时背后不会出现控制台: ```bash # Build a console-free Windows viewer (from any OS) kage pack paulgraham.com --format binary --base kage-windows-gui-amd64.exe # -> paulgraham.exe ``` ## 真正的窗口,而非浏览器标签页 默认情况下,打包后的二进制文件会打开系统浏览器,这意味着站点会作为又一个标签页出现,地址栏一应俱全,挤在你已经打开的 47 个标签旁边。使用 `webview` 标签构建 kage,它会在自己的窗口中打开站点,底层使用操作系统的 WebView(macOS 上为 WKWebView,Windows 上为 WebView2,Linux 上为 WebKitGTK)。Paul Graham 的文章,离线阅读,放在一个看起来、用起来都像真正应用的东西里: ![在原生 kage 窗口中离线提供 paulgraham.com](docs/static/webview.png) ```bash make build-webview # or: CGO_ENABLED=1 go build -tags webview ./cmd/kage kage pack paulgraham.com --format binary --base bin/kage -o paulgraham ./paulgraham # opens a window, no browser in sight ``` 此构建需要 cgo 并链接平台 WebView,因此仍为可选。默认构建为纯 Go(`CGO_ENABLED=0`),预构建的发布二进制文件会打开浏览器,这使交叉编译的发布保持简单。`kage open` 遵循同一标签,因此使用 `-tags webview` 构建时,也会在原生窗口中显示 ZIM。 ## 工作原理 ``` seed URL ─▶ headless Chrome ─▶ final DOM ─▶ strip JS ─▶ localise assets ─▶ disk (render) (snapshot) (sanitize) (rewrite links) ``` 一组 Chrome 标签页负责渲染页面;另一组通过纯 HTTP 获取资源。每个 URL 都会确定性地映射到本地路径,因此在它们指向的资源下载完成之前,链接就已经被重写。输出如下所示: ``` paulgraham.com/ ├── index.html # the home page, scripts stripped ├── greatwork.html # /greatwork.html, an essay ├── _kage/ # reserved: assets and crawl state │ ├── paulgraham.com/site.css # localised stylesheet (url() rewritten) │ ├── paulgraham.com/pg.png │ └── state.json # visited set, for resuming └── ... ``` `pack` 沿用同一思路:镜像中的链接已经是镜像相对路径,这些路径与归档内容条目一一对应,因此在已提供服务的页面中点击会直接命中正确的条目,完全无需重写。 ## 从源码构建 ```bash git clone https://github.com/tamnd/kage cd kage make build # -> bin/kage (pure Go, opens the browser) make build-webview # -> bin/kage with the native-window viewer (needs cgo) make test # full suite, including the Chrome-driven end-to-end tests make test-short # skip the tests that launch a browser ``` 仓库按职责划分: ``` cmd/kage/ thin main: pins the main thread, then hands off to cli.Execute cli/ the cobra command tree and flag wiring clone/ the crawl: frontier, render workers, asset workers, resume state browser/ headless Chrome control and DOM snapshotting sanitize/ strip scripts, handlers, and javascript: URLs from the DOM asset/ download and localise CSS, images, and fonts urlx/ the deterministic URL-to-path mapping zim/ a pure-Go ZIM reader and writer pack/ mirror to ZIM or self-contained binary, and the offline HTTP handler viewer/ present a served site: system browser, or native window (webview tag) docs/ the tago documentation site ``` ## 发布 推送版本标签后,GitHub Actions 会运行 GoReleaser,构建归档文件、`.deb`/`.rpm`/`.apk` 包、捆绑 Chromium 的多架构 GHCR 镜像、校验和、SBOM 以及 cosign 签名: ```bash git tag v0.1.1 git push --tags ``` 镜像标签不带 `v` 前缀(`ghcr.io/tamnd/kage:0.1.1`)。Homebrew 和 Scoop 步骤会在其 token 存在之前自动禁用,因此首次发布无需额外密钥即可工作。 ## 许可证 MIT。参见 [LICENSE](LICENSE)。