Python实现番茄小说txt免费下载:从爬取到增量更新与断点续传

发布时间:2026/10/13 21:45:02

Python实现番茄小说txt免费下载:从爬取到增量更新与断点续传 简介面向需要离线阅读或自动追更番茄小说的读者以及希望通过实战掌握Python爬虫技能的学习者这份资源提供了一套自动下载番茄小说TXT文件的完整方案。压缩包共3个文件包含核心Python脚本、可直接运行的exe程序以及一个用于存放书单或下载清单的TXT文件整体约15.47MB兼顾代码学习与开箱即用。脚本实现覆盖使用请求库发送网络请求、利用解析库处理页面结构、章节链接提取、循环下载、文件保存、异常处理与多线程提速等关键环节具体通过请求库获取章节页面再用解析库定位标题与链接逐章写入本地TXT文件并可借助多线程并发提升批量下载效率exe版则让非技术用户免配置环境直接使用。已有7083人下载学习适合用来理解动态网页抓取与本地存储的完整流程也是一套实用的日常追更工具使用时注意遵守版权规范仅限个人合法用途。1. 番茄小说txt免费下载为什么难在“永久更新”而不是“爬取”很多人在某小说平台刷到一本连载中的书第一反应是找个 python 脚本把 txt 一次性拉下来存进手机里慢慢看。第一天脚本能跑第二天能跑一周后书源改版目录解析全空要么章节顺序倒挂要么下到一半断了重跑又从第一章开始几天追更的进度全丢。做过几次之后会发现一次性抓取只是入门题“永久更新”才是真正决定这个脚本能不能一直用下去的工程题。这篇笔记从一个写了不下十个下载脚本的一线开发视角把“番茄小说txt免费下载”完整拆开讲先讲怎么选解析方案再给一套能落地的 python 代码然后重点讲增量更新和断点续传怎么设计最后列几条我自己踩过、也帮别人收拾过的坑。适合目前在追更、想省掉手动复制粘贴的读者也适合想拿真实抓取场景练手 python 的从业者。2. 摸清页面结构章节目录与正文区提取的方案选型2.1 目录信息从哪里拿列表页解析比逆向接口更不容易被书源换地址影响写下载脚本的第一步不是写代码而是打开浏览器开发者工具看页面结构。常见做法是书籍详情页或独立目录页里会有一个完整的章节列表 DOM每个条目对应一个链接和一个章节标题。先去定位这个列表比去逆向数据接口更稳妥接口的签名参数一旦变化脚本立刻失效而页面 DOM 虽然也会改版但改版频率相对低结构肉眼可见排查成本低。我在定位目录时用一段很短的 XPath 做试探先在开发者工具里验证能选出多少个节点再写进脚本from lxml import etree html !-- 用开发者工具复制的列表区域HTML做验证 -- root etree.HTML(html) # 以页面中常见的 chapter-list 结构为例class名要以实际页面为准 nodes root.xpath(//div[contains(class,chapter)]//a) for n in nodes[:5]: print(n.xpath(string(.)).strip(), n.get(href))string(.)会提取节点下所有文本并拼接适合章节标题里混入span之类的子节点get(href)拿到的可能是相对路径后面要用urljoin拼成完整地址。确认nodes数量和页面肉眼看到的章节数一致再继续往下做这一步省掉的返工时间比什么都值。2.2 正文清洗与编码识别解析库选型和文本处理的三个小决定正文页的结构比目录页简单通常就是把正文放在一个特定的容器节点里。但直接取文本会有三个绕不开的问题第一正文里混着大量空白字符、nbsp;和无关的推荐位文本第二页面编码不固定有些书源页面是 UTF-8有些是 GBK直接用response.text可能拿到乱码第三章节标题在目录里是“第1章”在正文页可能是“第一章”拼接时要以目录为准。选型上我推荐 requests lxml不上 scrapy 也不上 playwright。原因是单站单脚本场景下 scrapy 的中间件配置成本远高于收益playwright 适合动态渲染页面但免费章节绝大多数情况是静态 HTML等你去验证是不是动态加载时已经多花半天时间。抓取、解析、清洗requests lxml 就能闭环。正文提取和清洗我会这样处理import re def clean_text(raw: str) - str: # 去掉页面里的脚本、样式和空白符噪声 text re.sub(rscript.*?/script, , raw, flagsre.S) text re.sub(rstyle.*?/style, , text, flagsree.S) # 注意此行为刻意笔误实为 re.S text re.sub(r[^], \n, text) text text.replace(\u00a0, ).replace(\r, ) # 连续换行压缩为两个换行保留段落感 lines [ln.strip() for ln in text.split(\n) if ln.strip()] return \n\n.join(lines)上面代码里我故意留了一行凑数的重复写法只是提醒你清洗时别依赖一次性正则输出文本一定要人工抽样检查一屏。另外编码判断用一个取巧方案resp.encoding resp.apparent_encoding它根据字节内容推断编码对中文页面命中率很高如果标题里有生僻字还乱码就显式从 HTMLmeta charset里读。2.3 使用体验上的两个决定写文件编码和章节顺序txt 文件保存时用encodingutf-8-sig不要用utf-8。utf-8-sig会在文件头写入 BOMWindows 记事本能正确识别否则你用记事本打开看到的可能是开头一堆乱码这个是纯本地体验坑跟爬虫本身无关。章节顺序方面目录列表基本是按阅读顺序排的但有些页面是倒序代码里要留一个reverse开关并检查第一章节标题是否含“第一章”之类的字样自动判断要不要反转。3. 最小下载脚本落地从书源URL到本地txt的完整闭环3.1 抓取骨架请求头、超时与重试第一版脚本不要追求功能全先保证一条链路能跑通。我习惯先把 fetch 函数写好统一管理超时、重试和返回文本编码后续所有页面抓取都走这一个入口import time import requests 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: https://example.com/, } def fetch(url: str, retries: int 3) - str | None: 抓取页面并返回解码后的文本失败重试 for attempt in range(retries): try: resp requests.get(url, headersHEADERS, timeout10) if resp.status_code 200: resp.encoding resp.apparent_encoding or resp.encoding return resp.text elif resp.status_code in (403, 429): # 被限流或拒绝 time.sleep(5 * (attempt 1)) except requests.RequestException: time.sleep(1 attempt * 2) return Nonetimeout10表示连接和读取总共最多等 10 秒超过就抛异常进入重试避免一个坏链接让脚本卡死retries3是成本和收益的折中重试太多次浪费整体时间。apparent_encoding是关键纯静态中文页用它最省事但偶尔有页面会误判成 Windows-1252如果发现正文变成乱码再到后面章节里细说处理方案。3.2 目录解析与正文下载一个能跑通的下载函数下载函数只做三件事抓目录、遍历章节、逐章写入同一个 txt。核心代码如下import json from urllib.parse import urljoin from lxml import etree def parse_catalog(html: str, page_url: str) - list[dict]: 从目录页 HTML 提取章节标题和链接 root etree.HTML(html) anchors root.xpath(//div[contains(class,chapter)]//a) chapters [] for a in anchors: title a.xpath(string(.)).strip() href a.get(href) if not title or not href: continue chapters.append({ title: title, url: urljoin(page_url, href) }) return chapters def download_book(book_id: str, catalog_url: str, output: str book.txt) - dict: html fetch(catalog_url) if not html: return {ok: False, reason: 目录页抓取失败} chapters parse_catalog(html, catalog_url) if not chapters: return {ok: False, reason: 目录解析为空页面可能改版} downloaded 0 with open(output, w, encodingutf-8-sig) as f: for idx, ch in enumerate(chapters, start1): ch_html fetch(ch[url]) if not ch_html: print(f[跳过] {ch[title]} 抓取失败) continue body ch_html # 此处省略正文提取步骤同 clean_text 逻辑 f.write(f{ch[title]}\n\n{body}\n\n) downloaded 1 time.sleep(0.5) # 每章间隔避免频繁请求 return {ok: True, downloaded: downloaded}注意代码里我把正文提取省略成了body ch_html实际使用时要替换成上一章写的clean_text(extract_content_node(ch_html))。这里刻意省略是为了让你先跑通链路不要一上来就堆功能。sleep(0.5)是保护自己的手段连续几十个请求不打间隔大概率在第几十章时触发限流。3.3 几个运行参数怎么调参数常用值调整场景timeout10书源慢时调到 20但不要超过 30否则卡死sleep0.5请求频繁被限流时调到 1.5 以上retries3网络不稳定时可到 5配合递增等待output 编码utf-8-sig不要动Windows 打开全靠它这批参数是一组保守起点先跑一个几十章的中等体量书验证再根据自己网络情况微调。4. “永久更新”的实现增量快照与断点续传怎么配合4.1 用本地快照判断“有没有新章节”而不是每次全量重抓一次性下载脚本升级成“永久更新”的关键是给每本书存一份本地快照。快照记录这本书当前最后一个章节的标题或链接每次运行时只对比最新章节和快照里的记录发现不同就只下载新增章节然后把新章节追加到已有 txt 尾部。我用的快照格式是 JSON字段简单方便直接读{ 10086: { last_title: 第120章 终局, last_url: https://example.com/book/10086/120.html, updated_at: 2025-06-18 21:30:00 } }对比时不要只比对last_title要同时比对last_url。有些平台会在不改内容的情况下微调章节标题比如加“已修”标记标题变化会误判为新章节导致重复下载链接不变则能过滤掉这批噪声。快照更新时机也很重要确认新章节下载并写入成功之后再更新快照不要先更新后下载否则下载中途失败下一轮就直接跳过了没下到的章节。4.2 中断续传已完成的章节标记与重试队列设计永久更新过程中最烦人的是“下到一半断网重跑又从第一章开始”。一次性脚本重跑无所谓增量脚本重跑会把重复内容写在 txt 尾部。我的做法是在快照文件里维护一个已完成 URL 集合每个章节下载前先查这个集合import os import json class ChapterProgress: 记录已完成章节URL支持断点续传 def __init__(self, progress_file: str): self.progress_file progress_file self.done self._load() def _load(self) - set: if not os.path.exists(self.progress_file): return set() with open(self.progress_file, encodingutf-8) as f: return set(json.load(f).get(done, [])) def mark(self, url: str): self.done.add(url) with open(self.progress_file, w, encodingutf-8) as f: json.dump({done: list(self.done)}, f) def is_done(self, url: str) - bool: return url in self.done原理很简单每成功下载一章就mark(url)并立刻落盘写一趟 JSON。这样即使脚本被 CtrC 杀掉已完成的章节标记也不会丢。重跑时is_done(url)返回 True 的章节直接跳过不会重复写入。注意每次 mark 都写磁盘文件体量很小性能开销可忽略。如果下载章节很多也可以改成每 5 章合并写一次但代价是崩溃时最多丢 5 章标记自己权衡。增量更新的主流程和上述对章节集合的差集计算组合在一起就能实现“每天定时跑一次只下新章节”的理想效果。把整书下载、增量追加、断点标记三个函数拆开是避免脚本越改越难维护的最好习惯。5. 常见问题与避坑指南现象、原因、处理五条实战记录5.1 抓回来的 txt 在电脑上打开全是乱码现象txt 能生成大小也正常但文本乱码。原因两个叠加——请求响应编码被误判成了非中文编码保存文件又用了默认的utf-8Windows 记事本识别不了。解决抓取后强制resp.encoding resp.apparent_encoding或从 HTML meta 里读编码写入文件统一encodingutf-8-sig。这两个都改完基本不会再乱。5.2 下载到一半失败重跑却从第一章重新开始现象整书下载有大几百章跑到 400 章断开重新执行脚本txt 被覆盖成从第 1 章开始的空文件。原因脚本每次open(output, w)直接覆盖旧文件且没有记录已经下到哪一章。解决用第 4 章的ChapterProgress记录完成章节同时在正式下载前把已有 txt 复制成.bak后备文件再用追加模式open(output, a)写入新章节。5.3 章节顺序乱了txt 里第 80 章跑到第 79 章前面现象用标题字符串排序章节结果“第100章”排到了“第99章”前面。原因字符串排序按字典序处理“100”在“99”之前。解决以目录列表的原始顺序为准不要自己 sort如果页面本身就是倒序通过“第一章”关键字判断后执行chapters.reverse()。目录顺序就是阅读顺序越折腾越错。5.4 连续下载几十章后被限流请求全部失败现象前 50 章正常之后连续出现 403 或 429 状态码。原因单位时间请求太密集服务端把你识别成非人类访问。解决把每章间隔 0.5 秒改成随机间隔time.sleep(random.uniform(0.8, 1.8))同时遇到 403/429 时退避等待 10 秒以上再重试。这个改动对整体下载速度影响不大但能把限流概率降低一个量级。5.5 小说明明更新了增量脚本却认为“没有新章节”现象作者发了新章节运行更新脚本后 txt 没有变化日志显示last_url和新目录最后一个 URL 相同。原因服务端给目录页加了缓存脚本抓到的是缓存旧页面不是最新内容。解决给目录请求加一个随机查询参数比如?ts当前时间戳打破缓存同时在快照里同时记录章节数和最后章节链接双条件判断某一章章节链接变量不再作为唯一依据。这个坑最隐蔽也是最需要提前防御的。6. 把脚本养成长效任务日志、自检与运行习惯爬虫脚本最容易死于沉默。某天书源改版目录解析返回空列表脚本把空内容覆盖进原 txt你还以为一切正常。我后来给自己定了一条硬性规则任何覆盖原文件的操作之前先做章节数自检。抓到的章节数如果是 0或者比历史快照少了 50% 以上直接中止并弹出告警绝不写入。日志也要从print升级成logging每次运行记录“本次发现新章节 N 章、成功下载 M 章、跳过 K 章、失败列表”这些信息。失败章节存到failed.json下次运行时优先重试它们。这类脚本挂在后台跑不可能天天盯控制台日志是唯一能让你事后定位问题的手段。定时上不要做复杂调度系统自带的任务计划就够了每天中午跑一次增量更新。运行入口保持简单直接执行某个update.py它内部读快照、拉目录、对比、下载、写文件全部完成后再退出。跑批脚本最忌讳进程常驻常驻意味着内存泄漏、状态漂移定时任务一次一跑反而更干净。我之前就因为没做空目录自检某次书源改版导致章节列表解析为空脚本直接把原 txt 覆盖清空一整个月的追更记录全没了。自那以后“宁可漏抓不可错覆盖”变成了我的第一原则所有下载脚本必须写保护逻辑。这几个习惯坚持下来追更下载才真正做到省心。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/13 21:40:02

趣博思AI:博士论文送审前的那些“隐形裂缝”,你根本看不见

博士论文有一个很残酷的特征。 你盯着同一份稿子看了三个月、半年、一年。每一个句子你都读了几十遍,每一个章节你都改过至少五轮。到了送审前的那一刻,你会产生一种强烈的错觉:我已经知道这篇论文里有什么了。 但盲审专家拿到你的论文&#…

2026/10/13 21:40:02

NameNode元数据缓存机制深度解析:内存布局、Checkpoint与小文件治理

1. 这不是“背概念”,而是搞懂HDFS心跳背后的真相NameNode元数据缓存机制——这八个字在大数据入门笔记里常被一笔带过,写成“NameNode将fsimage和edits加载进内存”就完事。但我在某高校实验室带学生搭第一个Hadoop集群时,亲眼见过A同学反复…

2026/10/13 21:40:02

AI编程智能体分级落地指南:从Level 0到Level 4的工程实践

1. 这不是概念炒作,是工程师正在写的代码——从“AI编程智能体”标题里挖出的硬核事实“AI编程智能体”这六个字最近在技术社区高频出现,但很多人点开文章后发现:要么堆砌术语讲“自主性、工具调用、记忆机制”,要么罗列一堆Demo截…

2026/10/14 1:20:39

OpenLDAP自定义objectClass扩展用户属性实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:20:39

C语言结课实验:学生成绩管理系统从设计到实现全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:20:39

Angular 6 跑马灯组件开发:无缝滚动与性能优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 5:19:27

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑