
3个坑搞定全本小说下载器,新手避坑指南
看了一堆教程还是不会写项目?别慌,这太正常了。
很多新手卡在“代码能跑”和“项目能用”之间,差的就是那层窗户纸。
今天咱们不聊虚的,直接上手写一个全本小说下载器。
这是新手避坑的绝佳练手项目,逻辑简单,涉及网络请求、文件处理、异常捕获。
写完这个,你对 Python 的实战理解会提升一大截。
很多兄弟在掘金技术社区分享过类似经验,说写爬虫是入行第一关。
没错,但很多人死在了“反爬”和“编码”这两个坑里。
这篇文章,我就带你把这些坑全填了。
咱们目标明确:写出一个稳定、可运行、能处理常见报错的下载器。
不用复杂的 Selenium,纯 Python 标准库 + requests 就够了。
准备好你的编辑器,咱们开始。
概念速懂:下载器到底在干嘛
很多人以为下载器就是“下载文件”,其实没那么简单。
一个合格的下载器,核心逻辑分三步:解析、获取、保存。解析:从网页 HTML 里找出每一章的链接。
获取:根据链接去请求服务器,拿到正文内容。
保存:把内容格式化,写入本地 TXT 或 HTML 文件。听起来很简单,对吧?
但坑就在细节里。
比如,网页编码是 UTF-8 还是 GBK?
比如,请求太快被封 IP 怎么办?
比如,某章加载失败,程序是崩溃还是跳过?
这些问题,教程里往往一笔带过,但实战中全是泪。
我们今天要解决的,就是这些“隐形杀手”。
先理清技术栈:requests:发送 HTTP 请求,比 urllib 好用太多。
BeautifulSoup4:解析 HTML,提取数据的神器。
time:控制请求频率,避免被封。
os/pathlib:处理文件路径,跨平台兼容。为什么不用 Selenium?
因为对于纯文本小说,Selenium 太重了。
加载浏览器、渲染 JS,耗时且不稳定。
除非网站是纯 JS 渲染(如 Vue/React SPA),否则 requests 足矣。
90% 的小说网站,都是服务端渲染 HTML,requests 完全够用。
这也是新手避坑的关键:不要过度设计。
能用简单方案解决的,别上重型工具。
环境准备:工欲善其事
别急着写代码,环境没配好,后面全是坑。
第一步,安装依赖。
打开终端(Mac/Linux)或 CMD/PowerShell(Windows)。
执行以下命令:
pip install requests beautifulsoup4如果你用的是国内网络,pip 可能很慢。
加上清华镜像源,速度起飞:
pip install requests beautifulsoup4 -i https://pypi.tuna.tsinghua.edu.cn/simple第二步,确认 Python 版本。
建议 Python 3.8+。
Python 2 已经退役,别用。
在终端输入 python --version 检查。
第三步,创建项目结构。
不要把所有代码扔在一个文件里。
养成好习惯,建个文件夹 novel_downloader。
里面放 main.py 和 config.py(可选)。
咱们今天为了简单,先写一个 main.py。
后续进阶再拆分模块。
还有一个关键点:User-Agent。
默认 requests 的 UA 是 python-requests/2.x.x。
很多网站会直接拦截这个 UA,返回 403 或空页面。
必须伪装成浏览器。
比如 Chrome:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
这个 UA 字符串,建议存成常量,方便修改。
核心语法:逐行拆解关键代码
现在咱们写核心逻辑。
我分三段讲解,每段都是可运行的片段。
先看最基础的:请求网页并解析。
import requests
from bs4 import BeautifulSoup# 1. 设置请求头,伪装成浏览器
headers = {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
}# 2. 请求目录页
url = https://example-novel.com/catalog/12345
try:response = requests.get(url, headers=headers, timeout=10)response.raise_for_status() # 关键:检查 HTTP 状态码# 自动检测编码,防止乱码response.encoding = response.apparent_encodinghtml_content = response.text
except requests.exceptions.RequestException as e:print(f请求失败: {e})exit()# 3. 解析 HTML
soup = BeautifulSoup(html_content, html.parser)
# 假设章节列表在 div class=chapter-list 下的 a 标签
chapter_links = soup.select(div.chapter-list a)print(f找到 {len(chapter_links)} 个章节)
for link in chapter_links:title = link.get_text(strip=True)href = link.get(href)# 处理相对路径if href.startswith(/):full_url = https://example-novel.com + hrefelse:full_url = hrefprint(f章节: {title} - {full_url})重点解析:response.raise_for_status():这是新手最爱漏的。如果服务器返回 404 或 500,response.text 可能是错误页面 HTML,而不是小说内容。加上这个,错误会直接抛出异常,方便你捕获。
response.encoding = response.apparent_encoding:requests 默认猜的编码可能不对,尤其是中文网站。用 apparent_encoding 让 chardet 库自动检测,能解决 80% 的乱码问题。
BeautifulSoup 的 select:用 CSS 选择器比 find_all 更直观,写起来更快。接下来,看单章内容提取。
假设章节页结构是:
div id=contentp第一章内容.../pp第二章内容.../p
/divdef fetch_chapter_content(url, headers):try:resp = requests.get(url, headers=headers, timeout=10)resp.raise_for_status()resp.encoding = resp.apparent_encodingsoup = BeautifulSoup(resp.text, html.parser)# 找到内容区域content_div = soup.find(div, id=content)if not content_div:print(未找到内容区域,可能结构变更)return # 提取所有 p 标签文本paragraphs = content_div.find_all(p)text = \n\n.join([p.get_text(strip=True) for p in paragraphs])return textexcept requests.exceptions.RequestException as e:print(f获取章节失败 {url}: {e})return 避坑点:find_all(p):不要直接 get_text() 整个 div,那样会包含换行符和空格,导致 TXT 文件杂乱。提取 p 再拼接,格式更干净。
strip=True:去掉首尾空格,保持文本整洁。完整代码示例:组装成可用工具
现在,把前面的片段拼起来,加上文件保存和异常处理。
这是一个完整的、可运行的脚本。
你可以直接复制,修改 URL 测试。
import requests
from bs4 import BeautifulSoup
import time
import os# 配置区
BASE_URL = https://example-novel.com
CATALOG_URL = f{BASE_URL}/catalog/12345
SAVE_DIR = ./novel_output
DELAY = 2 # 每次请求间隔秒数,防封headers = {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
}def init_dir():if not os.path.exists(SAVE_DIR):os.makedirs(SAVE_DIR)print(f创建目录: {SAVE_DIR})def get_chapter_list(url):获取所有章节链接和标题resp = requests.get(url, headers=headers, timeout=10)resp.raise_for_status()resp.encoding = resp.apparent_encodingsoup = BeautifulSoup(resp.text, html.parser)# 根据实际网站结构修改选择器links = soup.select(div.chapter-list a)chapters = []for a in links:title = a.get_text(strip=True)href = a.get(href)if href.startswith(/):href = BASE_URL + hrefelif not href.startswith(http):href = BASE_URL + / + hrefchapters.append((title, href))return chaptersdef save_chapter(title, content, index):保存单章到文件filename = f{index:03d}_{title.replace('/', '_').replace('\\', '_')}.txtfilepath = os.path.join(SAVE_DIR, filename)with open(filepath, w, encoding=utf-8) as f:f.write(f章节: {title}\n\n)f.write(content)print(f已保存: {filename})def main():init_dir()print(正在获取目录...)chapters = get_chapter_list(CATALOG_URL)print(f共 {len(chapters)} 章)for i, (title, url) in enumerate(chapters, 1):print(f下载第 {i}/{len(chapters)} 章: {title})content = fetch_chapter_content(url, headers)# 如果内容为空,可能是 VIP 章节或解析失败if not content:print(f警告: 第 {i} 章内容为空,跳过)continuesave_chapter(title, content, i)# 延迟请求,避免被封time.sleep(DELAY)# 随机延迟,更拟人化# time.sleep(random.uniform(1, 3))print(下载完成!)# 调用前面定义的 fetch_chapter_content
def fetch_chapter_content(url, headers):try:resp = requests.get(url, headers=headers, timeout=10)resp.raise_for_status()resp.encoding = resp.apparent_encodingsoup = BeautifulSoup(resp.text, html.parser)content_div = soup.find(div, id=content)if not content_div:return paragraphs = content_div.find_all(p)return \n\n.join([p.get_text(strip=True) for p in paragraphs])except Exception as e:print(f错误: {e})return if __name__ == __main__:main()运行前检查清单:修改 CATALOG_URL 为你实际测试的小说目录页。
检查 get_chapter_list 中的 CSS 选择器 div.chapter-list a 是否匹配目标网站。
检查 fetch_chapter_content 中的 id=content 是否匹配。
DELAY 建议设为 2-5 秒,太快容易封 IP。常见报错与解决策略
新手写爬虫,报错是家常便饭。
这里列出三个最高频的问题,以及对策。
1. 乱码:中文变成 ??? 或 �原因:编码不匹配。服务器返回 GBK,你按 UTF-8 读。
对策:始终使用 response.encoding = response.apparent_encoding。
备选:如果 apparent_encoding 不准,手动指定 response.encoding = 'gbk'。
保存文件时:务必指定 encoding=utf-8,否则 Windows 记事本打开可能还是乱码。2. 403 Forbidden 或空页面原因:被反爬拦截。UA 不对,或者请求频率太高。
对策:更换 UA,尝试 Firefox 或 Safari 的 UA。
增加 DELAY,比如从 2 秒增加到 5 秒。
添加 Cookie。有些网站需要登录或首次访问后的 Cookie。
代码示例:
cookies = {session_id: xxx, user_id: 123}
requests.get(url, headers=headers, cookies=cookies)3. 解析不到内容:章节列表为空或正文为空原因:网站结构变了,或者内容是 JS 动态加载。
对策:打开浏览器 F12,查看 Network 标签,看真实请求的 URL 和 HTML 结构。
如果 HTML 里只有 script 没有正文,说明是 JS 渲染。
对策 A:找 API 接口。很多网站有 JSON API,直接请求 API 比解析 HTML 更稳。
对策 B:用 Selenium。但这会增加复杂度,慎用。
对策 C:检查 CSS 选择器。用 DevTools 的“检查元素”确认标签名和 class。新手避坑心法:不要相信文档里的示例 URL 永远有效。网站改版是常态。
不要一次性下载整本书。先测前 3 章,确认无误再全量跑。
日志要详细。打印每一章的标题和状态,方便定位问题。小结与进阶方向
到这里,一个基础的全本小说下载器就写完了。
回顾一下,我们解决了:环境配置与依赖安装。
请求头伪装与编码处理。
HTML 解析与数据提取。
文件保存与异常捕获。
频率控制与反爬基础应对。这个项目的价值,不在于代码本身,而在于流程思维。
从输入 URL 到输出 TXT,中间每一步都可能出错。
你能不能预判错误?能不能优雅处理?这就是工程师思维。
进阶方向:多线程下载:用 concurrent.futures.ThreadPoolExecutor 并发请求,速度提升 3-5 倍。注意控制并发数,别把 IP 搞挂了。
代理池:接入代理 IP 服务,轮换 IP,应对更严格的反爬。
数据清洗:去掉广告、推广语、空行。用正则表达式清洗文本。
格式转换:转成 EPUB 或 MOBI,方便 Kindle 阅读。可以用 ebook-convert 工具。
GUI 界面:用 tkinter 或 PyQt 做个简单界面,方便非技术用户使用。关于法律与道德:
爬虫技术本身中性。
但请遵守目标网站的服务条款。
不要爬取受版权保护的内容用于商业分发。
个人学习、备份已购书籍,通常风险较低,但仍需谨慎。
尊重原创,尊重开发者劳动。
互动环节:
你公司项目里是怎么处理这类批量数据抓取任务的?
是自建集群还是用现成工具?
遇到过哪些奇葩的反爬策略?
欢迎在评论区分享你的实战经验,一起避坑。