首页 / 资讯中心 / 文章详情

小说下载器与格式转换实战:从网页抓取到本地书库构建

小说下载器与格式转换实战:从网页抓取到本地书库构建 ★ FEATURED ARTICLE
这几年身边看小说的朋友越来越多从通勤路上到睡前床上手机里总得存几本连载。可在线阅读平台一会儿要会员、一会儿章节锁掉、一会儿又改版界面追更体验实在谈不上舒服。于是很多书友开始折腾本地阅读把小说原文抓下来转成趁手的格式导入Kindle、文石、微信读书或者自己常用的阅读App。“番茄小说下载器”这个名字说的就是这一类把网页端小说资源批量抓取、整理并转换格式的小工具。今天这篇指南我会完整拆解这类工具的核心逻辑、实操流程和格式转换的细节特别是很多人问的HTML转WPS表格、以及把下载文本整理成标准电子书格式的问题一篇讲透。我自己维护这个工具小项目快两年了从最早只会抓一个网站的裸脚本慢慢迭代成带队列管理、断点续传、格式自动识别的完整流程。这篇文章不是什么官方文档就是我踩坑总结出来的实操记录适合已经在用下载器但总在格式转换上翻车的朋友也适合完全没接触过、想从零搭一套本地书库的入门读者。1. 项目整体设计与思路拆解1.1 为什么要自己折腾一套下载器在线小说平台本质上是一个内容订阅系统今天能看的章节明天可能因为版权调整、作者修稿、平台下架而消失。我身边好几个朋友都经历过追了一年的书突然被下架评论区哀嚎一片书单从此变成残本。如果你只是随便翻翻在线看不影响但如果你想把喜欢的作品保存下来做笔记、做摘抄、跨设备同步阅读那么一套本地书库就是刚需。本地书库的好处很直接文件归你管格式你说了算导入任何阅读器都行不存在平台限制。而要把一个网站上的几十万字符变成规整的本地文件手动复制粘贴完全不现实批量抓取工具就是为这个场景设计的。我强调一下这类工具适合处理自己已经获得合法访问权限的内容比如平台免费章节、作者本人公开分享的文本或者你购买后需要做个人备份的内容。支持正版、尊重作者劳动是玩转这批工具的基本前提。1.2 下载器的工作原理与设计选型一个小说下载器把它拆到最底层就三个动作请求网页、解析章节内容、按规则存文件。请求网页这一步常见方案是用Python的requests库直接拉HTML或者用Playwright、Selenium模拟浏览器。两者差别很大requests轻量快速适合结构简单、没有复杂前端渲染的页面但如果你碰到的站点是Vue、React这类前端框架渲染内容直接拿requests拿到的HTML里根本没有正文这时候就得靠playwright这类无头浏览器让它先执行页面脚本再提取。我前期吃了不少亏用requests抓某个网站返回的页面里全是空的div还以为是IP被封后来才发现是页面本身是JS渲染。换无头浏览器之后问题立刻解决。解析环节BeautifulSoup和lxml是绝对主力。拿到HTML后先定位正文容器再做清洗。正文容器的定位是整个抓取流程里最脆弱的环节因为不同网站甚至同一网站的不同书籍页HTML结构可能完全不一致。我的经验是不要只写死一个class名最好做多级候选先试特定ID再试通用class最后用正则匹配正文特征逐层兜底。文件输出环节则是模板化的TXT就是纯文本拼接EPUB本质上是一个zip包里面装着XHTML、CSS、OPF目录文件和NCX导航文件MOBI则复杂一些。直接输出EPUB其实没有想象中难Python的ebooklib库封装掉了大部分底层细节写起来和构建一个网站导航差不多。设计上我的建议是请求、解析、输出三个模块解耦。一开始我图省事全写在一个脚本里后来单个网站改版直接导致整个脚本罢工。拆分之后即使解析规则崩了换一个解析函数就行下载队列、输出逻辑完全不受影响。2. 核心功能拆解与实操要点2.1 章节抓取与去重机制批量下载几百章内容最怕两件事重复抓取和漏章。重复抓取浪费流量、浪费时间漏章最头疼往往读到第八百章突然跳回到第七百章内容阅读体验直接崩掉。我的做法是引入一个章节指纹机制。每抓取一个章节对正文文本计算一个简单哈希值存进本地数据库。下次抓取前先比指纹如果这本小说已经存在相同正文指纹就跳过这章。这个方案比单纯比对章节号可靠因为有些站点章节号错乱但正文内容不会重复。指纹比对用Python的hashlib库就行十几行代码的事性价比极高。漏章问题的核心在于目录页解析。很多站点的目录页只展示最近几十章需要点击“查看更多”或者翻页才能拿到完整目录。这里我建议做一个目录收集循环若有“下一页”或“展开全书目录”按钮就持续请求直到拿完所有章节链接。收集完目录后再统一排序去重按序号重新编号避免个别章节序号缺失导致排序错乱。另外要重点注意记忆已下载的章节索引用SQLite存一个id、书名、章节号、指纹、下载时间字段的小表就够了。再次运行时优先查表发现已存在指纹的直接跳过。这算是最简单的增量更新方案比每次全量下载再人工对比省一百倍时间。2.2 异常重试与请求频率控制写到这一节的时候我想到的是刚做工具那会儿遇到的痛苦跑到第500章网络一抖脚本直接崩溃前面全部白抓。后来我加了两道保险——异常重试和请求间隔。异常重试的逻辑很直白给每个请求包装一个带重试次数的装饰器遇到超时、连接错误、HTTP 429状态码时按指数退避策略等待1秒、2秒、4秒后重试最大重试3到5次。指数退避比固定等待好用得多因为它不会在服务端还在限流时继续猛烈请求给服务器恢复留了时间。请求频率控制也不能省。我实测过对一般小说站每秒超过2个请求很快就触发限流或者临时封IP。把请求间隔设置在0.5到1.5秒之间配合随机抖动是最稳妥的模式。所谓随机抖动就是在固定间隔基础上增加一个随机偏移量比如0.5到1秒之间的随机时间这样请求模式不像爬虫也不容易触发行为检测。这里用time.sleep加上random.uniform就能实现。如果你一定要大规模抓取且对速度有要求可以引入线程池但要谨慎控制并发数。我个人的经验是并发数不要超过3且一定要共享同一个Session对象这样才能复用HTTP连接池减少握手开销。2.3 元数据整理与目录树结构下载下来的文件如果只有正文那只是半成品。真正好用的本地书库需要元数据支撑书名、作者、简介、封面、卷名、章节序号以及这些信息与正文的正确关联。抓取元数据的最佳时机是解析目录页的时候。目录页除了章节链接通常还带着书籍名称和作者信息。把章节链接和元数据一起保存后续渲染EPUB目录、生成文件名、建立书库索引都方便。这个环节多做一步后面转换格式会省很多麻烦。文件组织方面我给自己的书库定的规则是“作者/书名/格式/文件名”四级目录。比如“天蚕土豆/斗破苍穹/EPUB/斗破苍穹_第001章_陨落的天才.epub”。文件名里嵌了序号排序时不会乱目录按作者分组整理起来直观。虽然前期建目录稍微麻烦但后面书多了以后这一套规则能让你的本地书库完全不乱。3. 格式转换全攻略TXT、EPUB、PDF与HTML转WPS表格3.1 转换格式选型不同格式的适用场景下载器抓下来的初始文本一般是TXT或者HTML片段这只能算原料不是成品。要把原料变成适合自己阅读习惯的格式就需要格式转换。我自己的使用场景可以分成三类第一类是纯文字阅读用TXT最省事所有设备都能打开缺点是排版表现力弱插图、注释、加粗这些信息全丢。第二类是重度阅读器用户用EPUB是主流选择它支持目录导航、字号调整、自定义CSS主题、封面和元数据导入微信读书、Apple Books、Kindle需转制后都体验良好。第三类是打印或资料归档场景PDF更像静态快照适合需要固定版面的场景但PDF在小屏幕上重排能力为0我不建议把它当作主力阅读格式。关于格式转换几乎所有人的启蒙工具都是Calibre。它的功能远超一个转换器而是一个完整的电子书管理生态。下载器抓到TXT之后用Calibre一键转EPUB方便得很。但我不建议所有转换都交给Calibre因为它的批量转换对元数据管理不够透明很多时候我想精确控制封面、目录层级和格式化规则还是需要自己写脚本。3.2 HTML转换WPS表格的实操“鼠鼠”式需求最近有个热搜词叫“html格式转换wps表格”你可能觉得奇怪小说下载和表格有什么关系但这件事在书友圈里其实很常见。追更追了好几百章很多人会顺手把网页正文存档成本地HTML之后想做成一份“章节-字数-关键词”的整理清单放进Excel或WPS表格里做阅读记录还有做自媒体素材的人需要把小说里的金句、设定、章节摘要批量提取成表格。这就是“HTML转WPS表格”的真实场景需求。最简单的方案是用Python的pandas库先解析HTML中的表格标签再输出成xlsx。如果HTML里没有标准表格而是普通正文段落那么先做一套“模板清洗字段提取”流程。比如读取保存的HTML文件筛出章节标题和正文长度按行写入DataFrame最后导出Excel。为了演示下面我给一个可直接改用的脚本示例import pandas as pd from bs4 import BeautifulSoup def html_to_table(html_path, output_path): with open(html_path, r, encodingutf-8) as f: soup BeautifulSoup(f.read(), html.parser) chapters [] # 假设正文段落都在 div classchapter-content 内 content soup.find(div, class_chapter-content) if not content: content soup.body for tag in content.find_all([h1, h2, h3, p]): text tag.get_text(stripTrue) if not text: continue chapters.append({ 元素: tag.name, 文本长度: len(text), 内容: text[:100], # 表格里只放前100字完整文本另行保存 完整路径: html_path, }) df pd.DataFrame(chapters) df.to_excel(output_path, indexFalse, engineopenpyxl) print(f已输出 {len(df)} 行到 {output_path}) html_to_table(book.html, book_chapters.xlsx)用的时候注意两点第一HTML的编码必须是UTF-8否则中文全变问号第二HTML里如果嵌套了很多脚本标签和样式标签最好在提取前先把它们decompose掉避免表格里混入乱码。脚本跑完以后用WPS直接打开xlsx数据就是规整的表格。另外如果你不想写代码WPS本身也有“从HTML导入”的入口。用WPS打开HTML文件WPS会把HTML里的表格自动识别为单元格。但这个方案只适合原本就是标准table的数据普通小说正文套进去效果很差。所以“鼠鼠”们要整理小说阅读数据我还是推荐走pandas生成Excel这条路一劳永逸。3.3 EPUB制作的细节处理EPUB是目前阅读体验最均衡的格式绝大多数阅读App都原生支持。用ebooklib库加上jieba等分词工具做标签时用可以做出非常精细的EPUB文件。下面是我常用的一个精简流程。先建立EpubBook对象设置元数据再逐章添加章节内容。每个章节其实就是一个XHTML文件正文内容放在标签里。为了让目录能正确显示章节必须设置title属性且添加顺序就是阅读顺序。封面的话单独下载封面图片然后用EpubItem加进去。这一步说起来简单但有几个坑。第一个坑是目录层级。如果你想做“卷/章”两级目录必须在添加章节时传入chapter.metadata把父级信息带上。ebooklib支持向EpubNavItem传子项但写法比较繁琐。我建议你在生成章节列表时就建立树状结构而不是先平铺再后处理。第二个坑是CSS样式。EPUB默认样式在不同阅读器下表现差异很大。我的经验是内置一套最简单的CSS定义body字体大小用em行距控制在1.6左右标题用h1/h2正文用p标签不要用table、float这类重排版依赖属性。第三个坑是图片处理。小说配图通常分辨率低如果直接插入EPUB会让体积爆炸。建议在插入前统一压缩。用Pillow把图片最长边缩到1000像素以内质量调到85%体积往往能缩减60%以上。3.4 PDF和MOBI按需制作PDF不是我推荐的阅读格式但如果你有打印需求或者要给长辈看Kindle设备对EPUB支持还行但很多老旧墨水屏只认PDF/MOBI那也得会做。PDF生成用reportlab就能达成。核心思路是先注册中文字体再按页面尺寸排版段落。这里有个严重的坑reportlab默认字体不支持中文直接输出会变成方框。你得先加载系统中文字体文件比如文泉驿、思源宋体或者Noto Sans CJK的ttf文件才能正常渲染中文。MOBI格式则复杂一些。现在主流的方案是用Calibre把EPUB转MOBI这个转换已经非常成熟少走弯路。以前我自己尝试直接生成MOBI那个文件头结构太复杂实在没必要手搓。Calibre转出来的MOBI在Kindle上适配度很高。我的经验是先把文本整理成优质EPUB再统一批量转换而不是从TXT直接跳到MOBI因为TXT无法携带目录和元数据转出来的MOBI目录质量很差。4. 常见问题与排查技巧实录4.1 为什么章节抓不全或者抓乱了抓取结果是“缺章少节”或者顺序错乱这是最常遇到的问题通常出在目录页解析环节。很多站点的目录是一个懒加载列表前30章静态展示后面的章节要滚动或者点击“加载更多”才会出现在DOM里。如果你只抓了一次页面自然拿不到完整目录。我建议做一个自检步骤抓完目录后统计章节总数再去详情页校验最后一章的章节号两个值对不上就说明目录没抓全。此时需要换成无头浏览器模拟滚动或者直接从网站的API接口拿目录数据。有些站点前端会请求一个JSON格式的目录接口这个接口返回的目录数据结构清晰、字段完整比解析HTML靠谱得多。4.2 转换后中文乱码问题出在哪乱码问题在Windows环境下特别严重。根源在于文件编码不一致网页正文是UTF-8而你在Windows上有时用记事本打开TXT时默认按GBK解码两边对不上就全是“锟斤拷”。我处理这个问题有三板斧。第一抓取保存时就统一编码全部存成UTF-8无BOM格式避免跨平台时出幺蛾子。第二写代码读取字符串时优先用requests的response.encoding属性或者apparent_encoding自动判断但最终写入文件时必须强制指定encodingutf-8。第三如果你在Windows上要手动打开TXT用VS Code替代记事本打开即可VS Code会正确识别UTF-8编码。EPUB乱码的原因则多半是缺少元数据字段中的dc:language设置。如果EPUB文件没有声明语言部分阅读器会按系统区域猜测编码中文内容就可能变成乱码。创建EpubBook对象后第一时间设置book.set_language(zh-CN)这个习惯能省掉很多人为的编码问题。4.3 转换出来的WPS表格数据不对齐好几个人拿着热词“html格式转换wps表格”来问我说自己用WPS打开HTML文件后数据全挤在一列里完全没法看。这个问题的本质是WPS对HTML的解析逻辑和浏览器不同。WPS只会识别标准table标签不会解析divcss模拟的表格布局。如果你遇到小说站点的HTML是div并排模拟的章节目录而非table直接导入肯定失败。正确的做法是先解析结构把它转换成真正的表格数据。用我上面的pandas脚本把div中需要的内容提取出来生成二维列表再写入excel这样每个单元格就各归其位了。另一个容易被忽略的点是合并单元格。如果源HTML里存在rowspan、colspan这类属性pandas读取后会有NaN空值需要在生成表格前用fillna处理或者按引用逻辑填充前值。4.4 下载被限流和IP临时封禁怎么办碰到被封不要慌先判断自己是触发了频率限制还是IP封禁。如果返回429或者页面里出现验证码属于前者解决办法就是放慢速度、增加延时如果直接返回403或者拒绝连接属于后者通常是短时间内请求太密集。根本性的解决方案是控制并发数、增加随机延时、尽量使用网站的移动端页面接口因为移动版页面往往结构更轻量、限制更宽松。此外用Session维持Cookie也有帮助可以避免每一次请求都被当成新用户。有些登录后才能看全文的站点需要你在脚本里手动填入登录后的Cookie这个行为相当于模拟一个正常用户在阅读风险要小很多。这里我还想提醒一句任何工具的使用都应该在法律允许的范围内不要恶意攻击站点、不要绕过付费墙抓取付费内容保护原创作者的合法权益。下载器这类工具最好的定位是个人备份和学习研究。5. 扩展思路与我的个人心得5.1 从下载器到完整书库的进阶玩法当你的下载器稳定运行、格式转换也顺手以后可以考虑把整个体系升级成一个个人书库。我目前的做法是下载器负责抓取入库Calibre负责书库管理和格式转制NAS负责定期同步备份手机端用同步阅读。整个闭环下来基本等同于自建了一个私有化的阅读中台。书库管理的关键点在于元数据一致性和去重。我建了一个ISBN风格的自定义ID每本书入库时生成唯一编号。后续所有操作——格式转换、封面替换、笔记关联——都围绕这个ID展开。这样即使同一本书有多个格式文件书库里也只显示一条主记录不会出现两本重复的《斗破苍穹》让你看着难受。另外如果你的下载器支持自定义脚本钩子我强烈建议把“正文查错”步骤加进去。检查正文里有没有“本章未完”“请记住本站域名”这类站点噪声自动清洗掉。这一步在抓取阶段完成后立刻做比全文入库后再清理要高效得多。5.2 这个项目后续还能怎么扩展很多朋友问我下一步该往哪个方向加功能我的第一推荐是做一个“增量巡检”模块定时去源站点比对最新章节与本地书库的章节数变化有新增就自动下载并转换。比如用GitHub Actions的定时任务每天早上8点跑一次巡检抓取当天的更新章节转换成EPUB推送到你的阅读设备。基本实现了“追更无感化”。第二推荐是做“章节摘要与关键词抽取”。用简单的结巴分词加TF-IDF算法就能给每一章生成关键词列表方便你回溯情节。这个功能不需要大模型轻量跑在本地配合阅读记录可以做非常有意思的年度阅读报告。第三推荐是“多书源聚合”。不同站点内容收录情况不同有的章节更全、有的排版更干净。做一个书源优选逻辑对同一本书自动选择最优源站点抓取能在很大程度上提升成功率。具体的做法是维护一个书源列表每个书源配置独立的解析规则抓取前先测试可用性按优先级尝试。5.3 我踩过数次坑最后的真心话这套流程跑了这么长时间最大的感悟是“工具是次要的规范才是首要的”。一开始我的标题乱命名、格式乱来、编码混乱后面整理书库的时候耗费了成倍的时间。吃过亏以后我把命名规则、目录层级、编码标准全部定死再配合脚本自动化执行整个维护成本立刻降下来了。另外一个小建议是做好备份。本地书库如果因为硬盘损坏全没了那真是欲哭无泪。我现在是每次下载完新书立刻同步到NAS周五再手动做一次云端异地备份。数据安全感带来的快乐和追更的快乐一样重要。最后再分享一个小技巧做格式转换时别急着把原始抓取文件删掉。原始HTML保留了网页的所有排版信息和插图链接是后续一切二次处理的素材底稿。哪怕你当下只想转TXT也建议把HTML归档保留。等哪天想要精排版EPUB或者提取配图的时候你就知道这份底稿有多值钱了。
阅读完成 · 觉得有帮助?
咨询建站