别担心,今天我就带大家用Python亲手写一个简单实用的小说爬虫,实现从目录获取到章节内容抓取,再到最终保存为TXT文件的全流程。


🛠️ 准备工作:环境与库

在开始编码前,请确保你的Python环境已经安装了以下两个核心库。如果还没有,请在终端或命令行中运行以下命令进行安装:

pip install requests lxml
  • requests: 用于发送网络请求,获取网页的HTML源码。
  • lxml: 一个高效的HTML/XML解析库,我们将用它来提取网页中的关键信息。

🏗️ 项目结构:让代码更清晰

为了让代码结构清晰、易于维护,我们将采用面向对象的方式,把配置和逻辑分离开来。整个爬虫主要由两个类构成:

  1. NovelCrawlerConfig: 配置类,集中管理所有可变参数,如网址、请求头、保存路径等。
  2. NovelCrawler: 爬虫主类,包含获取目录、抓取内容、保存文件等核心逻辑。

💻 代码实战:分步解析

第一步:定义配置类 NovelCrawlerConfig

首先,我们创建一个配置类,把所有可能会用到的参数都放在这里。这样做的好处是,如果你想爬取另一本小说,只需要修改这里的几个变量即可,无需改动核心逻辑。

class NovelCrawlerConfig:
    """小说爬虫配置类"""
    # 基础URL和目录页URL
    BASE_URL = "https://m.shuke8.cc"
    CATALOGUE_URL = "https://m.shuke8.cc/cygksjhsj/"
    
    # 请求头,伪装成浏览器访问,防止被反爬
    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',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    }
    
    # 文件保存配置
    OUTPUT_DIR = "Txt"
    NOVEL_TITLE = "从野怪开始升级"
    FILENAME = "从野怪开始升级.txt"
    
    # 爬虫控制配置
    REQUEST_TIMEOUT = 10  # 请求超时时间(秒)
    REQUEST_DELAY = 1     # 每次请求间隔时间(秒),避免过于频繁
第二步:初始化爬虫类 NovelCrawler

接下来是爬虫的主类。在 __init__ 方法中,我们初始化配置、创建一个列表来存储章节链接,并检查输出目录是否存在。

import os
import time
from lxml import html
import requests

class NovelCrawler:
    """小说爬虫主类"""
    def __init__(self, start_chapter_index=0):
        self.config = NovelCrawlerConfig()
        self.href_list = [] # 存储所有章节链接
        self.start_chapter_index = start_chapter_index # 支持断点续爬
        
        # 如果输出目录不存在,则创建
        if not os.path.exists(self.config.OUTPUT_DIR):
            os.mkdir(self.config.OUTPUT_DIR)
第三步:实现核心网络请求方法 fetch_page

这是一个基础但至关重要的方法,负责向目标URL发送请求并返回解析后的HTML对象。我们在这里加入了异常处理,确保网络波动不会导致程序直接崩溃。

    def fetch_page(self, url):
        """获取网页内容"""
        try:
            # 禁用SSL警告,并发送GET请求
            response = requests.get(url, headers=self.config.HEADERS, timeout=self.config.REQUEST_TIMEOUT, verify=False)
            response.encoding = 'utf-8' # 明确指定编码
            
            if response.status_code == 200:
                return html.fromstring(response.text) # 使用lxml解析HTML
            else:
                print(f"请求失败,状态码: {response.status_code}")
                return None
        except Exception as e:
            print(f"请求出错: {e}")
            return None
第四步:获取章节目录 get_all_chapters

这个方法负责遍历小说的所有目录页,提取出每一章的标题和链接。通过观察目标网站,我们发现目录是分页的,所以需要一个循环来处理。

    def get_chapter_list_from_page(self, url):
        """从单个目录页获取章节列表"""
        document = self.fetch_page(url)
        if not document: return []
        
        chapter_list = []
        # 使用XPath定位章节链接,这个路径需要根据实际网页结构调整
        links = document.xpath('//*[@id="Body"]/div[4]/ol/li/a')
        for link in links:
            href = link.xpath('@href')[0]
            text = link.xpath('text()')[0]
            chapter_list.append({"href": href, "text": text})
            print(f"发现章节: {text}")
        return chapter_list

    def get_all_chapters(self):
        """获取小说所有章节列表"""
        print("开始获取章节列表...")
        # 遍历所有目录页
        for page_num in range(1, 34): # 假设最多33页目录
            if page_num == 1:
                url = f"{self.config.CATALOGUE_URL}1.html"
            else:
                url = f"{self.config.CATALOGUE_URL}1_{page_num}.html"
            
            print(f"正在获取第 {page_num} 页目录...")
            chapters = self.get_chapter_list_from_page(url)
            self.href_list.extend(chapters)
            
            time.sleep(self.config.REQUEST_DELAY) # 礼貌爬取,稍作停顿
            
        print(f"总共获取到 {len(self.href_list)} 个章节")
第五步:抓取章节内容并保存 crawl_chapters

这是爬虫的最终目的。我们遍历上一步获取到的章节链接列表,逐个访问并提取正文内容,然后调用 save_chapter 方法写入文件。

    def save_chapter(self, title, content):
        """保存章节内容到文件"""
        filepath = os.path.join(self.config.OUTPUT_DIR, self.config.FILENAME)
        with open(filepath, 'a', encoding='utf-8') as f:
            f.write(f"\n{'='*50}\n{title}\n{'='*50}\n\n")
            for paragraph in content:
                if paragraph.strip():
                    f.write(f" {paragraph}\n\n")

    def get_chapter_content(self, url, title):
        """获取单个章节的详细内容"""
        print(f"正在获取: {title}")
        document = self.fetch_page(url)
        if not document: return False
        
        try:
            # 使用XPath提取正文段落
            paragraphs = document.xpath('//*[@id="bodybox"]/p/text()')
            if paragraphs:
                content = [p.strip() for p in paragraphs if p.strip()]
                self.save_chapter(title, content)
                print(f"✓ {title} 获取成功!")
                return True
            else:
                print(f"✗ {title} 未找到内容")
                return False
        except Exception as e:
            print(f"✗ {title} 解析失败: {e}")
            return False

    def crawl_chapters(self):
        """爬取所有章节内容"""
        if not self.href_list: return
        
        print("\n开始爬取章节内容...")
        success_count = 0
        for index in range(self.start_chapter_index, len(self.href_list)):
            chapter = self.href_list[index]
            chapter_url = f"{self.config.BASE_URL}{chapter['href']}"
            
            if self.get_chapter_content(chapter_url, chapter['title']):
                success_count += 1
            
            print(f"进度: {index + 1}/{len(self.href_list)} | 成功: {success_count}")
            time.sleep(self.config.REQUEST_DELAY) # 再次停顿,做个“好公民”
            
        print(f"爬取完成!总计成功: {success_count} 章")
第六步:主程序入口 main

最后,我们将所有步骤串联起来,形成一个完整的执行流程。

    def run(self):
        """运行爬虫主流程"""
        print(f"小说爬虫启动 - 《{self.config.NOVEL_TITLE}》")
        self.get_all_chapters()
        if self.href_list:
            self.crawl_chapters()
        else:
            print("未获取到章节列表,程序退出")

def main():
    # 可以设置从第几章开始爬取,0代表从头开始
    crawler = NovelCrawler(start_chapter_index=0)
    crawler.run()

if __name__ == '__main__':
    main()

🚀 运行与测试

将以上所有代码整合到一个 .py 文件中(例如 novel_crawler.py)。在终端运行:

python novel_crawler.py

你会看到程序开始打印日志,显示正在获取目录、正在下载章节。稍等片刻,你就会在项目目录下发现一个名为 Txt 的文件夹,里面静静地躺着你的 从野怪开始升级.txt 了!

💡 总结与注意事项

通过这个实例,我们完成了一个功能完整的小说爬虫。它涵盖了网络请求、HTML解析、数据提取、文件IO和流程控制等爬虫开发的核心知识点。

最后,有几点非常重要的提醒:

  1. 遵守robots.txt协议:在爬取任何网站前,请先查看其 robots.txt 文件,了解哪些内容允许爬取。
User-agent: * 
Disallow: /member
Disallow: /ajax

  1. 控制请求频率:代码中使用了 time.sleep() 来增加请求间隔,这是非常重要的“礼貌”行为,可以避免给目标服务器造成过大压力,也能有效降低IP被封禁的风险。
  2. 仅供学习交流:爬取的内容请仅用于个人学习和研究,切勿用于任何商业用途或大规模传播,尊重原作者和网站的版权。
  3. XPath路径非通用:代码中的XPath路径(如 '//*[@id="bodybox"]/p/text()')是针对示例网站特定的,爬取其他网站时需要你用浏览器的开发者工具(F12)重新分析并修改。

希望这篇教程能帮助你入门Python爬虫!如果有任何问题,欢迎在评论区留言讨论。

源码

更多推荐