日本国立国会文化研究机构(日文研)有一个公开的「艶本資料データベース」,收录了 470 部江户至明治时期的艳本资料,页面扫描件全部公开。网站是九十年代风格的 CGI 加 frameset,逐页点击可以看,整套保存下来要花不少功夫。
这个工具叫 EhonShelf,用 Go 加 MyGo 写的原生桌面应用,把整个数据库做成一个可以离线翻阅的书架。
一、阅读器

主界面是封面网格:书名、绘师、年代、页数。下载完成的作品显示完整封面,未完成的卡片半透明。
阅读器支持单页/双页切换、左右阅读方向切换、页码跳转、缩放(最大 6 倍)、适应窗口。键盘方向键翻页,Cmd+0 回到适应窗口。

设置页有三项配置:数据文件夹、代理、请求间隔。后两项的取值来自下面的调试过程。
二、下载思路
下载器前后改了五版,每一版解决一个具体问题。
第一版:全部请求返回 403
程序发出去的请求全部是 403。
按顺序排查:
| 猜测 | 验证方式 | 结果 |
|---|---|---|
| UA 被拦 | 换成 Chrome UA | 仍 403 |
| Cookie 缺失 | 从浏览器导出完整 cookie | 仍 403 |
| TLS 指纹 | 用 curl_cffi 伪装 Chrome 的 JA3 | 仍 403 |
| 频率限制 | 间隔从 1 秒放到 10 秒 | 仍 403 |
同一时间浏览器打开同一个页面正常。
最后测试网络出口:
1 | curl -x http://127.0.0.1:7897 https://lapis.nichibun.ac.jp/enp/UserMenu |
结果:该站点按地区放行 IP,直连的国内出口一律返回 403。
改动是给 HTTP 客户端加 Transport.Proxy,同时把 403 的错误提示改成「需要代理」。
第二版:图片清晰度不一致
能连上之后,部分作品是 3000 像素宽的扫描件,部分作品只有 133×90 的缩略图。
抓一份 PDF 查看内部结构:
1 | 430_A_15.pdf 899992 字节 PDF-1.6 /JPXDecode |
站点使用两种编码:普通 JPEG(/DCTDecode)和 JPEG 2000(/JPXDecode)。提取逻辑只处理前者,遇到后者返回空,程序退回缩略图。
JPEG 2000 的 Go 解码器选了纯 Go 的 mrjoshuak/go-jpeg2000,解开后是 3300×2600。
同时发现另一种降级情况:站点在高负载时返回 1182 字节的 HTML 错误页,程序把它当作 PDF 解析,同样退回缩略图。现在先检查文件头是否为 %PDF,不符合就重试。
第三版:等待时间过长
测得 1 秒/次的请求连续 8 到 10 次会触发封禁,于是设置封禁后固定等待 10 分钟。
两小时下载 6 部,日志里 13 次封禁。同期手动请求被拒的页面,返回 200 的时间远早于 10 分钟。
改为边等待边探测,每 30 秒请求一次:
1 | 19:04:07 HTTP 403 /enp/image/1/264_B_07.pdf |
同样一次封禁,耗时从 10 分钟降到 30 秒。
第四版:探测请求本身失败
改完之后仍然卡住。程序探的是被拒的那一页,手动用 curl 请求返回 200,程序自己的探测一直返回 403。
原因是探测复用了爬虫的 HTTP 客户端,它的 cookie jar 和连接池保留了被拒绝时的状态。
改动是探测使用新建的临时客户端,探测成功后重置爬虫的 jar。
第五版:无界面全量下载
全量下载使用 -fetchall 模式,直接写入 NAS:
1 | ./ehonshelf -fetchall -out /Volumes/TO3TNS/EhonShelf -proxy http://127.0.0.1:7897 |
设计要点:
- 断点续传:每部作品一个目录,
meta.json记录页数,已存在的页跳过。 - 逐页落盘:每完成一页写入一次
meta.json,长篇作品中途中断不用从头开始。 - 失败集中重试:全部跑完后重新扫描失败的 id,这类失败多为限流,重试即可通过。
- 日志完整:每行带时间戳,封禁、探测、恢复都有记录。
三、调试过程中的几个判断
这几次排查的共同点是:第一眼看到的症状和实际原因不一致。
403 的初判是 UA 或 cookie,实际是 IP 归属;图片模糊的初判是站点分辨率有限,实际是编码格式未识别;下载慢的初判是限流严格,实际是等待策略过长。
另外有一次改完代码没有重新编译,程序运行的仍是两小时前的二进制,排查方向因此偏了一段时间。现在每次修改后会先核对二进制文件的时间戳。
项目地址:github.com/mcxen/ehon-shelf