简介:Melon Google Books Downloader(MGBD)是一款用于在Google Books中按合法途径保存图书页面的轻量工具,面向需要离线阅读或摘录参考书内容的研究者、学生与书籍爱好者。压缩包为zip格式,整体仅1.58MB,共11个文件,以主程序exe、多个依赖dll及xml配置文件为主,其中包含Newtonsoft.Json、iTextSharp等常见组件,解压后即可运行核心功能。资源为0.1.0 alpha8早期版本,已具备输入书籍URL、选择PDF/EPUB格式及自定义范围下载的基础流程,同时附带了日志组件,便于排查运行问题。对于频繁查阅Google Books的用户来说,这款小工具能明显提升资料获取效率,但使用前需留意版权规范,仅可下载具备合法使用权限的内容。目前已有3369人下载学习,适合具备一定电脑操作基础、需要批量保存书籍页面的读者。
1. Melon Google Books Downloader 值不值得自己动手折腾:先搞清楚它解决的是哪一类痛点
Melon Google Books Downloader(后面统一叫 MGBD)解决的问题非常具体:你在某个在线书库里翻到一本工程手册,预览一切正常,页页能翻,但就是找不到导出按钮;用浏览器“打印为 PDF”试过的人都知道,几十页还能忍,几百页的书光渲染就要卡死好几次,出来的文件还经常缺图、乱排版。MGBD 走的是另一条路——直接去网页阅读器背后的取流接口拿每一页的原图,再按页码顺序拼装成一份本地 PDF,同时把书名、作者、书页数这类信息写成 JSON。适合谁?适合需要把参考书、手册、公版书做成离线备档的人,也适合那种不想每次都在线翻页、想拥有一份干净文件慢慢标注的从业者。它不是一个大而全的软件,但一旦你需要的正是“整本下载”,它比大多数通用方案都省心。
2. 原理:为什么网页没有“另存为 PDF”按钮,MGBD 却能整本拿下来
2.1 网页阅读器本质是图片流,不是文本流
大多数在线书库的阅读器,包括 Google Books 这套体系,界面里确实有文字层,但正文真正渲染的时候走的是“图片切块”路线:服务端把每一页扫描件切成一定分辨率的图片,浏览器端只负责按页码拉取对应的图片 URL,然后铺进容器里。你看到的平滑缩放、翻页动画,其实都是在切换不同缩放等级的图片文件,并不是在渲染 HTML 文本。
这个架构带来的直接结果是:你在浏览器里“另存为”只能存下一个空壳页面,正文图片不在 DOM 里,另存下来的文件几乎没有任何阅读价值。MGBD 这类工具会做的第一件事,就是绕过浏览器界面,直接去解析阅读器初始化时拿到的书目元数据,从中取出书籍 ID、总页数、每一页对应的图片地址规则,再像浏览器一样把这些图片按顺序拉下来。思路不是我发明的,凡是做电子书归档的工具基本都是这个套路,MGBD 只不过把这些步骤收敛成了一条命令行。
2.2 取流地址的构成:页码、缩放、Cookie 三要素
如果你在浏览器开发者工具里看过阅读器发出的图片请求,会发现 URL 长得都很相似,区别基本在三个地方:页码标识、缩放级别、会话凭证。页码标识决定拉的是哪一页,缩放级别决定图片清晰度,会话凭证决定你有没有权限看到完整内容。MGBD 的原理就是把这三个要素拆开管理。
具体到实现上,常见做法是先用一个会话抓取书籍首页的 HTML,把页面里内嵌的 JSON 元数据解析出来,这里面包含了页数、默认缩放级别、图片 URL 模板;然后拿着登录后的 Cookie 去请求每一页的图片地址,把返回的 JPEG 文件存到本地临时目录;全部页面拉完后,调用图像处理库按页码顺序拼装成 PDF。这个流程里最关键的其实是 Cookie,它代表你的登录态,也决定了你能看到的授权范围。MGBD 一般会把 Cookie 做成一个独立的文本文件传进去,这样多个下载任务可以复用同一份登录态,不用担心每次都手动粘贴。
2.3 为什么直接用“网页另存为”抓不下来
对比一下会更清楚:网页另存为拿到的是一份 HTML + JS 资源包,阅读器正文由 JavaScript 在浏览器里动态生成,HTML 文件里只有容器结构和初始化参数;就算你把 JS 也一起存下来,脱离了原始域名和登录态,图片接口照样拒绝访问。MGBD 的做法完全不同,它是直接以程序模拟浏览器的行为去请求原始资源,拿到的是真正的一张张页面原图,不经过前端模板渲染,所以不受“动态页面无法保存”的限制。
这里还涉及一个常见的权限差异,我用一张表说明。
| 访问状态 | 网页阅读器表现 | 下载工具能拿到的内容 |
|---|---|---|
| 未登录公开预览 | 只能翻看前几十页或随机预览段 | 只能获得预览段图片,输出 PDF 不完整 |
| 账号已授权或订阅中 | 能正常翻阅全书 | 通常可以拉取全部页面的图片地址 |
| 公版书或已解除限制的书籍 | 页面右上角有下载入口 | 即使不用工具也能直接下载源文件 |
你大概能看出来了,MGBD 本质上是在“没有下载入口但有完整阅读权限”的场景里补上导出能力,它的价值建立在你有合法访问权的前提上。我不会去讨论怎么绕过付费墙,这类讨论没有意义,权限之外的页面拿到手也是碎片,配合上阅读器校验反而更容易出问题。规规矩矩用它下载自己已授权的内容或者公版书,才是这个工具的正确打开方式。
3. 环境准备与第一行命令:从空目录到拿到 PDF
3.1 Python 虚拟环境与依赖安装
MGBD 这类工具基本都跑在 Python 生态里,因为后续拼 PDF、解析 HTML、处理图片都有成熟的库可以用。先把环境准备齐,建议用虚拟环境隔离,不要直接往系统 Python 里塞东西。我一般会这样初始化:
mkdir -p booklab && cd booklab python -m venv .venv source .venv/bin/activate pip install requests pillow beautifulsoup4 tqdm这几步的逻辑是:venv创建独立的运行环境,避免污染系统 Python;requests负责发 HTTP 请求拉取图片数据;pillow用来处理图片格式、尺寸和旋转方向;beautifulsoup4用来解析书页 HTML 里的元数据;tqdm提供下载进度条,跑长任务的时候能看到实时反馈,减少心里没底的感觉。Python 版本建议 3.9 以上,太低的话部分依赖的高版本会安装失败。
如果你本机已经有其他 Python 项目在跑,这一步尤其重要——依赖版本冲突是这类工具最常见的启动失败原因。一个干净虚拟环境能挡掉九成麻烦。
3.2 先从浏览器里把 Cookie 导出成文本
MGBD 不会自己帮你登录,它把登录这件事留给浏览器处理。方法很简单:先在自己常用的浏览器里登录账号,打开要下载的书籍页面,进入开发者工具找到网络面板,随便点开一个图片请求,在请求头里找到Cookie字段,复制整段内容保存到本地文件。命名随意,我习惯叫cookies.txt,和下载目录放一起。
需要注意的是,Cookie 是有时效的。Google Books 这种服务通常会定期刷新会话令牌,失效之后你再跑下载命令就会看到一片 403 报错。所以我的习惯是下载之前现取现用,不要拿三天前导出的 Cookie 去跑一个几百页的大任务。保存格式没有硬性规定,MGBD 一般会自己解析你给的那段文本,确保它长这样即可:
cookie1=value1; cookie2=value2; sessionid=xxx就是浏览器请求头里复制出来的原始格式,不要多加换行,也不要把别的请求头混进去。如果你用的浏览器有多个配置文件的隔离环境,记得先切到正确的配置文件再登录,免得导出一份空 Cookie。
3.3 命令行参数和一次完整下载
环境到位、Cookie 就绪之后,下载本身是一条命令的事。这里我给出我常用的参数写法,不同版本略有差异,但核心参数名基本不会跑偏:
python -m mgbd \ --url "https://books.google.com/books?id=YOUR_BOOK_ID" \ --cookies cookies.txt \ --output books/hello \ --format pdf \ --save-json \ --start-page 1 \ --end-page 320 \ --concurrency 4 \ --retry 3逐个拆一下关键参数:
--url是书籍的阅读页面地址,注意要复制阅读器专属的那个 URL,而不是搜索结果页或者书籍详情页;--cookies指向刚才导出的 Cookie 文件;--output指定输出目录,MGBD 会在这个目录下生成 PDF 和配套的 JSON 文件;--save-json表示同时保留元数据,这一步我建议每次都加上,后面做验证和归档都靠它;--start-page和--end-page限制页码范围,适合只想下载中间某几章的场景;--concurrency控制同时下载的线程数,我先给 4 是因为这个值对绝大多数情况都够用,调到 8 或 16 未必更快,后面避坑章节会详细展开;--retry 3表示单张图片下载失败时最多重试三次,三次还失败就记录下来继续后面的页面。
命令跑起来之后,你应该能看到类似这样的输出:
[1/320] 100%|████████| 320/320 [03:12<00:00, 9.02it/s] PDF saved to: books/hello/hello.pdf Metadata saved to: books/hello/meta.json看到PDF saved to这行就说明整本书已经合成完毕,可以直接去输出目录里查收。运行过程中如果中途报错中断,先不要立刻改参数重跑,很可能是临时文件没清理干净,重跑会冲突,这部分内容我会在第 5 章详细说。
3.4 输出目录结构长什么样
跑通一次之后,你会在--output指定的目录里看到一套完整的产出物。我见过的组织方式大多类似这样:
books/hello/ ├── hello.pdf ├── meta.json └── page ├── 0001.jpg ├── 0002.jpg └── ...hello.pdf是最终拼接好的电子书,meta.json里记录书籍的基本信息,page目录则是下载过程中保留的原图。保留原图目录这个设计很实用——如果你发现 PDF 拼得不对,或者想换一种格式重新生成,不需要重新拉一遍图片流,直接从page里的原图二次加工就行。这也是我把这类下载工具和“一次性抓图脚本”区分开的原因:一个合格的下载器应该允许你重做出图结果,而不是让你每次从头请求一遍远程服务器。
4. 进阶:页面顺序、断点续传与并发控制(不影响质量的前提下跑得快)
4.1 真实页序与 URL 页码是两个概念
第一本下载成功之后往往会出现一个诡异的现象:PDF 文件确实生成出来了,但某些章节的页码是乱的,甚至个别页会重复。这不是下载时丢包,而是“URL 页码”和“印刷页码”根本不是一个体系。
URL 里的页码代表的是书库服务器内部存储的扫描件顺序,它可能是按物理页排列的,而物理页和书脊上的印刷页码之间常有偏移、插页、空白页。MGBD 在保存原图时通常会按 URL 顺序命名文件,同时把服务器下发的页码标识记录在元数据里。正确的处理方式是在拼接 PDF 之前,按服务器元数据里的真实页序做一次重排,而不是凭文件名顺序硬拼。
如果你发现下载完后页序有问题,第一反应不是重新下载,而是去meta.json里核对每个文件名对应的原始页标识。常见做法是写一小段脚本重排文件命名:
import json from pathlib import Path meta = json.loads(Path("books/hello/meta.json").read_text()) order = [p["local_name"] for p in sorted(meta["pages"], key=lambda x: x["page_index"])] for idx, name in enumerate(order): src = Path("books/hello/page") / name dst = Path("books/hello/page") / f"order_{idx:04d}.jpg" src.rename(dst)这段代码的逻辑是先按page_index字段重新排序,再统一命名为order_0001.jpg这种连续的文件名。注意meta["pages"]里每一项都有local_name和page_index两个字段,前者是服务器原始文件名,后者是实际阅读顺序。参数要看元数据的实际字段名,我这里是按常见命名习惯写的,你的输出字段可能叫page_no或者seq,先jq .看一下结构再改。
4.2 断点续传:用临时目录换后悔药
几百页的大书下载到一半断网或者被限流,这种经历一次就够难受了。MGBD 这类工具普遍支持断点续传,原理是在输出目录里维护了一个隐藏的临时状态文件,记录哪些页已经成功保存。再次执行时,它会先读这个状态文件,跳过已完成的部分,只补下缺失的页面。
我建议你了解它的逻辑,因为理解之后能少很多无用功。常见实现是每一页下载成功后,往done.json里追加一条页码记录,重跑时先读这个文件:
from pathlib import Path tmp_dir = Path("books/hello/.partial") done_file = tmp_dir / "done.json" if done_file.exists(): done = set(done_file.read_text().splitlines()) else: done = set() for page in pending_pages: if page in done: continue try: download_page(page) except Exception: continue with done_file.open("a") as f: f.write(page + "\n")这段代码的关键点是“每成功一页就立刻记录”,而不是等全部下载完再统一写状态。这样即使中途崩溃,最多丢最后一页没记录的那部分,不会回滚到开头。注意done.json记录的是页码,不是文件名,因为页码是稳定的业务标识,文件名可能会在重排序时变化,用页码做状态记录更安全。
4.3 并发线程数与退避重试
很多第一次用下载工具的人会把并发数调到最高,幻想 16 线程同时拉取能快到飞起。实际表现正好相反,尤其是面对有访问频率控制的服务。我测试过同一个书目,4 线程能在稳定三分钟跑完,16 线程反而频繁触发限流,最后变成每次请求都要等重试,总耗时翻了两倍不止。
这里有两个参数值得做实验:线程数和重试退避时间。线程数控制在 4 到 6 之间是大多数书库的舒适区,超过 10 基本就会触发保护机制。重试参数除了次数,还要看退避策略是固定间隔还是根据响应头里的Retry-After动态等待。更好的做法是让重试逻辑识别常见的限流状态码,遇到之后自动延长等待时间:
import time import requests for attempt in range(3): resp = requests.get(url, cookies=cookies) if resp.status_code == 200: return resp.content if resp.status_code == 429: wait = int(resp.headers.get("Retry-After", "5")) time.sleep(wait + attempt * 2)注意429状态码代表服务端明确告诉你请求太频繁了,这时候要乖乖等服务端给的Retry-After,自己硬等一个固定时间往往不保险。我见过不少项目在这里偷懒,重试间隔写死 1 秒,结果重试三次全部撞在限流窗口里,等于没重试。
4.4 元数据 JSON:留给下一次下载的笔记
--save-json这个参数的价值往往被人低估。我习惯把下载过的每本书的元数据都保留下来,因为一段时间之后你可能会想重新做一份不同格式的备份,或者想核对某本书到底有多少页,这时候meta.json就是最快的参考依据。它一般包含这几类信息:
- 书籍标识:书名、作者、ISBN、原始页面地址
- 下载参数:执行下载时用的起始页、结束页、并发数
- 页面清单:每页的原始文件名、对应页码、图片尺寸
- 时间戳:下载开始和完成的时间
这些信息本身不会直接影响 PDF 质量,但它让每一次下载都变成可追溯的记录。做批量归档的时候,我甚至会把多个meta.json汇总成一个索引文件,配合文件系统里的 PDF 形成一套轻量级资料库,不需要额外装管理软件,用命令行就能查。
5. 避坑:我踩过一次就不想踩第二次的五个问题
5.1 明明登录了,下载出来还是只有预览页
现象:浏览器里能正常翻阅全书,Cookie 也导出了,但下载下来的 PDF 只有前面二十几页,其他全都没拉下来。
原因:大概率是导出的 Cookie 来自普通浏览器页面,不是阅读器页面。在线书库对普通页面和阅读器页面的会话校验不一致,某些会话令牌只在阅读器域名下有效,你复制的 Cookie 缺少了对应域名的令牌。
解决:重新打开要下载的那本书的阅读器页面,在网络面板里点开一个图片请求,从请求头里复制完整的 Cookie。特别注意域名要和图片请求保持一致,不要从书库首页或者搜索页复制。
5.2 跑到 70% 被 403,重跑又从头开始
现象:上一次下载跑到一大半的时候报 403,重新执行命令后进度没有从 70% 继续,而是从头开始拉取。
原因:重跑时没有检查输出目录里的临时状态文件,或者是上一次中断后残留了不完整的图片文件,导致工具误判为没有下载过。部分实现是“文件存在就当成功”,它看到目录里有图片就以为那页没问题,但那些文件其实是残缺的。
解决:先检查输出目录下有没有.partial类似的隐藏目录,如果有就保留,重跑命令它会自动续传。如果发现工具不支持断点续传,那就把page目录里的残缺图片全部删掉再重跑,不要让残留文件干扰判断。删之前用文件大小筛一遍,低于正常体积的图片基本就是半截货。
5.3 生成 PDF 有黑页或整页旋转
现象:下载过程一切正常,但生成的 PDF 里某些页面是全黑的,或者有些页面内容转了 90 度。
原因:黑页多半是服务端返回了占位图,这种情况往往出现在扫描件有版权保护标记的页面,或者这本书本身存在空白分隔页,不一定是下载失败。旋转问题则是图片素材本身带有 EXIF 方向信息,拼 PDF 时没有解析这个字段,直接按原始像素输出,于是横向扫描页就变成了侧着看。
解决:打开输出目录里的原图确认一下,如果图片本身是正常的,就用 Pillow 批量把方向信息校正后再重新合成 PDF。具体做法是读取图片的 EXIF 方向标签,自动旋转到自己正的视角。
5.4 并发调高反而变慢,被限流
现象:并发数从 4 调到 16 之后,速度不仅没上去,还频繁出现失败重试,整体耗时拉长。
原因:服务端对单会话的并发请求数有限制。一旦超过限制,响应速度指数级下降,甚至直接拒绝。很多下载器默认的重试逻辑是等几秒再试,但这种固定等待对限流场景并不友好。
解决:把并发数调回 4 到 6。如果确实需要更快,可以准备多个 Cookie 文件轮换使用,但要注意这些 Cookie 可能关联同一个账号,限流还是会追上来。更务实的做法是接受这个速度,下载期间别同时开多个任务。
5.5 同一本书下载两次,第二次覆盖了第一次
现象:第一次下载用的输出目录是books/hello,第二次重命名了书却忘了改目录名,结果第二次的 PDF 把第一次的覆盖了,旧文件找不回来。
原因:输出目录没有做唯一性隔离,同名目录直接被复用。工具本身不会帮你做版本管理,它只是忠实地往目标路径写文件。
解决:输出目录里加上书籍标识和时间戳,我习惯用books/ISBN_书名首字母_年月日这种命名规则。即使重复下载也不会互相覆盖,而且每次下载的元数据都能独立审计。
6. 收尾:下载完先别着急读,做这三项核对
PDF 生成出来之后,我建议先花一分钟做三项核对,不要直接丢进书架。第一项,打开meta.json看总页数,再打开 PDF 确认实际页数一致。命令行里可以这样快速核对:
python -c "import fitz; doc = fitz.open('books/hello/hello.pdf'); print(doc.page_count)"第二项,检查 PDF 文件大小是否合理——几百页的书通常应该有几十 MB 量级,如果一个三百页的 PDF 只有几 MB,大概率是图片分辨率不对或者缺了页。第三项,抽查中间的十页,确认没有黑页、旋转页、重复页。这三项都过了,再放到阅读器里享受离线阅读的踏实感。从那以后我每下载完一本书,都会强制走一遍这个核对流程,再顺手把meta.json归档到资料库索引里,免得哪天想找某本书的原始下载参数时翻遍硬盘也找不到记录。希望帮到你。
本文还有配套的精品资源,点击获取