2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00
2026-10-11 12:56:38 +08:00

4KHD Downloader

migmg.ssuu.uk(4KHD 系采集站)画廊批量下载器。浏览器里用油猴脚本把画廊推给本地程序, 程序解析分页页、拿到全部图片直链、多线程下载到本地。

程序结构和交互沿用 eh-downloader(E-Hentai 版), 但站点协议层是照着 4KHD 的真实页面重写的。


快速上手

1. 构建

前置:Go 1.24+,以及 Wails CLI v2 和 WebView2 运行时(Win10/11 一般已自带)。

# 装 Wails CLI
go install github.com/wailsapp/wails/v2/cmd/wails@v2.16.0

# 构建
cd hd4k-downloader
wails build

产物在 build\bin\4KHD-Downloader.exe。

本机 Go 装在 %USERPROFILE%\.local\go,不在系统 PATH 里, 调用前需要 $env:PATH = "$env:USERPROFILE\.local\go\bin;$env:PATH"。

2. 装油猴脚本

把 userscript/hd4k-to-downloader.user.js 拖进 Tampermonkey 即可,无需额外配置(端口和 token 已写死在内)。

  • 画廊详情页:标题下面出现「📤 发送画廊地址」按钮
  • 列表页:每条画廊前面出现复选框,右下角浮出批量工具条

3. 下图

点推送 → 程序窗口自动弹到前台 → 「开始下载」。

也可在程序里「导入链接」,粘贴一批 URL(每行一条)手动加任务。


它是怎么工作的

浏览器 (油猴脚本)
    │  GM_xmlhttpRequest → http://127.0.0.1:37778/add
    ▼
本地桥口 (Go, 只监听 127.0.0.1)
    │  {url, title} → 解析成 GalleryRef,URL 域名归一化,入队
    ▼
下载引擎
    │  抓第 1 页 → 从 .page-link-box 读总页数 → 并发抓 2..N 页
    │  从 HTML 抽 i0.wp.com 直链 → 过滤推荐位 → 按页去重
    │  多线程下载 → .part 临时文件 → 原子改名
    ▼
下载目录/<模板>/xxx-001.webp …  +  .hd4k.json(清单,用于续传)

和 eh-downloader 的关键差异

EH Downloader 4KHD Downloader
任务主键 gid + token id(主域名 + 路径)
图片地址 hath 签名链接,会过期 稳定 URL 哈希,永久有效
续传方式 清单存直链,重启 0 请求 同左(4KHD 更彻底)
抓取请求数 2N+1(列表 + 详情 ×2) N(只有分页页)
分辨率 可选 xres=org 等 走源站直链(不用 Photon 代理,否则免费额度打爆会被 429 封)
目录占位符 {title} {gid} {artist} {group} {series} {category} {lang} {tag1} {tags:3} {title} {name} {slug} {host} {category} {id}
桥口端口 37777 37778

4KHD 没有 EH 那套 artist/group/series/tags 标签体系,站点真实提供的元数据只有 标题、路径名、域名和分类数字,所以占位符和预设都相应变少了。


站点适配(重要)

这类采集站随时换域名。实测 www.4khd.com、m.4khd.com 现在全部 404, 只有 migmg.ssuu.uk 活着。所以域名做成了设置项,不用改代码:

  • 设置 → 站点域名 → 加一行即可
  • 保存后,油猴脚本下次加载会从桥口 /info 读到最新域名列表

域名归一化

页面上散布的链接未必指向主域名。实测 migmg.ssuu.uk 首页里 90 个 /content/ 链接全部指向已 404 的 www.4khd.com。

所以 4khd.com、www.4khd.com、m.4khd.com 都配成了 migmg.ssuu.uk 的 别名:不管用户从哪个域名复制链接,后端都会归一到主域名。 不归一的话,同一个画廊会因为「从哪个域名复制的链接」变成两个不同任务, 而且引擎会去请求一个死域名。

分页后缀(…/x.html/2)也会被剥掉,避免 /2 和 /1 变成两个任务。


协议层是怎么定下来的

全部基于 2026-10-11 对真实页面的实测,不是照着 HTML 猜的。几个关键点:

图片地址:页面给的是 Jetpack Photon 代理,但我们不直接用

页面里的图片地址长这样:

https://i0.wp.com/pic.4khd.com/{seg1}/{seg2}/{seg3}/w1300-rw/{name}.webp?w=1300

去掉 https://i0.wp.com/ 前缀、丢掉 ?w=1300 查询串,就是源站直链:

https://pic.4khd.com/{seg1}/{seg2}/{seg3}/w1300-rw/{name}.webp

解析时一律转成源站直链(OriginURL())。原因见下面的「为什么不用 Photon」。

  • 路径里的 w1300-rw 是原站自己的缩略图目录,必须保留; Photon 的 ?w=1300 是它的 resize 参数,源站不认,要去掉
  • 实测去掉查询串后字节数与 Photon 正常时完全一致,画质没变
  • 源站零防盗链:裸 GET(无 Referer / Cookie / UA)直接 200

为什么不用 Photon(2026-10-11 实测教训)

Photon 是 WordPress 的免费图片 CDN。批量下载会打爆它的免费额度, 被封后返回:

HTTP 429
Access has been disabled due to excessive usage.

关键点:这跟请求头无关。实测对照同一张图、同一时刻:

请求方式 结果
我们的头(Chrome UA + Referer) 429
完整浏览器头(+ Sec-Fetch-* / avif) 429
裸请求(一个头都不发) 429
源站直连(pic.4khd.com) 200,128056 字节

被封后连裸请求都 429,说明是按 IP 的额度封禁,不是指纹识别。 而浏览器还能正常加载,只是因为它那点量远没到限额。

源站压测:20 并发全部成功,零限流;连续下 50 张也全部成功。

遇到限流怎么办

即便换了源站,如果将来源站也开始限流,引擎有全局冷却机制:

  • 认出 429/503/403 后,所有 worker 一起停(而不是各自退避重试)
  • 优先遵守服务器给的 Retry-After,没给就按 10s→20s→40s→80s 指数退避
  • 冷却期满一起放行

为什么必须「一起停」:40 个 worker 各自独立退避会错峰重试, 结果是持续往已限流的服务器打请求,永远缓不过来。

每页必须去重:每张图在 HTML 里出现两次 —— 一次 <a href>、一次 <img src>, 内容完全相同。不去重的话 20 张会解析成 40 张(这个 bug 是被真实样本夹具测出来的)。

推荐位图片怎么认:正文图和推荐图走的是同一个 CDN,域名区分不了,只能靠特征:

  • 4KHD-beautifulGirls.webp(推荐位统一封面)
  • /w1090-h1500-p-k-no-rw/(推荐位尺寸段)
  • ?resize=(推荐位查询参数)

路径第 3 段的时间戳区分不了两者 —— 实测两边形式完全一样,都是 base36 时间戳。

分页:.page-link-box 里每个页码是独立的 <li class="numpages">, 取最大值即总页数。

不能追 rel=next:页面里的 rel="next" 和 canonical 都指向已 404 的 www.4khd.com。所以引擎只按总页数自己拼 URL,不跟这条链。

标题:优先 h3.wp-block-post-title。

⚠️ 别抓 <h1> —— 那是站点名 "4KHD",不是画廊标题。 h3 里是双空格(Myu_a(뮤아)␣␣SWEETBOX,分享链接里的 %20%20 证实双空格才是原文), 而 <title> / og:title 被 WordPress 规范化成了单空格。解析时会折叠空白。


项目结构

main.go              Wails 入口
app.go               绑定层:把 Go 能力暴露给前端
wails.json           Wails 配置

internal/hd4k/       ← 站点协议层(换站点只动这里)
  site.go              站点配置、URL 解析、域名归一化
  hd4k.go              抓取、图片 URL 提取、下载
  limiter.go           限流器
  testdata/            真实页面样本(p1/p2/p3)
  hd4k_test.go
  manual_real_test.go  连真实站点的手动验证(-tags manual)

internal/core/       ← 与站点无关的通用引擎
  store.go             任务表 + 设置(原子落盘)
  engine.go            调度、并发、重试、断点续传
  manifest.go          .hd4k.json 清单读写
  paths.go             目录模板与文件名清洗
  bridge.go            127.0.0.1 本地桥口(给油猴用)
  version.go           版本号、桥口 token
  core_test.go
  engine_test.go       端到端集成测试(本地假站点)

frontend/            静态界面(无框架、无打包)
userscript/          油猴脚本

测试

$env:PATH = "$env:USERPROFILE\.local\go\bin;$env:PATH"
go test ./internal/... -v

56 个顶层测试 + 3 个参数化子测试,全部离线跑(本地 httptest 假站点)。

关键覆盖:

  • 真实页面夹具:p1/p2/p3 三页真实 HTML,断言 20/20/13 张、共 53 张
  • 解析器边界:标题提取与实体解码、分页后缀剥离、别名域名归一
  • 桥口:token 校验(无 token → 403)、CORS 只放行已知站点、只绑回环地址
  • 引擎端到端:解析 → 下载 → 落盘 → 写清单 → 状态机整条链路
  • 断点续传:清单完整时重启 0 网络请求;清单信息不全时老实回退重抓
  • 改目录模板:复用老目录里已下好的文件,不重下

连真实站点验证(需网络,默认不跑)

$env:HD4K_URL = "https://migmg.ssuu.uk/content/09/djawa-photo-vol0411-ddiddi-yummy-delivery.html"
$env:HD4K_N   = "6"
go test -tags manual -run TestRealSiteDownload -v ./internal/hd4k/

应用图标

用的是站点自己的图标(/wp-content/uploads/2023/02/4khd-1.png,200×200)。 注意它在页面上挂的是已 404 的 www.4khd.com,所以从 migmg.ssuu.uk 取。

环境里没有 ImageMagick / ffmpeg / Python,所以 ICO 是自己生成的 (tools/mkico,10 个尺寸,小尺寸 BMP + 大尺寸 PNG-in-ICO):

go run ./tools/mkico -in build/icon-source.png -out build/windows/icon.ico
go run ./tools/icocheck build/windows/icon.ico <导出目录>   # 校验 + 导出各尺寸看效果
go run ./tools/alphaprobe <某张图>                          # 打印采样点的 RGBA 与 alpha 分布

缩放必须用 box filter + premultiplied alpha。少做 premultiplied 那一步的话, 缩放结果会反色(橙色变纯白)—— 这就是 alphaprobe 存在的理由, 光看缩略图分不清是图错了还是预览器把透明当白底。


清理过小的图片

「清除已完成」按钮除了清任务列表,还会顺带删掉画廊目录里小于 10KB 的图片。

门槛 MinImageBytes = 10KB(internal/core/paths.go)取自实测:4KHD 正文图 最小也在 90KB 以上,而占位图 / 损坏图 / CDN 错误页伪装的图通常只有几 KB。 实测 50 张真实下载的图,最小 90.2 KB —— 10KB 门槛不会误伤正常图。

清扫规则:

  • 只删图片扩展名的文件(.webp .jpg .jpeg .png .gif .bmp .avif .jfif)
  • 清单(.hd4k.json)不动 —— 要留着续传
  • .part 未完成的下载不动 —— 它可能正好小于门槛但其实没下完
  • 非图片文件(.txt 等)不动
  • 单个文件出错不中断整个清扫

没有已完成任务时也能用 —— 这时按钮只做「清理小图」这一步。


已知限制

  • 解析器首选 i\d+.wp.com 精确匹配;一张都匹配不到时会退到宽松匹配 (任何图片扩展名 + 推荐位特征过滤),以防站点换 CDN 就全线失败
  • 单个画廊最多抓 2000 页,防止异常页码把机器跑满
  • 桥口 token 是明文常量,仅靠「只监听 127.0.0.1」防外部访问 —— 别把它暴露到公网

声明

本工具仅供个人学习与备份自己有权保存的内容使用。 使用者需自行遵守目标站点的条款与所在地区的法律, 因使用本工具产生的一切后果由使用者自行承担。

S
Description
No description provided
Readme
220 KiB
Languages
Go 79.8%
JavaScript 13.5%
CSS 3.5%
HTML 3.2%