中文 | English
⚠️ 仅供学习与研究使用。 使用前请阅读 免责声明。 请遵守目标站点的robots.txt与服务条款;抓取到的内容仅限本地个人研究, 不得二次分发或公开传播。使用本项目产生的一切责任由使用者自行承担。
把一个网站扒到本地,断网也能打开。命令行工具,零依赖——不需要 npm install,
不需要 Docker,不需要任何前置服务,git clone 完直接能跑。只要有 Node 18+。
所有资源先走本地
本地没有,再去远程请求
请求成功后,缓存到本地
以后再访问,直接读本地
打个比方:它是个会自己进货的小卖部。你要一瓶水,它先翻货架——有就直接给你(毫秒级); 没有就跑去大超市买一瓶,给你的同时把货架补上。下次再要,就不用出门了。
git clone https://github.com/baize-lab/mirrorweb.git && cd mirrorweb
./install.sh装好后 mirrorweb 命令就在 ~/.local/bin 里了。要求 Node ≥18。
卸载就一句 rm ~/.local/bin/mirrorweb——软链而已,不留残渣。
不想装也行,直接
node bin/mirrorweb <子命令>。
在 macOS 上开发和测试;纯 Node 实现、无平台专有调用,Linux 应该也能跑(未系统验证)。
mirrorweb grab https://example.com # 抓
mirrorweb serve --out ./example.com # 起本地服务器,浏览器打开默认直连、不走任何代理,也不需要你本机有任何代理服务。上面两条就是完整流程。
常用选项:
mirrorweb grab https://example.com/docs --depth 6 # 抓深一点
mirrorweb grab https://example.com --concurrency 1 # 更轻手轻脚
mirrorweb grab https://example.com --resume # 断了接着抓
mirrorweb grab https://example.com --proxy direct # 不走代理池
mirrorweb status --out ./example.com # 看进度和失败清单
mirrorweb check --out ./example.com # 扫坏缓存
mirrorweb check --out ./example.com --fix # 删掉坏缓存mirrorweb --help 有全部选项。
抓站最常见的失败不是"程序写错了",而是被拦。这四层是本工具与普通扒站脚本的主要差别:
默认不走代理,这一节可以直接跳过。
需要代理时,最简单的方式是给一个地址:
mirrorweb grab https://example.com --proxy http://127.0.0.1:7890如果你本机有一个能换出口 IP 的代理池,可以让本工具在撞封时自动降级换 IP。 用环境变量告诉它端口(本项目不自带这些服务,也不会替你启动):
export MIRRORWEB_PROXY_FAST=7890 # 快档,--proxy auto 用
export MIRRORWEB_PROXY_ROTATE=7891 # 撞封后的降级档
export MIRRORWEB_PROXY_OVERSEAS=7892 # --proxy overseas 用
export MIRRORWEB_PROXY_HOST=127.0.0.1 # 可选,默认 127.0.0.1
export MIRRORWEB_ROTATE_CMD="你的换IP命令" # 可选,例如 "clashctl rotate"
mirrorweb grab https://example.com --proxy auto配好之后的行为:撞到 403/429/503/105 → 自动切到降级档并执行换 IP 命令后重试; 连续成功 5 次自动升回快档,不赖在慢档上。换 IP 后会丢弃所有 keepAlive 连接—— 否则复用的旧 socket 仍绑在旧出口上,等于换了个寂寞。
没配 MIRRORWEB_ROTATE_CMD 也能用,只是撞封时只换档不换 IP(功能降级,不会崩)。
🔴 代码里不硬编码任何代理端口,端口只能来自上述环境变量。 这条写进了单元测试,避免把某个人的本机设施固化进项目。
忘了配就用
--proxy auto?工具会明确告诉你缺哪个环境变量、 以及更简单的替代方案,不会静默失败。
- 坏缓存防护:远程返回 HTML 错误页却要存成
.jpg?用 magic bytes + content-type 双判拒存。 这类坏缓存最阴险的地方是它看起来是成功的——文件在、大小非零,只有打开页面才发现图全裂了。 - 失败必须可见:被封、超时、内容异常分类记账,结束时逐类汇总。 无声失败是最贵的失败,绝不允许"抓完了看起来挺好,其实一半资源是空的"。
- 不整体重写 JS:压缩脚本里全是正则和模板字符串,粗暴替换会把菜单轮播全弄坏且极难排查。 只对 JS 做保守的绝对 URL 替换,HTML/CSS 才做完整重写。
- 路径穿越防护:
%2f编码斜杠能绕过new URL()的规范化,单独拦截,外加落点越界兜底。
老实说清楚,省得你抱期望:
- JS 动态渲染的站抓不全 —— 纯 HTTP 抓取,不跑浏览器。内容靠前端请求接口生成的站,扒下来是空壳。
- 需要登录的内容抓不到 —— 不做鉴权绕过。
- 不是完整复制 —— 后端实时计算的东西,本地是死的。
- 一次只抓一个站;没有增量更新(只有全量 + 断点续)。
仅供学习研究与本地测试。
- 抓取前请自行确认目标站的服务条款、
robots.txt与当地法律 - 下载的内容仅限本地使用,不得二次上传、公开传播或搭建公开镜像站
- 本工具不实现验证码绕过、付费墙绕过、登录鉴权绕过——这三样不在能力范围内,也不会加
- 默认值刻意保守(并发 2、遵守 robots),请不要为了快而把它调成压垮小站的样子
因使用本工具产生的任何风险与责任由使用者自行承担。
思路受 MirrorKit(AGPL-3.0)启发—— "本地优先 → 远程回源 → 落盘缓存" 这条主线来自它。
本项目是干净室重写:不含 MirrorKit 任何代码,独立实现,因此不受 AGPL 约束。
逐行比对的重合部分仅为 MIME 映射表、magic bytes 常量(均为 IANA/格式规范的事实性数据,
已在代码中标注来源)与 try {、return; 一类语言样板,无任何算法或控制流复制。
npm test # 或直接: node --test "test/*.test.js"119 个测试,覆盖代理换档、robots 最长匹配、撞封重试、路径穿越、坏缓存识别、 断点续跑、外链重写幂等等。
- wanghao
MIT