Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WebClone — 网页模板 / 整站克隆工具

C# + WPF 编写的网页克隆小工具:输入网址,把页面的 HTML 源码及引用的 CSS、JS、 图片、字体等资源全部下载到本地,自动处理文件名合法性并把绝对路径改写为相对路径, 让克隆结果可以离线打开、不跳外站。

对应 wget 的两种用法:

模式 等价 wget 命令 说明
单页模板 wget -r -E -np -c -k --ignore-tags=a <url> 只抓当前页与它引用的资源,不递归 a 链接
整站克隆 wget -r -np -c -k <url> 递归追踪站内链接(不向上级目录),抓全站

项目结构

WebClone/
├── src/
│   ├── WebClone.Core/    核心引擎(net10.0,零反射,可被 NativeAOT 完整编译)
│   ├── WebClone.Cli/     命令行版(PublishAot,产出原生 exe,是 AOT 兼容性的验证载体)
│   └── WebClone.Wpf/     图形界面(net10.0-windows,单文件自包含发布)
└── tests/
    ├── make_test_site.py 生成功能验证站点(含 GBK 页面、查询参数资源、CSS 嵌套引用等)
    ├── test_server.py    支持 ETag / 304 / Range 续传的本地测试服务器
    └── test_proxy.py     极简 HTTP / SOCKS5 双模代理,用于验证代理功能

截图预览

GUI 运行实况 —— 单页克隆 go.dev,68 项资源全部完成,无失败:

GUI 运行界面

克隆产物 —— 按 URL 目录结构归档,引用已改写为相对路径:

克隆产物目录

离线渲染效果 —— 本地静态服务打开克隆结果,样式、图片、字体完整加载:

离线渲染效果

功能与实现要点

  • 两种模式:单页模板 / 整站递归,整站模式遵循 -np 语义(不下载上级目录)。
  • 文件名处理:URL 带查询参数时生成 原名_8位哈希.ext(如 style_78fe4a82.css), 合法且可读;同时处理 Windows 保留名、非法字符、路径穿越。
  • 路径改写:所有资源引用改写为相对路径;<base href> 重定向为 .; 跨域 CDN 资源按各自域名归档后同样以相对路径引用。
  • 重定向归一:下载时跟随服务端重定向,最终地址(如 /doc/ 重定向自 /doc) 与请求地址共享同一本地文件,整站克隆不会因链接写法差异存两份。
  • 类型修正:下载完成后按响应的真实 Content-Type 修正落盘扩展名 —— 像 Google Fonts 的 /css?family=... 这类无扩展名动态样式表会被正确存为 .css, 避免浏览器因 MIME 类型不符拒绝应用样式(Material Icons 图标失效的根因)。
  • 单页模式防跳转:指向未下载页面的 a 链接统一替换为 #;整站模式保留外站原链。
  • 代理:HTTP(S) 代理与 SOCKS5 代理。SOCKS5 为手写实现(RFC 1928/1929), 通过 SocketsHttpHandler.ConnectCallback 只接管 TCP 建连,TLS 与 HTTP 仍由框架处理, HTTPS 天然走 CONNECT 隧道。支持用户名密码认证。
  • 增量与断点续传:
    • 图片、字体等二进制资源用 If-None-Match / If-Modified-Since 条件请求,304 直接跳过;
    • 中断的下载用 Range + If-Range 续传(资源变化时服务端自动降级为 200 全量);
    • 一律先写 .part 再原子改名,不留残缺成品;
    • HTML / CSS 因为落盘前会被改写,始终取原始内容,避免把改写后的文件误当服务器响应。
  • 编码:按 BOM → HTTP charset → meta charset / @charset → 严格 UTF-8 嗅探 → GB18030 兜底的顺序识别,统一转 UTF-8 保存并改写编码声明,GBK 页面不乱码。
  • 解析器自研:HTML / CSS 扫描器为零依赖状态机(约 700 行),记录原文下标做原地替换, 不引入 HtmlAgilityPack / AngleSharp 等反射大户,这是能过 NativeAOT 的关键。 覆盖 <link>、<script>、<img>、srcset、<video>/<audio>/<source>、 <object>/<embed>、<iframe>、内联 style、<style> 正文、CSS url() / @import / @font-face,以及 data-src 等懒加载属性。

关于 AOT 的说明

WPF 与 NativeAOT 不兼容(SDK 硬性报 NETSDK1168:启用剪裁时不支持 WPF)。 因此采用分层方案:

  • WebClone.Core + WebClone.Cli:PublishAot=true,产出免安装、秒启动的原生 exe, 全程零 IL2xxx / IL3xxx 裁剪警告;
  • WebClone.Wpf:单文件自包含发布(ReadyToRun),业务逻辑全部在 Core 里, Core 的 AOT 干净度由 CLI 项目实际编译验证。

构建

一键构建脚本(Windows PowerShell):

.\build.ps1                # CLI + GUI 全部构建并打包到 dist\
.\build.ps1 -SkipCli       # 只构建 GUI
.\build.ps1 -SkipGui       # 只构建 CLI(AOT)
.\build.ps1 -Clean         # 先清理 dist\ 与 obj\bin 再构建
.\build.ps1 -Configuration Debug

产物:

文件 说明
dist\webclone-cli.exe 命令行版,NativeAOT 原生可执行(约 8 MB,免安装)
dist\WebClone-GUI.exe 图形界面版,单文件自包含(约 150 MB,首次启动自解压稍慢)

手动构建(等效于脚本内部行为):

# 需要 .NET 10 SDK + VS C++ 工具链(NativeAOT 依赖)
dotnet publish src/WebClone.Cli -c Release -r win-x64
dotnet publish src/WebClone.Wpf -c Release

在 Git Bash / Cygwin 会话里构建,若报 Value cannot be null. (Parameter 'path1') 或 Cross-OS native compilation is not supported,是会话缺少 ProgramFiles / OS 等环境变量所致,source ~/.workbuddy/bin/env-fix.sh 后用 runenv dotnet ... 即可。

应用图标位于 assets/app.ico(自绘,无版权问题),由 tests/make_icon.py 生成, CLI 与 GUI 共用。

CLI 用法

webclone <网址> [选项]

# 单页模板,输出到指定目录
webclone https://example.com -o D:\site

# 整站递归,深度 3,并发 16
webclone https://example.com -m full -d 3 -c 16 -o D:\site

# 走 SOCKS5 代理(支持 user:pass 认证)
webclone https://example.com --proxy socks5://user:pass@127.0.0.1:1080

# 走 HTTP 代理
webclone https://example.com --proxy http://127.0.0.1:8080

常用选项:-m single|full、-o 目录、-d 深度、-c 并发、--max-pages、 --no-cross-origin(不抓 CDN)、--no-incremental、--keep-absolute、 --keep-external-links、--ignore-cert-errors、-v 详细日志。

测试

一键回归(自动起服务器、跑五组场景断言、汇总 PASS/FAIL):

.\tests\regression.ps1                    # 默认用 dist\webclone-cli.exe
.\tests\regression.ps1 -PythonPath py     # 指定 python 命令

覆盖场景:单页克隆(含哈希文件名与资源落盘)、增量二跑、整站三页递归、 -np 不越界、GBK 转码。引擎改动后跑一遍即可确认无回归。

手动验证步骤:

# 1. 生成测试站点并启动本地服务器(支持 304 / Range)
python tests/make_test_site.py tests/site
python tests/test_server.py 8765 tests/site

# 2. 启动双模代理(可选,验证代理功能)
python tests/test_proxy.py http 8899
python tests/test_proxy.py socks5 8898

# 3. 克隆并对比
webclone http://127.0.0.1:8765/ -m full -o out

已验证项:单页/整站递归、CSS 内 url()/@import/@font-face、内联 style、srcset、 带查询参数的文件名重命名、GBK 编码转码、增量 304 跳过、断点续传(206 拼接后 与源文件逐字节一致)、HTTP/SOCKS5 代理克隆、AOT 原生发布、重定向归一、 按 Content-Type 修正扩展名。

GUI 选项记忆

GUI 每次启动自动回显上次的选项,配置存放在 exe 同目录的 webclone-gui.json:

  • 首次运行自动生成默认配置;点"开始克隆"或关闭窗口时自动保存当前选项
  • 单个字段损坏只跳过该字段用默认值;整个文件损坏自动重建
  • 配置写失败(如放在无写权限目录)静默跳过,不影响克隆

About

C# WPF开发的网页克隆小工具,wget命令等效重制版,支持单页克隆或整站克隆,支持指定网络代理,另有Rust实现版,打包体积更小:https://github.com/hexiyou/WebClone-rs

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages