尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Python爬虫实战:从飞卢小说网抓取小说并生成离线阅读文件

Python爬虫实战:从飞卢小说网抓取小说并生成离线阅读文件 1. 项目缘起为什么选择飞卢小说网作为爬取目标最近在整理自己的电子书库想找几本特定题材的小说离线阅读结果发现很多平台要么需要付费订阅要么就是阅读体验被广告和弹窗搞得支离破碎。作为一个有十多年经验的开发者我第一个想到的自然是自己动手丰衣足食——写个爬虫。在众多小说网站里飞卢小说网成了一个很有意思的目标。它不像起点、纵横那样有极其复杂的反爬机制和动态渲染但也不像一些纯静态的小站那样毫无挑战。飞卢的页面结构相对清晰更新频率高小说题材也很有特色非常适合作为中级爬虫练手和实战的项目。这个项目的核心就是通过Python爬虫技术自动化地从飞卢小说网抓取指定小说的完整内容并整理成结构化的文本文件如TXT或EPUB方便我们在任何设备上离线阅读。听起来简单但里面涉及到的细节可不少如何模拟真实用户请求绕过基础反爬如何处理分页和章节链接怎样优雅地处理网络异常和编码问题以及最重要的如何在满足个人学习需求的同时严格遵守法律法规和网站的服务条款做到“取之有道”接下来我就把自己这次爬取飞卢小说的完整过程、踩过的坑以及总结的经验毫无保留地分享出来。2. 环境准备与核心工具选型工欲善其事必先利其器。在开始写代码之前选择合适的工具库能让我们事半功倍。对于飞卢小说网这样的现代网站直接使用requests库裸奔式地抓取html内容大概率会吃闭门羹。我们需要一套能更好模拟浏览器行为的工具链。2.1 为什么选择 Requests BeautifulSoup 动态Headers最初我尝试了最经典的requestsBeautifulSoup组合。Requests库简单易用是处理HTTP请求的瑞士军刀BeautifulSoup则是解析HTML/XML的利器用起来非常顺手。但很快我就遇到了第一个障碍直接使用requests.get()获取飞卢小说网的章节页面返回的往往是状态码200但内容为空或者是一段提示“请开启JavaScript”的页面。这其实是网站最基础的反爬手段之一检查请求头Headers。服务器会通过分析User-Agent、Referer、Cookie等字段来判断请求是来自真实的浏览器还是脚本。我们的脚本如果使用默认的Headers一眼就会被识别出来。因此我们的第一个关键步骤就是伪装Headers。我们需要让我们的请求看起来像一个从谷歌浏览器发起的普通访问。import requests from bs4 import BeautifulSoup # 定义一个完整的、模拟Chrome浏览器的请求头 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, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, Upgrade-Insecure-Requests: 1, Cache-Control: max-age0, }这里的User-Agent字符串至关重要它直接告诉服务器我们使用的是哪个浏览器。你可以通过打开浏览器的开发者工具F12在Network标签页中查看任意一个请求复制它的User-Agent值。Accept-Language告诉服务器我们优先接收中文内容这对获取正确编码的页面也有帮助。2.2 备用方案Selenium/Playwright的必要性评估尽管加上了Headers但在实际爬取过程中我仍然发现有些页面内容特别是涉及动态加载的推荐列表、评论区无法通过简单的requests获取。这时就需要考虑更高级的工具Selenium或Playwright。这两个工具可以自动化控制一个真实的浏览器如Chrome、Firefox执行点击、滚动、输入等操作并能获取到JavaScript完全执行后的页面源码。这对于爬取高度动态化的网站几乎是必备的。Selenium老牌自动化测试工具社区庞大资料多。但需要单独下载浏览器驱动如chromedriver配置稍麻烦运行速度相对较慢。Playwright后起之秀由微软开发。它内置了浏览器内核无需单独管理驱动API更现代执行速度也更快对动态内容的处理能力更强。对于飞卢小说网我的经验是核心的小说正文内容用requests伪装Headers基本可以搞定但如果你需要爬取需要登录后才能看的小说如VIP章节或者网站后期升级了更复杂的反爬如滑块验证那么Selenium或Playwright就是必须的。在本项目中为了聚焦于核心的爬取逻辑和降低环境复杂度我优先使用requests方案。但我会在代码结构中预留接口方便后续无缝切换到浏览器自动化方案。这是工程实践中一个很好的习惯将数据获取层Fetcher与数据解析层Parser解耦。2.3 项目依赖安装确定了技术栈我们就可以创建虚拟环境并安装依赖了。强烈建议使用虚拟环境如venv或conda来管理项目依赖避免污染全局环境。# 创建并激活虚拟环境以venv为例 python -m venv feilu_env source feilu_env/bin/activate # Linux/Mac # feilu_env\Scripts\activate # Windows # 安装核心依赖 pip install requests beautifulsoup4 lxml # 可选如果后续决定使用Playwright pip install playwright playwright install chromium # 安装Chromium浏览器这里解释一下几个包requests: 用于发送HTTP请求。beautifulsoup4: 用于解析HTML。lxml: 是BeautifulSoup的一个解析器速度比Python内置的html.parser快容错性更好建议安装。playwright: 如果需要用于控制浏览器。3. 网站结构分析与爬取策略制定在动手写代码之前花时间分析目标网站的结构是最高效的一步。盲目开始写解析规则后面大概率会因页面结构变动而返工。3.1 飞卢小说网页面结构拆解飞卢小说网的小说阅读页通常有比较固定的URL模式例如https://b.faloo.com/1234567_1.html。其中1234567是小说ID_1代表第一章。我们可以通过访问小说目录页来获取所有章节的链接。找到目录页通常在小说简介页或阅读页的侧边栏或顶部会有“目录”链接。其URL可能类似于https://b.faloo.com/f/1234567.html。解析目录页目录页列出了所有章节的标题和链接。我们需要用BeautifulSoup解析这个页面提取出每一个a标签的href属性和文本内容。这里要注意飞卢的目录可能是分页加载的我们需要检查是否有“下一页”按钮并循环抓取所有目录页。解析章节页进入具体的章节页面如https://b.faloo.com/1234567_1.html我们需要定位到小说正文所在的HTML元素。通过浏览器开发者工具的“检查”功能可以很容易地看到正文通常包裹在一个具有特定id或class的div标签内例如div idcontent”或div classcontent”。注意网站的前端结构可能会改版。今天有效的CSS选择器明天可能就失效了。因此我们的解析规则要尽量健壮。不要依赖于过于复杂或脆弱的选择路径优先使用id属性其次是具有唯一性的class名。同时写好异常处理和日志一旦解析失败能立刻知道。3.2 设计健壮的爬取流程一个健壮的爬虫不应该是一次性的脚本它需要考虑到网络波动、页面结构微调、反爬策略升级等各种情况。我设计了以下核心流程输入与初始化接收用户输入的小说ID或目录页URL。初始化请求会话requests.Session()复用TCP连接和Cookies提升效率。目录抓取模块发送请求获取目录页HTML。解析出所有章节链接和标题存储为列表[(‘第1章 标题’, ‘url1’), (‘第2章 标题’, ‘url2’), …]。处理目录分页逻辑。内容抓取模块遍历章节链接列表。对每个链接发送请求带上Headers并合理设置timeout和重试机制。解析响应HTML提取正文内容。清洗内容去除HTML标签、广告段落、无关的空格和换行符。存储模块将清洗后的正文与标题组合按顺序写入到一个TXT文件中。可以考虑更复杂的存储如每章一个文件或者生成EPUB格式。控制与容错模块在每章抓取之间插入随机延时如time.sleep(random.uniform(1, 3))避免请求过于频繁被服务器封禁IP。实现重试逻辑当请求失败超时、状态码非200时自动重试若干次。记录日志成功抓取了哪些章节哪些失败了失败原因是什么。这个流程的核心思想是“模块化”和“可容错”。每个模块职责单一方便单独测试和替换比如把解析器从BeautifulSoup换成parsel。容错机制保证了即使个别章节抓取失败整个任务也不会崩溃我们可以事后根据日志手动补抓。4. 核心代码实现与逐行解读理论说得再多不如一行代码。下面我将分模块展示核心代码并解释关键行背后的考量。4.1 请求会话与工具函数首先我们创建一个Session对象并定义一些工具函数。import requests import time import random import logging from bs4 import BeautifulSoup from urllib.parse import urljoin # 配置日志方便调试和追踪 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s: %(message)s) logger logging.getLogger(__name__) class FeiluNovelSpider: def __init__(self): self.session requests.Session() # 设置会话级别的Headers该会话下的所有请求都会携带这些头信息 self.session.headers.update({ 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, Accept-Language: zh-CN,zh;q0.9, }) self.base_url ‘https://b.faloo.com’ # 飞卢小说网基础域名 def _get_page(self, url, max_retries3): 获取页面HTML包含重试机制 for i in range(max_retries): try: # 随机延时模拟人类操作 time.sleep(random.uniform(1, 2)) resp self.session.get(url, timeout10) resp.raise_for_status() # 如果状态码不是200抛出HTTPError异常 # 检查编码飞卢通常使用utf-8或gbk这里先尝试utf-8 resp.encoding resp.apparent_encoding # 让requests自动判断编码 return resp.text except requests.exceptions.RequestException as e: logger.warning(f”第{i1}次尝试请求 {url} 失败: {e}”) if i max_retries - 1: wait_time 2 ** i # 指数退避策略等待时间递增 logger.info(f”等待{wait_time}秒后重试...”) time.sleep(wait_time) else: logger.error(f”请求 {url} 失败已达最大重试次数。”) return None关键点解读requests.Session(): 创建一个会话对象可以自动处理Cookies并在多次请求间保持连接比单次requests.get更高效。_get_page方法这是整个爬虫的基石。它包含了随机延时time.sleep和指数退避重试机制。指数退避2 ** i是一种经典的网络请求重试策略首次失败等2秒第二次等4秒以此类推避免在服务器临时故障时加剧其负载。resp.encoding resp.apparent_encoding: 自动检测响应内容的编码并设置可以解决大部分中文乱码问题。如果发现还有乱码可以尝试强制指定resp.encoding ‘gbk’或’utf-8’。4.2 目录解析器实现目录页是爬虫的“地图”我们必须准确无误地解析它。def parse_catalog(self, catalog_url): 解析小说目录页获取所有章节的标题和链接 html self._get_page(catalog_url) if not html: logger.error(“无法获取目录页请检查URL或网络。”) return [] soup BeautifulSoup(html, ‘lxml’) chapter_list [] # 关键步骤找到包含所有章节链接的容器。 # 需要通过浏览器开发者工具仔细分析目录页的HTML结构。 # 假设章节链接都在 class 为 ‘chapter-list’ 的 div 下的 a 标签里 catalog_div soup.find(‘div’, class_‘chapter-list’) if not catalog_div: # 如果找不到尝试其他可能的选择器这是解析器需要适应性的地方 catalog_div soup.find(‘div’, id‘chapterList’) if not catalog_div: logger.error(“未找到章节列表容器网站结构可能已更改。”) # 可以尝试打印一部分HTML来调试 # logger.debug(soup.prettify()[:1000]) return [] # 找到所有的章节链接 for a_tag in catalog_div.find_all(‘a’, hrefTrue): chapter_title a_tag.get_text().strip() chapter_url a_tag[‘href’] # 处理相对链接拼接成完整URL full_chapter_url urljoin(self.base_url, chapter_url) # 过滤掉可能不是正文章节的链接比如“最新章节”、“作品相关” if chapter_title and ‘chapter’ in full_chapter_url.lower(): chapter_list.append((chapter_title, full_chapter_url)) logger.debug(f”找到章节: {chapter_title} - {full_chapter_url}”) logger.info(f”共解析到 {len(chapter_list)} 个章节。”) return chapter_list关键点解读与避坑soup.find(‘div’, class_‘chapter-list’): 这是解析的核心依赖于网站的CSS类名。这是整个爬虫最脆弱的部分。一旦网站改版这个选择器就失效了。因此代码中提供了备选选择器id‘chapterList’并记录了详细的错误日志。在实际操作中你可能需要根据实际的页面结构调整这个选择器。urljoin: 非常重要章节链接href很可能是相对路径如/1234567_1.html。urljoin函数能将其与基础URLbase_url正确拼接成绝对路径。忽略这一步会导致后续请求失败。过滤逻辑if ‘chapter’ in full_chapter_url.lower()是一个简单的过滤防止把“推荐票”、“评论”等无关链接也当成章节抓取。你可能需要根据实际情况调整过滤条件。4.3 正文内容提取与清洗获取到章节链接后下一步就是抓取并清洗正文。def parse_chapter_content(self, chapter_url): 解析单个章节页面提取并清洗正文内容 html self._get_page(chapter_url) if not html: return “【内容获取失败】” soup BeautifulSoup(html, ‘lxml’) # 关键步骤定位正文所在的元素。同样需要分析页面结构。 # 假设正文在 id 为 ‘content’ 的 div 里 content_div soup.find(‘div’, id‘content’) if not content_div: # 尝试其他常见的选择器 content_div soup.find(‘div’, class_‘content’) if not content_div: logger.error(f”无法定位正文内容URL: {chapter_url}”) return “【正文定位失败】” # 提取文本并清洗 raw_text content_div.get_text(separator‘\n’, stripTrue) # 进一步清洗移除常见的广告语、网站声明等 lines raw_text.split(‘\n’) cleaned_lines [] for line in lines: line line.strip() # 过滤空行和包含特定广告关键词的行 if line and not any(ad in line for ad in [‘飞卢小说网’ ‘www.faloo.com’ ‘最新网址’ ‘PS:’]): cleaned_lines.append(line) cleaned_content ‘\n’.join(cleaned_lines) # 简单处理首行缩进两个全角空格 cleaned_content cleaned_content.replace(‘ ‘, ‘ ‘) # 将两个全角空格替换为四个半角空格或根据喜好调整 return cleaned_content关键点解读与清洗技巧get_text(separator‘\n’, stripTrue): BeautifulSoup的get_text方法可以提取标签内所有文本。separator‘\n’指定用换行符连接不同标签的文本这样能保留段落结构。stripTrue去除首尾空白。二次清洗直接提取的文本往往包含网站版权声明、广告插入等无关信息。我们需要根据这些信息的特征进行过滤。上面的代码通过检查行是否包含特定关键词如“飞卢小说网”来过滤。这是一个持续的过程你可能会在爬取过程中发现新的“垃圾行”需要不断更新这个过滤列表。格式美化cleaned_content.replace(‘ ‘, ‘ ‘)是一个简单的格式处理将中文段落首行常见的两个全角空格替换为四个半角空格让生成的TXT文件排版更美观。你可以根据个人阅读习惯调整。4.4 主控流程与文件存储最后我们把所有模块串联起来并实现文件存储。def crawl(self, catalog_url, output_file‘novel.txt’): 主爬取流程 logger.info(f”开始爬取小说目录页: {catalog_url}”) chapters self.parse_catalog(catalog_url) if not chapters: logger.error(“未获取到任何章节程序退出。”) return logger.info(f”开始抓取正文共 {len(chapters)} 章。”) with open(output_file, ‘w’, encoding‘utf-8’) as f: for idx, (title, url) in enumerate(chapters, 1): logger.info(f”正在抓取 [{idx}/{len(chapters)}] {title}”) content self.parse_chapter_content(url) # 写入文件 f.write(f”{title}\n\n”) # 写入章节标题 f.write(f”{content}\n\n”) # 写入章节内容 f.write(“-” * 50 “\n\n”) # 章节分隔线 logger.info(f”章节 [{title}] 抓取完成。”) logger.info(f”小说爬取完成已保存至: {output_file}”) # 使用示例 if __name__ ‘__main__’: # 替换成你要爬取的小说的实际目录页URL catalog_url ‘https://b.faloo.com/f/1234567.html’ spider FeiluNovelSpider() spider.crawl(catalog_url, output_file‘我的飞卢小说.txt’)关键点解读enumerate(chapters, 1): 在遍历章节列表时获取索引1表示索引从1开始方便日志显示进度如[1/100]。with open(...) as f: 使用with上下文管理器打开文件可以确保在任何情况下即使程序异常中断文件都能被正确关闭。encoding‘utf-8’: 指定以UTF-8编码写入文件这是最通用的中文编码能确保在任何设备上打开都不会乱码。进度与状态日志在关键步骤开始、抓取每个章节、完成都输出日志这对于长时间运行的爬虫至关重要。当程序因网络中断而停止时你可以通过日志知道抓取到了哪一章下次可以从断点续爬。5. 高级话题反爬应对、效率优化与伦理边界一个只能在自己电脑上跑一次的爬虫是玩具一个能稳定、高效、可持续运行的爬虫才是工具。这就涉及到更高级的话题。5.1 应对更复杂的反爬机制如果网站加强了反爬我们可能会遇到以下情况需要升级我们的爬虫IP封禁这是最常见的反爬手段。解决方案是使用代理IP池。你可以购买付费的代理服务或者使用一些免费的代理但稳定性差。在代码中可以通过为requests.Session或单次请求配置proxies参数来使用代理。proxies {‘http’: ‘http://your-proxy-ip:port’ ‘https’: ‘https://your-proxy-ip:port’} resp self.session.get(url, proxiesproxies)请求频率限制即使有代理过快的请求频率也会触发风控。除了在代码中设置随机延时更科学的做法是遵守robots.txt协议并设置一个合理的、尊重服务器的爬取间隔例如每请求一次等待3-5秒。JavaScript验证与动态加载如前所述当requests无法获取到有效内容时就需要动用Selenium或Playwright。你需要编写控制浏览器点击、滚动、等待元素加载的代码。这会使爬虫速度大幅下降但能解决最棘手的问题。验证码遇到验证码就比较麻烦了。对于简单的图形验证码可以使用OCR库如ddddocr、tesseract尝试识别。对于复杂的点选、滑块验证码通常需要接入打码平台人工或AI识别成本会急剧上升。个人爬虫项目如果遇到验证码最务实的建议是考虑是否值得继续或者寻找其他数据源。5.2 效率优化从单线程到异步上述代码是单线程的抓取100章小说可能需要几十分钟。对于大量数据的抓取效率是关键。我们可以采用异步IOasyncioaiohttp来并发发送请求理论上可以将速度提升数十倍。异步爬虫的编写比同步复杂核心思想是创建一个任务列表每个章节一个抓取任务然后用一个异步的HTTP客户端aiohttp.ClientSession同时发起多个请求并等待所有结果返回。这需要对Python的异步编程有基本了解。对于新手我建议先确保同步版本稳定运行再考虑异步优化。5.3 不可逾越的伦理与法律边界这是所有技术从业者必须严肃对待的一课。爬虫技术是一把双刃剑。遵守robots.txt这是网站所有者表明爬虫访问意愿的文件。访问https://b.faloo.com/robots.txt可以查看飞卢小说网对爬虫有哪些限制。尊重这些规则是基本的网络礼仪。限制爬取速度不要对目标服务器进行DoS攻击式的疯狂请求。你的爬虫行为不应该影响网站的正常服务。明确数据用途本项目仅限个人学习、研究使用。绝对禁止将爬取到的小说内容用于任何商业用途包括但不限于重新发布到其他网站或App。制作成盗版电子书进行销售。用于AI模型训练除非获得明确授权。尊重版权小说是作者和平台的智力财产。爬取行为本身可能游走在法律边缘但将内容用于商业分发或大规模传播则明确构成侵权。规避技术保护措施如果网站采用了明确的技术手段如加密、混淆来防止爬取强行破解可能违反相关法律法规。我的个人原则是爬虫技术用于学习原理、自动化处理个人可公开访问的数据是合理的。但一旦涉及版权内容、商业数据或个人隐私就必须慎之又慎最好寻求合法授权。技术让我们有能力做很多事但智慧和操守决定我们应该做哪些事。6. 项目总结与个人实操心得回顾整个项目从环境搭建、页面分析、代码编写到反爬应对是一个完整的Web爬虫开发流程。飞卢小说网作为一个中等难度的目标很好地锻炼了HTML解析、请求模拟、异常处理和流程设计的能力。我踩过最大的坑不是技术而是“想当然”。最初我以为目录页的解析规则半小时就能搞定结果因为网站前端用了多层嵌套的div和动态加载花了整整一个下午才找到稳定的选择器。教训就是一定要用浏览器的开发者工具把HTML结构看清楚、看完整不要只看一眼就写代码。对于重要的元素多用几种选择器find,find_all, CSS选择器尝试并做好找不到元素时的异常处理。另一个心得是关于“健壮性”。最初的脚本没有重试机制网络一波动就全盘崩溃。加上指数退避重试和详细日志后脚本的稳定性大大提升甚至可以晚上挂着跑第二天早上直接看结果。日志是你的眼睛一定要把关键步骤开始、成功、失败、失败原因都记录下来。最后关于这个脚本的用途我再次强调它是我为了在通勤地铁上网络不好看一本感兴趣的小说而写的生成的文件只存在于我自己的硬盘里。技术的学习过程很有趣但请务必将它用在正途。如果你对爬虫技术本身感兴趣我建议可以从一些公开的、允许爬取的数据源开始练习比如各大高校的公开数据集、政府开放数据平台等那里有海量的、合法的数据等着你去探索和分析。
返回列表