拓冰建站拓冰建站
首页 / 资讯中心 / 正文

用Python批量下载flbook电子书并合并PDF的完整思路

简介这是一份用于下载flbook电子书的Python源码与说明文档资源适合有基础Python爬虫知识、希望批量获取flbook.com.cn电子书图片资源的开发者和学习者。包内共7个文件包含两个Python脚本demo_run.py与download_flbook.py、一个编译生成的pyc缓存、一个README.md使用说明、一个inscode配置文件及两张示例页面图片整体体积约872KB结构简洁清晰。目前已有377人学习/下载。源码提供了完整操作思路通过浏览器开发者工具在关键位置下断点利用JavaScript定位电子书图片直链再借助requests库发送HTTP请求批量下载并保存到本地同时配有演示脚本和说明文档便于读者快速复用和二次修改。适合用于自动化抓取、网络请求分析等领域入门实践。 拿到一本 flbook 电子书的链接想用 Python 写段源码把整本下载到本地这种需求我在群里见过不少次。flbook 是典型的翻页阅读平台杂志、画册、产品手册都喜欢用它发布。问题在于书在浏览器里是一页一页动态加载的每一页其实就是一张高清图片你想右键另存为只能保存当前这一张用浏览器打印成 PDF出来的不是缺页就是带着导航栏的模糊版。这篇文章把调试这类下载脚本的完整思路写出来从抓包定位图片地址到批量下载再到合并 PDF每个环节容易踩的坑我都会讲到。先把丑话说在前面下面的方法只适用于你有权使用的电子书比如自己上传的样本、平台明确免费开放的内容、或者已进入公共领域的资料拿它去扒商业出版物咱就不聊这个了。1. 为什么 flbook 的书不能直接另存为flbook 这类平台的书籍页面看起来是一个普通网页内里其实是一个由 JavaScript 驱动的翻页应用。打开一本书你看到的是加载动画和翻页动效浏览器背后在逐张请求高清图片再按顺序拼出整本画册。这个机制决定了它的下载方式和普通网页完全不同。平台选用图片而不是文字来承载内容核心原因很简单还原。图片方案能保证同一套排版在任何操作系统、任何屏幕上都一模一样不依赖客户端字体不担心段落错位。对杂志、画册、产品手册这种版式要求极高的内容这是最稳的展示方式。可这个优势反过来就是下载者的痛点——页面上根本没有整本书这个资源你看到多少页背后就有多少张图片在单独请求。而且翻页组件普遍懒加载没翻到的页面浏览器根本不会去请求你也就拿不到图片地址。想直接保存的话常规路子我都试过各有各的问题右键另存为只能保存当前正在展示的这一张换页之后再翻回去图片可能又被释放掉了得重新加载。浏览器打印/另存为 PDF页数一多就缺页页眉页脚还带着导航工具栏分辨率也差一截基本没法用。查看网页源码找图片能找到一部分图片地址但前端是拿着配置模板临时拼接 URL 的源码里只有规则没有完整的资源清单。综合下来能稳定跑通的思路是三步找到描述书籍结构的配置数据按配置拼出每一页图片的 URL最后按顺序下载并合并成 PDF。后面全部内容都是围绕这条主线展开。接下来要用的工具并不复杂一个 Chrome 开发者工具一个 Python 3.8 以上的环境再装一个 requests 库就够了。不需要 Selenium 这类重型浏览器自动化工具——翻页组件的数据完全可以直接从 HTTP 层拿到模拟浏览器反而会引入更多不稳定因素。2. 抓包定位图片地址到底藏在哪份配置里正式开始写代码之前请先花十分钟做抓包分析。这个环节没做扎实后面写多少代码都是在瞎猜。2.1 网络面板里翻页先看图片请求长什么样用 Chrome 打开书籍链接按 F12 进入开发者工具切到 Network 面板勾上 Preserve log。筛选框切到 Img然后手动翻动书页。面板里会冒出图片请求点开一张看预览如果是完整大图这就是页面原图也就是我们要下载的对象。看这张请求记下三件事图片 URL 里页码部分的变化规律、URL 有没有带签名参数、请求头里有没有特殊的 Referer 或 Cookie。这三样直接决定脚本要不要模拟请求头、要不要带 session、要不要处理 token漏掉任何一个程序可能跑一半就被卡住。很多教程到这里就直接结束了但我建议再把请求头完整展开看一遍尤其是 Cookie 里如果有会话字段说明平台是把翻到第几页这样的状态记在服务端的后面脚本里一定要带上 session 管理工作。2.2 找到决定书有多少页的那份配置细心的读者会发现翻页的时候除了图片请求页面还会提前加载一份配置数据。它告诉前端一共多少页图片放在哪个目录文件怎么命名。这份配置可能是独立的 JSON 文件也可能直接写在 HTML 的 script 标签里。找它的办法很多Network 面板按 XHR 过滤看最早发出的几个请求或者直接 CtrlF 搜页面源码里的 pages、image 这类关键词。配置拿到手之后重点提取三样总页数、图片基础路径、命名模板。字段名每家平台都不一样常见的有 totalPages/pageCount、imageBase/filePath、nameTemplate/imageName请以上一步抓到的实际 JSON 为准。这段配置是整个脚本的地基页面数量、URL 拼接、重试范围全都从它来。如果配置里没有列出具体图片 URL 模板而是给了一个接口地址那大概率是翻页组件有一个后端接口返回页列表。看到这种情况反而更省事直接 GET 一次接口把响应里返回的 url 数组原样存下来连拼接都免了。这一步的原理其实很朴素所有前端框架都必须在初始化时拿到这本书有多少页、图片在哪这个信息否则它根本渲染不出来。这个信息只可能出现在网络请求里不可能凭空产生。想清楚这一点不管平台页面长成什么样都不会被绕晕。3. Python 下载脚本的核心实现配置摸清楚后可以写脚本了。环境方面Python 3.8 以上安装 requests 即可解析 HTML 用 BeautifulSoup 或正则都行这里我直接用正则抽配置步骤更少。3.1 单页下载先把链路跑通拿到配置数据后不要急着写循环。先把第一页图片真实下载下来确认 URL 拼接是对的、请求头是够的、图片是完整的。这一步跑通后面批量才有意义。import json import re import requests book_url https://你的flbook书籍链接 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36, Referer: book_url, } session requests.Session() session.headers.update(headers) resp session.get(book_url, timeout15) html resp.text # 从 HTML 里提取书籍配置正则只做粗提取找到包裹 JSON 的边界即可 match re.search(rwindow\.bookData\s*\s*(\{.*?\});, html, re.S) book json.loads(match.group(1)) total_pages book[totalPages] # 总页数 image_base book[imageBase] # 图片基础路径 name_template book[nameTemplate] # 例如 page-{n}.jpg first_page image_base name_template.format(n1) r session.get(first_page, timeout20) if r.status_code 200: with open(pages/001.jpg, wb) as f: f.write(r.content)注意配置文件里的字段名因平台而异上面代码只是示例命名请以上一步抓包里看到的实际 JSON 为准。这段代码里有几个刻意的设计session 复用连接并自动维持 Cookie请求头带上 Referer 和常规浏览器 UA文件名补零为三位。第一个设计是为了绕过部分平台的会话校验第二个是为了防盗链第三个则是为后面合并 PDF 的顺序做准备。如果第一页返回 403回去比对请求头如果下载成功但打开是一张空白图或缩略图多半是页码偏移问题把拼接 URL 里的页码从 1 改成 0 再试一次。3.2 多线程批量下载I/O 密集型任务用线程池就够单页验证通过后就可以批量了。图片请求主要消耗的是网络等待时间不占 CPU所以用线程池就够不需要上多进程。max_workers 建议从 8 开始跑起来发现失败率偏高就往下调。import os import time from concurrent.futures import ThreadPoolExecutor, as_completed os.makedirs(pages, exist_okTrue) def download_page(n): url image_base name_template.format(nn) path fpages/{n:03d}.jpg if os.path.exists(path) and os.path.getsize(path) 50 * 1024: return n, skip for attempt in range(3): try: r session.get(url, timeout20) if r.status_code 200 and len(r.content) 10000: with open(path, wb) as f: f.write(r.content) return n, ok except requests.RequestException: pass time.sleep(1 * (attempt 1)) return n, failed with ThreadPoolExecutor(max_workers8) as executor: futures [executor.submit(download_page, n) for n in range(1, total_pages 1)] for future in as_completed(futures): n, status future.result() if status failed: print(fpage {n} failed)下载函数里做了两件事一是失败自动重试三次重试间隔逐次拉长二是下载成功的标准是响应体大于 10KB。这个阈值用来过滤错误页和占位图——原图动辄几十上百 KB而服务器返回 403 时往往给一个几百字节的提示页。只看 status_code 是否 200 是不够的我见过不少 200 状态码下面藏着一张错误占位图的情况。文件名用{n:03d}补零也不是矫情不补零的话排序时 10.jpg 会排在 2.jpg 前面后续合并 PDF 时整本书都是乱的。4. 装订成 PDFimg2pdf 和 Pillow 的选择标准图片全部落盘后到了合并 PDF 的环节。这一步有两种做法选错了下场完全不同。方案优点缺点适用场景img2pdf无损、速度快、内存占用低页面尺寸不一致时会出问题页宽高统一的画册、杂志Pillow能统一尺寸、补白边、控制压缩比重新编码有少量损失、内存占用高页面比例不统一的扫描件img2pdf 的底层逻辑是把 JPEG 的图片数据块直接复制进 PDF 容器中途不做任何重新编码所以速度极快、画质无损、内存占用也很低。代价是它对图片尺寸比较挑剔如果两张图片物理尺寸不一致生成出来的 PDF 各页大小会不一样阅读体验很糟糕。Pillow 则把图片读进来重新编码再写入 PDF可以在保存时统一尺寸、补白边、压缩体积。灵活是灵活但 200 页的大画册会在内存里同时驻留大量图片对象我实测过直接把 4G 内存吃满的案例。# 用 img2pdf适合尺寸统一的情况 import img2pdf from pathlib import Path page_paths sorted(str(p) for p in Path(pages).glob(*.jpg)) with open(book.pdf, wb) as f: f.write(img2pdf.convert(page_paths))# 用 Pillow适合尺寸不统一的情况统一按第一页的尺寸留白 from PIL import Image from pathlib import Path paths sorted(Path(pages).glob(*.jpg)) base Image.open(paths[0]).convert(RGB) w, h base.size images [base] for p in paths[1:]: im Image.open(p).convert(RGB) if (im.width, im.height) ! (w, h): canvas Image.new(RGB, (w, h), (255, 255, 255)) x (w - im.width) // 2 y (h - im.height) // 2 canvas.paste(im, (x, y)) im canvas images.append(im) images[0].save(book.pdf, save_allTrue, append_imagesimages[1:])我的选择标准很简单先在本地看两页图片的宽高。宽高一致直接 img2pdf一行搞定比例不一致再上 Pillow 做留白统一。不管用哪种排序都是必须的一步这一步错了前面所有工作都白费。5. 实测里的坑丢帧、防盗链、半截图片脚本能跑通和能稳定跑完整本书是两码事。下面这些坑是我实际下载过程中踩过的按出现频率排。5.1 页码偏移和封面缺失首尾页最容易出问题翻页平台的页码很多从 0 开始而配置里的 totalPages 可能不包含封面封面偶尔还会单独走一个独立 URL。于是你按 1 到 total_pages 循环下载结果首尾出现重复图或空白页。判断办法很笨但有效下完前五张肉眼扫一遍。发现异常就调整 range 的起点或终点比跑完整本再回头排查省事太多。还有一种情况是过了某个页数后 URL 规则突变比如前 50 页是page-01.jpg这种连续编号从第 51 页开始变成了另一套命名。遇到这种回到抓包环节把两批请求的 URL 放在一起对比差异一目了然。别指望一套规则跑到底平台不会替你的脚本考虑。5.2 403 防盗链Referer 和 Cookie 是主要门槛这类平台普遍有 CDN 防护最常见的是校验 Referer 和 User-Agent。赤裸裸的 requests 直接请求大概率被拒。我的习惯是在开发者工具里右键图片请求选 Copy as cURL再把 curl 里的请求头逐项搬进 Python。实际案例中九成情况补个 Referer 就过少数硬核平台会校验 Cookie 里的会话 Token——这种情况别自己拼 Cookie直接用requests.Session()先访问一次书籍页面让 session 自己把 Cookie 带上。另外图片 URL 里如果带着 token 签名参数务必原样使用。这种签名一般绑定了 IP 或会话一旦拆掉或者改动任何一个字符服务器都会判定为非法请求。代码里建议把image_base整段保存不要只截取域名以下的路径再去拼。5.3 半截图片与限流封禁下载完整性的两道坎网络波动时 requests 返回的响应可能不完整判断标准就是前面说的响应体大小。下载完再统一用 Pillow 打开验证一遍也能发现问题打不开的删掉重下即可。还有一个常见遗漏是完整性校验全部跑完后把 pages 目录里的文件数和配置里的 totalPages 对一遍对不上就看看缺口集中在哪个区间重点排查那个区间的命名规则。另一个容易翻车的是并发太高被限流。我遇到过下载到一半全部 403停十来分钟才能恢复的情况。如果发现连续失败率升高把线程降到 3加 0.1 到 0.3 秒的随机 sleep基本都能绕过去。别贪快flbook 这类平台的 CDN 对单 IP 的并发很敏感稳定比速度重要。最后再说回工具的边界。这套流程我建议只用在你有明确权限的内容上自己上传到平台的样书、平台公开免费授权的刊物、已经进入公共领域的资料下载到本地做离线备份或者整理数据集都非常省力。商业出版物的版权不是纸上谈兵下载来自用或许没人察觉向外传播就是实打实的侵权了。这个分寸自己把握好别让工具替你背锅。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门