Skip to content

Repository files navigation

mirrorweb

中文 | 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 有全部选项。


四层防封

抓站最常见的失败不是"程序写错了",而是被拦。这四层是本工具与普通扒站脚本的主要差别:

1. 代理池换 IP(撞封自动降级)

默认不走代理,这一节可以直接跳过。

需要代理时,最简单的方式是给一个地址:

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() 的规范化,单独拦截,外加落点越界兜底。

做不到什么

老实说清楚,省得你抱期望:

  1. JS 动态渲染的站抓不全 —— 纯 HTTP 抓取,不跑浏览器。内容靠前端请求接口生成的站,扒下来是空壳。
  2. 需要登录的内容抓不到 —— 不做鉴权绕过。
  3. 不是完整复制 —— 后端实时计算的东西,本地是死的。
  4. 一次只抓一个站;没有增量更新(只有全量 + 断点续)。

合规红线

仅供学习研究与本地测试

  • 抓取前请自行确认目标站的服务条款、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

About

把网站扒到本地、断网也能打开的 macOS CLI。零依赖,本地优先缓存,遵守 robots.txt。仅供学习研究。

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages