恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Python爬虫实战:从零构建小说本地TXT库
首页
资讯中心
/
Python爬虫实战:从零构建小说本地TXT库
Python爬虫实战:从零构建小说本地TXT库
发布时间:2026/8/17 8:56:21
1. 项目概述从零到一构建你的小说本地图书馆每次在网上追更小说最怕的就是网站突然打不开或者作者更新后之前的章节因为各种原因被下架。作为一个多年的Python开发者和小说爱好者我早就养成了一个习惯遇到喜欢的小说第一时间用爬虫把它“请”到我的本地硬盘里。这不仅仅是备份更是一种数字资产的掌控感。今天要聊的就是如何用Python爬虫把网络上的小说章节规规矩矩地爬取下来并保存成最通用、最易读的TXT文件。这个项目听起来简单但麻雀虽小五脏俱全。它涵盖了Python爬虫从入门到实践的核心链路如何发送请求获取网页、如何从复杂的HTML中精准提取文本内容、如何处理翻页逻辑、如何应对网站常见的反爬策略以及最后如何将数据持久化保存。无论你是刚学完Python基础语法想找个实战项目练手的新手还是已经有一定经验想完善自己数据采集流程的开发者这个项目都能给你带来实实在在的收获。我们将使用最主流的requests库和BeautifulSoup库全程代码清晰步骤详细确保你能跟着做做出成果。2. 核心思路与工具选型解析2.1 为什么选择Requests BeautifulSoup组合在Python爬虫生态中库的选择非常多。对于爬取小说这类以静态HTML页面为主、结构相对规整的任务requestsBeautifulSoup的组合是公认的“黄金搭档”。requests库负责网络通信它的API设计极其人性化几行代码就能完成GET、POST请求并且能方便地设置请求头、处理Cookie等。相比Python内置的urllibrequests的代码更简洁功能更强大。而BeautifulSoup是一个HTML/XML解析库它能够将复杂的HTML文档转换成一个复杂的树形结构然后让你用类似找东西的方式比如通过标签名、CSS选择器来提取其中的数据。这个组合的优势在于轻量、直观、学习曲线平缓非常适合处理我们目标中的小说网站。为什么不直接用更高级的框架如ScrapyScrapy确实强大它是一个为大规模爬取设计的异步框架自带队列、去重、管道等机制。但对于我们这种目标单一、逻辑线性的小说爬虫项目来说Scrapy显得有些“杀鸡用牛刀”其框架本身的学习成本可能会分散我们对核心爬取逻辑的注意力。因此从快速实现和易于理解的角度出发requestsBeautifulSoup是最佳选择。2.2 项目整体流程设计在动手写代码之前我们需要在脑子里把整个流程过一遍。一个健壮的小说爬虫不应该只是一个简单的脚本而应该是一个考虑周全的微型系统。其核心流程可以分解为以下几个步骤目标分析确定要爬取的小说目录页URL分析其页面结构特别是章节链接的分布规律和翻页逻辑。请求获取使用requests模拟浏览器发送HTTP请求到目标URL获取网页的HTML源代码。这里的关键是设置合理的请求头User-Agent让自己看起来更像一个真实的浏览器。内容解析使用BeautifulSoup加载获取到的HTML并利用其选择器如find,find_all,select定位到小说标题、章节链接、章节正文等关键元素。数据提取从解析后的元素中提取出纯文本信息如章节标题和正文内容并进行必要的清洗去除广告、乱码、多余的空格和换行符。翻页与遍历判断是否存在下一页并构建下一页的URL循环执行步骤2-4直到爬取完所有章节。文件保存将爬取到的章节内容按照一定的格式例如“第X章 章节标题”后跟正文追加写入到一个TXT文件中。需要考虑文件的创建、编码通常使用UTF-8以及写入时的异常处理。反爬应对与健壮性在关键步骤添加异常处理如网络超时、页面结构变化、设置请求间隔时间time.sleep以避免请求过快被封以及可能需要的代理IP池准备。这个流程构成了我们代码的主干。每一个步骤都有需要注意的细节和可能遇到的“坑”我们会在接下来的实操中一一展开。3. 实操准备与环境搭建3.1 Python环境与库安装首先确保你的电脑上安装了Python。推荐使用Python 3.6及以上版本。你可以通过在命令行输入python --version或python3 --version来检查。接下来安装我们所需的两个核心库。打开你的命令行终端CMD、PowerShell或Terminal使用pip命令进行安装pip install requests beautifulsoup4如果下载速度慢可以考虑使用国内的镜像源例如清华源pip install requests beautifulsoup4 -i https://pypi.tuna.tsinghua.edu.cn/simplebeautifulsoup4是库的包名但在代码中我们导入的是from bs4 import BeautifulSoup。安装成功后就可以开始我们的编码之旅了。3.2 选择目标网站与初步分析这是整个项目最关键的一步目标选得好事半功倍选得不好举步维艰。重要提示在爬取任何网站前请务必遵守该网站的robots.txt协议通常在网站根目录如https://www.example.com/robots.txt并尊重版权。本教程仅以技术学习为目的请勿用于商业用途或侵犯他人权益。最好选择那些明确声明允许爬取或没有明确禁止爬虫的网站进行练习。对于学习而言我们可以找一些结构清晰的网站。这里需要你亲自打开一个小说网站例如某个小说章节页使用浏览器的“开发者工具”按F12键进行分析。分析要点查看章节链接在目录页找到章节列表所在的HTML元素。右键点击一个章节链接选择“检查”。观察它的HTML结构比如它可能在一个div classlist下的多个a标签里。定位正文内容打开一个章节内容页同样使用“检查”找到小说正文所在的HTML元素。通常正文会放在一个具有特定id或class的div标签内例如div idcontent或div classchapter-content。观察翻页逻辑目录页如果章节很多通常会分页。查看“下一页”按钮的链接它是完整的URL还是一个相对路径URL中是否有像page2这样的参数来标识页码举个例子假设我们分析一个虚构的网站其章节链接结构可能是div idchapter-list a href/book/123/1.html第一章 初遇/a a href/book/123/2.html第二章 修炼/a ... /div正文结构可能是div classcontent idhtmlContent p这里是小说正文内容.../p /div而翻页可能是a href?page2下一页/a。记下这些选择器如#chapter-list a,#htmlContent它们将是我们编写解析代码的依据。网站结构千变万化这个分析过程必须由你针对具体目标完成。4. 核心代码实现与分步详解下面我们将按照流程一步步构建爬虫。我会以一个假设的、结构清晰的目标网站为例给出代码并详细解释。请注意你需要将代码中的URL和选择器替换成你实际分析的结果。4.1 步骤一获取目录页与解析章节链接首先我们从一个目录页开始获取所有章节的链接和标题。import requests from bs4 import BeautifulSoup import time def get_chapter_links(index_url): 从小说目录页获取所有章节的链接和标题 :param index_url: 小说目录页的URL :return: 包含章节标题 章节链接的列表 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 } try: # 发送GET请求 response requests.get(index_url, headersheaders) response.raise_for_status() # 如果状态码不是200抛出HTTPError异常 response.encoding response.apparent_encoding # 自动识别编码 except requests.RequestException as e: print(f请求目录页失败: {e}) return [] soup BeautifulSoup(response.text, html.parser) # 假设章节链接都在 id 为 “chapter-list” 的div下的所有a标签里 # 你需要根据实际网站结构调整这个选择器 chapter_elements soup.select(#chapter-list a) chapter_list [] for elem in chapter_elements: title elem.text.strip() # 获取链接文本作为章节标题 link elem.get(href) # 获取href属性作为链接 # 处理相对链接如果链接不是以http开头则拼接上基础URL if not link.startswith(http): # 这里需要根据实际情况拼接基础URL一个简单的方法是使用urllib.parse.urljoin from urllib.parse import urljoin link urljoin(index_url, link) chapter_list.append((title, link)) print(f共找到 {len(chapter_list)} 个章节。) return chapter_list # 使用示例 (URL需要替换) if __name__ __main__: book_index_url https://www.example-novel-site.com/book/123/ # 替换成真实目录页URL chapters get_chapter_links(book_index_url) for title, link in chapters[:5]: # 打印前5章看看 print(title, link)代码解析与注意事项请求头Headers设置User-Agent是绕过基础反爬的第一步让服务器认为请求来自浏览器而非脚本。异常处理使用try...except包裹网络请求response.raise_for_status()会在状态码为4xx或5xx时抛出异常避免程序因网络问题崩溃。编码处理response.encoding response.apparent_encoding让BeautifulSoup用更准确的方式解码网页内容减少乱码。但有些网站可能需要强制指定utf-8。选择器Selectsoup.select(‘#chapter-list a’)使用了CSS选择器语法意思是“选择id为chapter-list的元素内部的所有a标签”。这是最常用的定位方式。如果网站结构复杂可能需要更复杂的选择器如‘div.list-main ul li a’。链接拼接网站上的链接常常是相对路径如/book/123/1.html。urljoin函数能智能地将其与基础URL拼接成绝对URL这是处理链接的稳健做法。4.2 步骤二抓取单章内容并清洗有了章节链接列表我们就可以遍历它逐个抓取正文。def get_chapter_content(chapter_url): 获取单个章节的正文内容 :param chapter_url: 章节内容页的URL :return: 清洗后的章节正文文本 headers {‘User-Agent‘: ’Mozilla/5.0...‘} # 同上最好复用或定义为全局变量 try: response requests.get(chapter_url, headersheaders) response.raise_for_status() response.encoding response.apparent_encoding except requests.RequestException as e: print(f“请求章节页失败: {chapter_url}, 错误: {e}“) return “【内容获取失败】“ soup BeautifulSoup(response.text, ’html.parser‘) # 假设正文在 id 为 “htmlContent” 的div里 # 你需要根据实际网站结构调整这个选择器 content_elem soup.find(’div‘, id’htmlContent‘) if not content_elem: # 如果第一种选择器找不到尝试其他常见选择器 content_elem soup.find(’div‘, class_’content‘) if not content_elem: print(f“未找到正文内容元素URL: {chapter_url}“) return “【正文元素未找到】“ # 获取文本并清洗 content_text content_elem.get_text() # 清洗步骤 # 1. 替换多个换行符和空格为合理的格式 import re content_text re.sub(r‘\s‘, ’\n ‘, content_text) # 将连续空白符包括换行替换为一个换行加四个空格作为段首 # 2. 移除常见的网页广告文字根据实际情况调整 ad_keywords [‘笔趣阁‘, ’记住本站地址‘, ’最新最快无防盗免费阅读‘] for keyword in ad_keywords: content_text content_text.replace(keyword, ’’) # 3. 去除首尾空白 content_text content_text.strip() return content_text # 使用示例 if __name__ ’__main__‘: # 假设这是其中一个章节的链接 test_url “https://www.example-novel-site.com/book/123/1.html“ content get_chapter_content(test_url) print(content[:500]) # 打印前500字符看看效果内容清洗的艺术直接get_text()得到的文本往往包含大量多余的换行、空格以及嵌入的广告文本。清洗是提升最终TXT文件可读性的关键。正则表达式清洗re.sub(r‘\s‘, ’\n ‘, content_text)是一个非常实用的技巧。\s匹配任何空白字符空格、制表符、换行等一次或多次。我们将其统一替换为“一个换行加四个空格”这样既能区分段落又让段落开头有缩进更符合阅读习惯。定向替换观察抓取下来的文本找出那些固定的、非小说内容的广告词如“上一章”、“下一章”、“点击这里”等将它们替换为空字符串。分段优化有些网站正文每个句子都放在单独的p标签里导致get_text()后句子间没有空格。你可能需要更精细的处理比如在获取所有p标签文本后用’ ‘.join()连接。4.3 步骤三整合流程并保存为TXT文件现在我们把前两步结合起来并加入文件保存和进度显示功能。import requests from bs4 import BeautifulSoup from urllib.parse import urljoin import time import re def crawl_novel(index_url, filename‘novel.txt‘): ”“” 爬取整本小说并保存到文件 :param index_url: 小说目录页URL :param filename: 保存的文件名 ”“” # 1. 获取所有章节链接 print(“正在获取章节列表...“) chapters get_chapter_links(index_url) # 复用之前定义的函数 if not chapters: print(“未获取到任何章节程序退出。“) return # 2. 创建或打开文件使用UTF-8编码写入 with open(filename, ’w‘, encoding’utf-8‘) as f: # 可以先写入一个书名标题如果需要可以从目录页解析 f.write(” 小说爬取存档 \n\n“) total_chapters len(chapters) for idx, (title, url) in enumerate(chapters, start1): print(f”正在爬取 [{idx}/{total_chapters}]{title}“) # 3. 获取单章内容 content get_chapter_content(url) # 复用之前定义的函数 # 4. 将章节标题和内容写入文件 f.write(f”\n\n第{idx}章 {title}\n“) f.write(”-“ * 40 ”\n“) # 分隔线 f.write(content ”\n“) # 5. 礼貌性延迟避免请求过快 time.sleep(1) # 每秒请求一次这是一个比较保守友好的间隔 # 可选每爬取10章打印一次进度 if idx % 10 0: print(f”进度{idx}/{total_chapters}“) print(f”\n爬取完成小说已保存至 {filename}“) # 主函数入口 if __name__ ’__main__‘: # 替换成你要爬的小说的真实目录页URL novel_index_url “https://www.example-novel-site.com/book/123/“ output_filename “我最爱的小说.txt“ crawl_novel(novel_index_url, output_filename)文件保存的关键点编码务必使用encoding‘utf-8’打开文件。这是最通用的编码方式能确保中文等字符正确保存和显示。如果使用Windows记事本打开UTF-8文件发现乱码可以尝试用更现代的编辑器如VS Code、Notepad或Sublime Text。写入模式使用‘w’模式会覆盖已存在的文件。如果你想在已有文件后追加内容例如分多次爬取可以使用‘a’追加模式但要注意管理好章节顺序和重复内容。格式美化在写入章节标题和正文时加入换行符\n和分隔线-可以让生成的TXT文件结构更清晰便于阅读。延迟Sleeptime.sleep(1)是至关重要的道德和技术考量。不加延迟地高速请求会对目标服务器造成压力极易触发反爬机制导致IP被封锁。1到3秒的间隔是常见的礼貌等待时间。对于大规模爬取这个时间可能需要更长或者使用更复杂的随机延迟。5. 进阶优化与反爬策略应对一个只能爬取“友好”网站的爬虫是脆弱的。实际项目中你会遇到各种反爬手段。下面介绍几种常见的应对策略。5.1 处理动态加载内容越来越多的网站使用JavaScript动态加载内容如Ajax。用requests抓取到的HTML可能不包含章节列表或正文因为它们是在浏览器执行JS后才生成的。解决方案寻找隐藏的API打开浏览器的开发者工具切换到“网络”Network选项卡过滤XHR或Fetch请求。刷新页面或点击翻页观察是否有新的数据请求通常是JSON格式。直接模拟这个请求往往能拿到结构化数据比解析HTML更简单。使用SeleniumSelenium可以控制一个真实的浏览器如Chrome来加载页面等待JS执行完毕后再获取完整的HTML。这种方法强大但重量级速度慢资源消耗大。仅作为最后手段。from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver webdriver.Chrome() # 需要下载对应浏览器的driver driver.get(url) # 等待某个特定元素加载出来 element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, “chapter-list”)) ) html driver.page_source driver.quit() # 然后用BeautifulSoup解析html5.2 应对IP封锁与验证码频繁请求会导致IP被暂时或永久封锁。应对策略降低请求频率增加time.sleep()的间隔时间或在间隔中加入随机性如time.sleep(random.uniform(1, 3))让请求行为更接近人类。使用代理IP池这是应对IP封锁最有效的方法。你可以购买付费代理服务或者寻找一些免费的代理IP但稳定性差。在requests中使用代理很简单proxies { ‘http‘: ’http://your-proxy-ip:port‘, ’https‘: ’https://your-proxy-ip:port‘, } response requests.get(url, headersheaders, proxiesproxies)你需要一个管理器来维护一组代理IP并在请求失败时自动切换。处理验证码如果遇到验证码小型项目可以手动处理程序暂停等待用户输入。自动化识别验证码OCR、打码平台成本较高一般只在商业爬虫中考虑。5.3 模拟登录与会话保持有些小说网站需要登录才能观看VIP章节。解决方案使用Session对象requests.Session()可以自动保持Cookie模拟登录后的会话状态。session requests.Session() login_data {‘username‘: ’your_name‘, ’password‘: ’your_pass‘} login_url ‘https://example.com/login‘ # 通常登录是POST请求 session.post(login_url, datalogin_data, headersheaders) # 之后用这个session去请求需要登录的页面 response session.get(vip_chapter_url, headersheaders)直接使用Cookie如果你已经通过浏览器登录可以直接从浏览器开发者工具的“网络”选项卡中复制某个请求的Cookie字符串将其添加到请求头中。headers[‘Cookie‘] ’你的长Cookie字符串‘5.4 代码健壮性增强一个工业级的爬虫必须有良好的错误处理和日志记录。import logging # 配置日志 logging.basicConfig(levellogging.INFO, format’%(asctime)s - %(levelname)s - %(message)s‘, filename’novel_crawler.log‘) def robust_crawl(): chapters get_chapter_links(index_url) for idx, (title, url) in enumerate(chapters): try: content get_chapter_content(url) if “【内容获取失败】” in content or “【正文元素未找到】” in content: logging.warning(f”章节爬取异常标题{title}, URL: {url}“) # 可以在这里加入重试逻辑 continue # 正常写入文件... time.sleep(random.uniform(1.5, 3.5)) # 随机延迟 except Exception as e: logging.error(f”处理章节时发生未知错误标题{title}, URL: {url}, 错误{e}“) # 记录错误后根据策略决定是继续还是终止 continue6. 常见问题排查与实战心得在实际操作中你一定会遇到各种各样的问题。下面是我总结的一些常见“坑”和解决思路。6.1 编码乱码问题这是新手最常遇到的问题。屏幕上打印出来或保存到文件里的中文变成了乱码。可能原因1服务器返回的编码声明与实际不符。解决优先使用response.encoding response.apparent_encoding。如果还不行可以尝试常见的编码如‘utf-8’,‘gbk’,‘gb2312’进行手动测试。response.content.decode(‘gbk’)。可能原因2文件保存时编码错误。解决确保open()函数中指定了encoding‘utf-8’。排查技巧打印response.encoding和response.apparent_encoding的值并查看response.text的前几百个字符看是否有可识别的中文。也可以直接查看网页源码的head部分meta charset”...”标签。6.2 抓取不到内容或内容为空程序运行了但章节列表或正文是空的。可能原因1选择器写错了。解决这是最常见的原因。再次用浏览器开发者工具仔细检查目标元素的id、class或路径。BeautifulSoup的find和select方法非常依赖精确的选择器。可以先用print(soup.prettify()[:2000])打印部分美化后的HTML确认结构。可能原因2网站有反爬请求被拒绝。解决检查请求头是否完备。除了User-Agent有时还需要Referer,Accept-Language,Cookie等。可以复制浏览器中正常请求的Headers全部加进去试试。检查返回的状态码和HTML内容是否包含“拒绝访问”、“验证”等字样。可能原因3内容是动态加载的。解决按照5.1节的方法检查是否有隐藏的API或考虑使用Selenium。6.3 被网站屏蔽或封禁IP程序跑了一会儿就再也获取不到数据了或者返回错误页面。立即措施首先大幅增加请求间隔比如time.sleep(10)。然后更换你的IP地址重启路由器或使用代理。检查你的请求频率是否过高。长期策略实现一个完整的爬虫礼仪遵守robots.txt。设置合理的延迟并加入随机性。使用代理IP池轮换IP。模拟真实用户行为如随机滚动页面、随机点击对于需要JS的网站。设置断点续爬将已爬取的URL记录到文件或数据库下次从中断处开始避免重复请求。6.4 文件保存格式混乱TXT文件打开后段落挤在一起或出现很多奇怪的符号。解决核心在于内容清洗。我们的re.sub(r‘\s‘, ’\n ‘, content_text)是基础。你需要根据目标网站的具体情况定制清洗规则。例如有些网站用br换行BeautifulSoup解析时可能需要特殊处理。可以写一个专门的clean_text()函数通过多次正则替换和字符串操作来净化文本。我个人最深刻的体会是爬虫项目七分在分析三分在编码。花足够多的时间去理解目标网站的结构、数据加载方式和潜在的反爬机制比盲目写代码调试要高效得多。另外一定要有“友好爬取”的意识控制速度不要给别人的服务器造成负担。这个简单的“小说爬虫”项目就像一把钥匙帮你打开了网络数据采集的大门。理解了它的原理你就能触类旁通去应对更复杂的数据抓取任务了。