Trae+Python小说爬虫2:用Selenium搞定“单页章节链接+双页小说章节”式断点续传
1. 双页章节结构下断点续传为什么会重复抓取小说站点里有一类很常见的结构目录页把所有章节链接都列出来了看起来是“单页章节链接”但点进任意一章会发现正文被拆成两页第一页是xxx.html第二页是xxx_2.html。这种“单页章节链接 双页小说章节”的组合用普通爬虫跑起来最容易出的问题不是抓不到而是中断之后重跑会重复抓。原因在于进度记录只记了主链接。你第一次跑到第 80 章主链接80.html抓完写进进度文件但补充页80_2.html还没抓程序崩了。重启后读进度发现80.html已在集合里直接跳过于是第 80 章的后半段永远缺失反过来如果进度只记补充页主链接又会被重复下载。更糟的是有些实现把两个 URL 当成两个独立章节进度文件里混着80.html和80_2.html重启后判断逻辑对不上整章重抓一遍TXT 里就出现重复段落。我试过最省事的做法是把“一章”当成一个逻辑单元来管理进度主链接和补充链接共享同一个章节标识只有两页都成功写入文件才把这个章节标记为完成。这样中断重启时要么整章跳过要么整章重来不会出现半章缺失或半章重复。这里还要区分两个概念。断点续传解决的是“从哪继续”去重解决的是“同一章不写两遍”。前者靠进度文件后者靠章节标识归一化。很多人把两者混在一起用 URL 字符串直接做 key结果80.html和80_2.html被当成两章去重就失效了。正确做法是从 URL 里抽出章节号80作为唯一 key主链接和补充链接都映射到它。Trae 在这个环节的价值是帮你把“章节号提取 双页合并 进度落盘”这套逻辑一次性写对。你不需要手写正则去猜每个站点的 URL 规律把结构描述清楚让 Trae 生成可运行的骨架再按实际站点微调选择器就行。下面我会先讲清楚 TaoToken 怎么配再给可直接复制的断点续传配置最后用一次真实的中断重启验证它确实不重复。适合谁看已经用 Selenium 抓过单页小说、现在遇到双页章节结构、并且被重复抓取困扰的人。如果你还没搭过基础爬虫建议先看单页版本那篇把 driver 初始化和章节列表加载跑通再回来加双页逻辑。2. TaoToken 前置配置与 Trae 接入准备在 Trae 里让模型帮你写爬虫代码第一步是把模型接入配好。TaoToken 提供的是 OpenAI 兼容接口Trae 的自定义模型配置里填 Base URL 和 API Key 就能用。我实测下来接入过程不复杂关键是三个值要对上Base URL、Key、Model ID。先拿 Key。打开 https://taotoken.net/api-keys 登录后创建一个新的 API Key复制出来。这个 Key 只在创建时完整显示一次建议先存到本地临时文件别直接贴到会公开的代码里。Base URL 用 https://taotoken.net/api 注意结尾不要多加/v1Trae 的配置项里通常会自动补路径。如果你填成https://taotoken.net/api/v1有些版本会拼成/v1/v1/chat/completions直接 404。Model ID 按你实际要用的模型填。做爬虫代码生成选一个代码能力强的就行具体可用列表在 https://taotoken.net/doc 里能查到。填的时候注意大小写和连字符写错会报 model not found。Trae 里配置自定义模型的位置在设置里的模型管理新增一个 OpenAI 兼容 provider把上面三个值填进去。保存后新建一个对话选这个模型发一句“你好”测试连通。如果返回正常说明接入成功。这里有个容易踩的坑Trae 的某些版本对 Base URL 的校验比较严如果你填的地址返回 401先检查 Key 有没有多余空格再检查 Base URL 是不是被自动加了斜杠。401 基本都是 Key 问题不是地址问题。配好之后你就可以在 Trae 里直接描述需求让它生成爬虫代码。比如把“单页章节链接 双页小说章节 断点续传”这三个关键词写进 promptTrae 会给你一个带进度文件和去重逻辑的骨架。下面这段 prompt 可以直接用用 Python Selenium 写一个小说爬虫要求 1. 目录页是单页所有章节链接都在一个列表里 2. 每个章节有两个页面第二页是在第一页链接后加 _2 再接 .html例如 80.html 和 80_2.html 3. 支持断点续传进度保存到 json 文件重启后从断点继续 4. 同一章的主链接和补充链接共享同一个章节号只有两页都抓完才标记完成避免重复下载 5. 章节标题和正文用多个 XPath 选择器兜底抓不到时刷新重试。Trae 生成代码后你要做的是把选择器换成目标站点的真实 XPath把保存路径改成自己的目录。代码逻辑不用大改重点核对进度文件的读写和章节号提取这两处。如果你打算长期跑爬虫任务或者想让 Trae 持续帮你迭代代码可以考虑 Coding Plan地址是 https://taotoken.net/coding-plan 。它适合这种需要多轮修改、反复调试的场景比单次对话更省事。3. 可复制的断点续传配置与双页合并逻辑这一节给的是可以直接抄进项目的配置和核心代码。先看进度文件的结构。我建议用 JSON 存一个对象而不是简单数组这样能记录每章的状态方便排查。{ novel_name: 阴物商人, base_url: https://www.tomcan.cc/shu/167845/, completed_chapters: [1, 2, 3, 80], partial_chapters: { 81: { main_done: true, supplement_done: false } }, last_update: 2024-06-01T12:00:00 }completed_chapters存的是章节号不是 URL。partial_chapters记录那些只抓了一半的章节重启后优先补这些再继续后面的。这样即使补充页抓失败也不会把整章标记成完成。章节号提取函数是关键它决定了主链接和补充链接能不能归一到同一个 keyimport re from urllib.parse import urlparse def extract_chapter_id(url): 从章节 URL 中提取章节号主链接和 _2 补充链接返回同一个值 path urlparse(url).path filename path.split(/)[-1] # 去掉 _2 后缀和 .html 扩展名 name re.sub(r_2\.html$, , filename) name re.sub(r\.html$, , name) # 提取纯数字章节号 match re.search(r(\d), name) if match: return match.group(1) return None def build_supplement_url(main_url): 由主链接生成补充页链接80.html - 80_2.html base, ext main_url.rsplit(., 1) return f{base}_2.{ext}注意build_supplement_url用的是rsplit(., 1)只切最后一个点。如果 URL 里带查询参数比如80.html?page1这种写法会出错需要先把查询串剥掉再拼。大多数小说站不带参数但你要心里有数。进度管理类这样写import json import os import logging class ProgressManager: def __init__(self, progress_file): self.progress_file progress_file self.data self._load() def _load(self): if os.path.exists(self.progress_file): try: with open(self.progress_file, r, encodingutf-8) as f: return json.load(f) except Exception as e: logging.warning(f进度文件损坏重建: {e}) return { completed_chapters: [], partial_chapters: {}, last_update: } def is_chapter_done(self, chapter_id): return chapter_id in self.data[completed_chapters] def mark_main_done(self, chapter_id): partial self.data[partial_chapters].setdefault(chapter_id, {}) partial[main_done] True self._save() def mark_supplement_done(self, chapter_id): partial self.data[partial_chapters].setdefault(chapter_id, {}) partial[supplement_done] True if partial.get(main_done): self.data[completed_chapters].append(chapter_id) self.data[partial_chapters].pop(chapter_id, None) self._save() def _save(self): from datetime import datetime self.data[last_update] datetime.now().isoformat() with open(self.progress_file, w, encodingutf-8) as f: json.dump(self.data, f, ensure_asciiFalse, indent2)核心逻辑在mark_supplement_done只有主链接和补充链接都完成才把章节号移进completed_chapters并从partial_chapters里删掉。这样重启后is_chapter_done判断的是整章状态不会出现半章跳过。主循环里这样用for index, (chapter_url, title) in enumerate(chapter_links, 1): chapter_id extract_chapter_id(chapter_url) if not chapter_id: logging.warning(f无法提取章节号跳过: {chapter_url}) continue if progress.is_chapter_done(chapter_id): logging.info(f第 {chapter_id} 章已完成跳过) continue supplement_url build_supplement_url(chapter_url) # 抓主链接 if not progress.data[partial_chapters].get(chapter_id, {}).get(main_done): ok process_single_chapter(chapter_url, index, total, f, is_supplementFalse) if ok: progress.mark_main_done(chapter_id) # 抓补充链接 if not progress.data[partial_chapters].get(chapter_id, {}).get(supplement_done): ok process_single_chapter(supplement_url, index, total, f, is_supplementTrue) if ok: progress.mark_supplement_done(chapter_id)写入文件时用追加模式a不要每次重开w否则重启后会把之前的内容覆盖掉。文件头只在第一次创建时写一次判断依据是进度文件是否存在。如果你用 Trae 生成代码把上面这段进度管理逻辑贴给它让它整合进你的爬虫类里。Trae 会帮你处理process_single_chapter的返回值判断和异常分支你只需要确认章节号提取和补充链接拼接这两处符合目标站点规律。4. 验证请求中断重启后从断点继续且不重复配置写完必须做一次真实的中断重启验证否则你不知道进度逻辑到底对不对。验证方法很简单跑到一半手动杀掉进程再重启看日志和输出文件。先准备一个测试用的小站点或者用你目标站点的前 10 章做测试。把chapter_links截断到前 10 章避免跑太久。启动爬虫观察日志INFO - 处理第 1/10 章: https://example.com/book/1.html INFO - 处理第 1/10 章补充部分: https://example.com/book/1_2.html INFO - 保存进度: 1 INFO - 处理第 2/10 章: https://example.com/book/2.html ...跑到第 5 章主链接抓完、补充链接还没抓的时候按 CtrlC 中断。此时进度文件应该是这样的{ completed_chapters: [1, 2, 3, 4], partial_chapters: { 5: { main_done: true, supplement_done: false } } }重启爬虫观察日志。正确行为是第 1 到 4 章直接跳过第 5 章只抓补充链接第 6 章开始正常抓。日志应该出现INFO - 第 1 章已完成跳过 INFO - 第 2 章已完成跳过 INFO - 第 3 章已完成跳过 INFO - 第 4 章已完成跳过 INFO - 处理第 5/10 章补充部分: https://example.com/book/5_2.html INFO - 处理第 6/10 章: https://example.com/book/6.html然后检查输出 TXT确认第 5 章的后半段被补上了且第 1 到 4 章没有重复出现。用grep -c 【第5章统计标题出现次数应该是 2 次主标题 续标题不是 4 次。如果重启后第 5 章主链接又被抓了一遍说明mark_main_done没生效或者is_chapter_done判断的是 URL 而不是章节号。回去检查extract_chapter_id的返回值打印出来看是不是None。如果第 5 章补充链接没抓直接跳到第 6 章说明partial_chapters的读取逻辑有问题可能是 JSON 反序列化后 key 类型变了。JSON 的 key 永远是字符串如果你用整数5去查会查不到。统一用字符串做 key。验证通过后把chapter_links恢复成完整列表正式跑一遍。跑的过程中可以随时中断重启后都会从断点继续。这就是断点续传该有的样子。补充一个验证技巧在process_single_chapter里加一行日志打印当前章节号和是否补充页方便对照。日志级别设成 INFO 就够DEBUG 会刷屏。5. 本篇常见报错排查跑双页断点续传最容易撞上的几个报错我列一下对照着查。401 Unauthorized这个跟爬虫本身无关是 Trae 接入 TaoToken 时 Key 或 Base URL 配错了。检查 https://taotoken.net/api-keys 里的 Key 是否复制完整Base URL 是否是 https://taotoken.net/api 。如果 Trae 报local proxy failed通常是本地网络或代理设置问题先确认 Trae 能正常访问外网再检查模型配置里的地址有没有被自动改写。reading choices of undefined这是模型返回体解析失败多半是 Base URL 拼错导致返回了 HTML 错误页而不是 JSON。把 Base URL 改成不带/v1的原始地址重新测试。如果还报去 https://taotoken.net/doc 核对当前模型 ID 是否可用。OAuth 相关报错如果你用的是需要 OAuth 的客户端注意 TaoToken 的 API Key 是直接填在配置里的不需要走 OAuth 流程。看到 OAuth 报错说明你选错了认证方式换成 API Key 认证。invalid session idSelenium 的 driver 会话失效常见于长时间运行或页面超时。代码里已经有重建 driver 的逻辑但要注意重建后要重新driver.get(self.url)建立会话否则后续请求还是会失败。如果频繁出现把--headlessnew换成旧版--headless试试某些 Chrome 版本对新 headless 支持不稳定。章节内容过短被跳过process_single_chapter里有个len(content.strip()) 50的判断如果正文确实很短比如只有一句话的过渡章会被误判成抓取失败。把阈值调低到 20或者改成判断是否包含中文。另外补充页的内容通常比主链接短阈值别设太高。补充链接 404不是所有章节都有第二页。有些站点只有部分章节分页你无脑拼_2.html会 404。处理办法是在process_single_chapter里对 404 做特殊处理返回一个“不存在”状态而不是“失败”。进度管理里把这种情况标记成补充页不存在直接完成整章。判断方法捕获driver.get后的页面标题如果包含“404”或“不存在”就当作补充页不存在。进度文件写入乱码json.dump要加ensure_asciiFalse文件打开要指定encodingutf-8。Windows 默认编码是 GBK不加这两处中文小说名会变成乱码重启后读进度直接报错。重复写入同一章检查文件打开模式。如果每次循环都用w打开前面的内容会被清空。正确做法是整个抓取过程只打开一次文件用a追加。如果中途重启判断文件是否存在存在就用a不存在才用w写文件头。章节顺序错乱chapter_links排序失败会导致章节乱序。extract_chapter_number要从 URL 和标题里都尝试提取数字取第一个匹配。如果站点章节号不是纯数字比如chapter-80正则要相应调整。排查顺序建议先看日志最后几行定位是请求失败还是解析失败再看进度文件确认状态是否符合预期最后看输出 TXT确认内容有没有重复或缺失。三步走完问题基本能定位。6. 长期跑爬虫任务的接入方式选择爬虫代码写完只是开始真正麻烦的是长期维护。站点改版、选择器失效、反爬升级都需要你不断改代码。这时候选对模型接入方式能省不少事。如果你只是偶尔改改代码用模型对话就够了地址是 https://taotoken.net/chat 。把报错日志和代码片段贴进去让它帮你定位问题改完复制回项目。这种方式适合单次调试不涉及多轮上下文。如果你要持续迭代这个爬虫比如加多站点支持、加代理池、加重试策略建议用 Coding Plan地址是 https://taotoken.net/coding-plan 。它能保持较长的上下文你前面定义的进度管理逻辑、章节号提取规则它都记得住不用每次重新解释。改代码时直接说“把补充页 404 的处理加上”它就能在现有逻辑上改不会把之前的断点续传逻辑改坏。接入文档在 https://taotoken.net/doc 里面有完整的 Base URL、认证方式和模型列表。配置时如果拿不准先看文档再填比反复试错快。最后说一个实用技巧把爬虫的进度文件和日志文件分开存进度文件放项目目录日志文件放logs/子目录。这样清理日志时不会误删进度重启后还能从断点继续。日志按天切分跑一周也不会撑爆磁盘。代码跑通之后你可以把extract_chapter_id和build_supplement_url这两个函数抽出来做成站点配置。不同站点的 URL 规律不一样但断点续传的框架是通用的。换站点时只改这两个函数和 XPath 选择器进度管理逻辑不用动。这样你的爬虫就能复用到多个小说站而不用每次重写。