Python Scrapy 实战指南:从 0 搭建爬虫项目,爬取数据效率提升 10 倍

目录

引言:为什么用 Requests 爬数据越用越累?Scrapy 才是终极解决方案

一、Scrapy 入门:搞懂它为什么比 Requests 更高效

1.1 Scrapy vs Requests:核心差异对比

1.2 Scrapy 的核心优势:3 个让你放弃 Requests 的理由

二、环境搭建:3 分钟搞定 Scrapy(Windows/Mac 通用)

2.1 方式 1:Anaconda 安装(推荐,适合新手)

步骤 1:安装 Anaconda(已安装的跳过)

步骤 2:用 conda 安装 Scrapy

步骤 3:验证安装

2.2 方式 2:pip 安装(适合已有 Python 环境的用户)

步骤 1:安装依赖(Windows 用户必看)

步骤 2:安装 Scrapy

步骤 3:验证安装

三、核心概念:搞懂 Scrapy 的 “五脏六腑”

3.1 第一步:创建第一个 Scrapy 项目

3.2 核心组件详解:每个文件是干嘛的?

四、第一个 Scrapy 爬虫:爬取豆瓣 Top250 电影(完整流程)

4.1 步骤 1:定义数据结构(items.py)

4.2 步骤 2:写爬虫核心代码(Spider)

步骤 2.1:创建 Spider 文件

步骤 2.2:编写 Spider 代码

4.3 步骤 3:配置项目设置(settings.py)

4.4 步骤 4:实现数据保存(pipelines.py)

4.5 步骤 5:运行爬虫,查看结果

步骤 5.1:执行爬虫命令

步骤 5.2:查看运行过程

步骤 5.3:查看 CSV 文件

五、Scrapy 核心技术:XPath 选择器与中间件

5.1 XPath 选择器:Scrapy 的 “数据提取利器”

5.1.1 常用 XPath 语法(必学 3 类)

5.1.2 实战:用 Chrome 开发者工具快速生成 XPath

5.1.3 Scrapy 中 XPath 的 2 个常用方法

5.2 中间件:Scrapy 的 “万能扩展工具”

5.2.1 为什么需要中间件?

5.2.2 实战 1:写一个 User-Agent 轮换中间件

步骤 1:写中间件代码(middlewares.py)

步骤 2:启用中间件(settings.py)

步骤 3:验证效果

5.2.3 实战 2:写一个 IP 代理中间件

步骤 1:准备 IP 代理列表

步骤 2:写代理中间件代码(middlewares.py)

步骤 3:启用代理中间件(settings.py)

步骤 4:验证效果

六、实战进阶:爬取需要登录的网站(模拟登录)

6.1 方式 1:Cookie 登录(简单,适合新手)

步骤 1:获取登录后的 Cookie

步骤 2:在 Scrapy 中添加 Cookie

方式 A:在 settings.py 中全局添加(所有请求都带 Cookie)

方式 B:在 Spider 中局部添加(特定请求带 Cookie)

6.2 方式 2:Form 表单登录(自动化,适合 Cookie 有效期短的网站)

步骤 1:分析登录表单(以 GitHub 为例)

步骤 2:编写登录代码(Spider)

步骤 3:运行爬虫

七、避坑指南:Scrapy 常见错误及解决方案

7.1 错误 1:爬虫运行后无数据,日志显示 “Crawled (403)”

7.2 错误 2:XPath 选择器提取不到数据(返回 None)

7.3 错误 3:Pipeline 不生效(数据没保存到 CSV)

7.4 错误 4:爬取速度过快,被网站封 IP

7.5 错误 5:模拟登录失败(返回登录页)

7.6 错误 6:CSV 文件中文乱码(Excel 打开显示 “???”)

八、总结与进阶学习建议

8.1 本文核心知识点总结

8.2 进阶学习方向

8.3 学习建议


 

class 卑微码农:
    def __init__(self):
        self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
        self.发量 = 100  # 初始发量
        self.咖啡因耐受度 = '极限'
        
    def 修Bug(self, bug):
        try:
            # 试图用玄学解决问题
            if bug.严重程度 == '离谱':
                print("这一定是环境问题!")
            else:
                print("让我看看是谁又没写注释...哦,是我自己。")
        except Exception as e:
            # 如果try块都救不了,那就...
            print("重启一下试试?")
            self.发量 -= 1  # 每解决一个bug,头发-1
 
 
# 实例化一个我
我 = 卑微码农()

引言:为什么用 Requests 爬数据越用越累?Scrapy 才是终极解决方案

如果你用 Python Requests 写过爬虫,大概率遇到过这些糟心事:“想爬取某电商 100 页商品数据,用循环一页页发请求,跑了半小时还没结束,中间断了还要重新来”;“爬取多页面数据时,要手动处理下一页链接、去重、保存数据,代码写得像‘补丁堆’,后期根本没法维护”;“遇到反爬严格的网站,要自己写 IP 代理池、User-Agent 轮换、Cookie 管理,光这些辅助功能就占了代码的 80%”。

其实,这些问题用Python Scrapy 框架就能轻松解决。

Scrapy 是 Python 生态中最强大的爬虫框架,它不是简单的 “请求 + 解析” 工具,而是一套 “开箱即用” 的爬虫系统 —— 内置高性能下载器、自动化去重、数据持久化、反爬防护等功能,能把你从 “重复造轮子” 中解放出来。用 Requests 爬 100 页数据要半小时,用 Scrapy 可能 10 分钟就搞定,而且代码更简洁、维护更方便。

这篇文章不会讲晦涩的框架原理,而是从 “零基础能上手、跟着做能跑通” 的角度,带你从环境搭建到实战项目,一步步掌握 Scrapy。每个知识点都配 “完整代码 + 注释 + 运行步骤 + 结果截图”,爬取中遇到的翻页、去重、反爬、数据保存等问题,都会教你怎么解决 —— 看完这篇,你再也不用为 “多页面爬取”“代码混乱” 头疼了。

一、Scrapy 入门:搞懂它为什么比 Requests 更高效

在动手写代码前,咱们先搞懂一个核心问题:Scrapy 和 Requests 的区别是什么?为什么说它更高效?

1.1 Scrapy vs Requests:核心差异对比

很多初学者以为 Scrapy 是 “升级版 Requests”,其实两者不是一个维度的工具 ——Requests 是 “请求库”,Scrapy 是 “爬虫框架”,就像 “自行车” 和 “汽车” 的区别。

对比维度RequestsScrapy
定位单一请求工具(类似 “自行车零件”)完整爬虫系统(类似 “整辆汽车”)
核心功能发送 HTTP 请求、获取响应请求调度、自动化解析、去重、持久化等
性能单线程,效率低(适合小数据量)多线程 / 异步,效率高(适合大数据量)
代码复杂度需手动写循环、去重、保存逻辑只需写核心业务逻辑(如解析数据)
反爬支持需手动实现代理、UA 轮换内置中间件,支持灵活扩展
适用场景单页面爬取、简单数据提取多页面爬取、大规模数据采集

举个例子:爬取豆瓣 Top250 电影(10 页,250 条数据)

  • 用 Requests:需要写循环生成 10 个页面 URL→逐个发请求→手动解析每个页面→去重(防止重复爬取)→手动保存到 CSV;
  • 用 Scrapy:只需定义 “起始 URL”“解析规则”“保存方式”,框架会自动处理请求调度、翻页、去重,你甚至不用写循环。

1.2 Scrapy 的核心优势:3 个让你放弃 Requests 的理由

  1. 高性能:多线程 + 异步下载Scrapy 默认用多线程处理请求,同时发起多个 HTTP 请求,比 Requests 的单线程循环快 3~10 倍。比如爬取 100 页数据,Requests 要等前一页请求完成才发下一页,Scrapy 能同时发 5~10 个请求(可配置),效率大幅提升。

  2. 自动化:少写 80% 重复代码框架自动处理 “请求去重”(避免重复爬取同一 URL)、“翻页调度”(找到下一页链接自动爬取)、“数据清洗”(用 Item 结构化数据),你不用再写 “判断 URL 是否爬过”“循环生成页码” 这类重复代码。

  3. 高扩展性:应对复杂反爬和需求Scrapy 的 “中间件” 机制支持灵活扩展 —— 想加 IP 代理?加一个下载中间件;想模拟登录?加一个请求中间件;想自定义数据保存方式?加一个 Pipeline。不用修改核心代码,就能实现复杂功能。

二、环境搭建:3 分钟搞定 Scrapy(Windows/Mac 通用)

Scrapy 的安装比 Requests 稍复杂(依赖较多),但按照步骤来,保证你能顺利搭好环境。推荐用Anaconda安装(自动处理依赖),如果没有 Anaconda,也可以用 pip 安装。

2.1 方式 1:Anaconda 安装(推荐,适合新手)

Anaconda 是 Python 的科学计算环境,能自动管理依赖包,避免 “安装失败”“版本冲突” 等问题。

步骤 1:安装 Anaconda(已安装的跳过)

  • Windows 用户

    1. 去 Anaconda 官网(https://www.anaconda.com/products/distribution#download-section)下载 Windows 版,默认安装即可(记得勾选 “Add Anaconda to my PATH environment variable”)。
    2. 安装完成后,打开 “Anaconda Prompt”(类似命令提示符),输入conda --version,显示 “conda 23.10.0”(版本号可能不同)即成功。
  • Mac 用户

    1. 下载 Mac 版 Anaconda,拖到应用程序中完成安装。
    2. 打开 “终端”,输入conda --version,显示版本号即成功。

步骤 2:用 conda 安装 Scrapy

在 Anaconda Prompt(Windows)或终端(Mac)中输入以下命令:

conda install -c conda-forge scrapy
  • 出现 “Proceed ([y]/n)?” 时,输入y回车,等待安装完成(约 1~3 分钟,视网络情况而定)。

步骤 3:验证安装

输入以下命令:

scrapy version

如果显示类似 “Scrapy 2.11.0” 的版本号,说明安装成功!

2.2 方式 2:pip 安装(适合已有 Python 环境的用户)

如果不想装 Anaconda,也可以用 pip 直接安装,但需要先处理依赖(尤其是 Windows 用户可能遇到的 “Twisted 安装失败” 问题)。

步骤 1:安装依赖(Windows 用户必看)

Windows 用户直接用pip install scrapy可能会报错(Twisted 依赖需要 C++ 编译环境),先手动安装 Twisted 的预编译包:

  1. 去 Unofficial Windows Binaries 网站(https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted),下载对应 Python 版本的 Twisted 包(比如 Python 3.10,下载 “Twisted‑22.10.0‑cp310‑cp310‑win_amd64.whl”)。
  2. 打开命令提示符,进入下载目录,输入pip install Twisted‑22.10.0‑cp310‑cp310‑win_amd64.whl(替换成你下载的文件名)。

步骤 2:安装 Scrapy

在命令提示符(Windows)或终端(Mac)中输入:

pip install scrapy

步骤 3:验证安装

输入scrapy version,显示版本号即成功。如果报错 “scrapy 不是内部或外部命令”,检查 Python 的 Scripts 目录是否在环境变量中(Windows 用户可重新安装并勾选 “Add Python to PATH”)。

三、核心概念:搞懂 Scrapy 的 “五脏六腑”

Scrapy 项目有固定的目录结构,每个文件负责不同的功能,就像 “五脏六腑” 各司其职。咱们先创建一个空项目,再逐个解释每个文件的作用。

3.1 第一步:创建第一个 Scrapy 项目

打开 Anaconda Prompt(Windows)或终端(Mac),执行以下命令:

# 创建项目:scrapy startproject + 项目名(这里叫douban_spider)
scrapy startproject douban_spider

执行完成后,会生成一个名为douban_spider的文件夹,目录结构如下:

douban_spider/          # 项目根目录
├── scrapy.cfg          # 项目配置文件(部署时用,新手暂时不用改)
└── douban_spider/     # 项目核心目录(所有代码在这里写)
    ├── __init__.py     # 空文件,标记为Python包
    ├── items.py        # 定义数据结构(类似“数据模板”)
    ├── middlewares.py  # 中间件(处理请求/响应,如代理、UA轮换)
    ├── pipelines.py    # 管道(处理爬取到的数据,如保存到CSV/数据库)
    ├── settings.py     # 项目设置(如下载延迟、User-Agent、中间件开关)
    └── spiders/        # 爬虫目录(存放Spider代码,核心!)
        └── __init__.py

3.2 核心组件详解:每个文件是干嘛的?

咱们用 “爬取豆瓣 Top250 电影” 的场景,解释每个组件的作用:

组件文件核心作用通俗理解
spiders/存放 Spider 代码,定义 “爬哪些 URL”“怎么解析数据”爬虫的 “大脑”,负责制定爬取规则
items.py定义数据结构(如电影名称、评分、年份)爬取数据的 “模板”,保证数据结构化
pipelines.py处理爬取到的数据(保存到 CSV/MySQL)数据的 “加工厂”,负责持久化
middlewares.py处理请求 / 响应(如添加代理、修改 User-Agent)爬虫的 “保镖”,负责反爬和请求优化
settings.py配置项目参数(下载延迟、中间件开关)爬虫的 “控制面板”,调节运行参数

举个例子:你想爬取 “电影名称、评分、上映年份”

  1. 先在items.py中定义这 3 个字段(告诉框架要爬哪些数据);
  2. spiders/中写 Spider 代码(告诉框架从豆瓣 Top250 的 URL 开始爬,用 XPath 提取这 3 个字段);
  3. pipelines.py中写代码(告诉框架把爬取到的数据保存到douban_top250.csv);
  4. settings.py中设置下载延迟(避免被豆瓣封 IP)。

四、第一个 Scrapy 爬虫:爬取豆瓣 Top250 电影(完整流程)

从这里开始,咱们一步步写代码,实现 “爬取豆瓣 Top250 电影,提取名称、评分、年份,保存到 CSV”。跟着做,保证你能跑通!

4.1 步骤 1:定义数据结构(items.py)

首先,在items.py中定义要爬取的数据字段 —— 豆瓣 Top250 需要 “排名、电影名称、评分、上映年份”,所以我们定义这 4 个字段。

打开douban_spider/douban_spider/items.py,修改代码如下:

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy

# 定义豆瓣电影的数据结构
class DoubanSpiderItem(scrapy.Item):
    # 排名
    rank = scrapy.Field()
    # 电影名称(中文)
    title = scrapy.Field()
    # 评分
    rating = scrapy.Field()
    # 上映年份
    year = scrapy.Field()

关键说明

  • scrapy.Item:所有数据结构的基类,类似 Python 的字典,但更规范;
  • scrapy.Field():定义字段,不用指定类型(支持字符串、数字等);
  • 作用:后续爬取数据时,用这个 “模板” 封装数据,避免数据格式混乱(比如有的地方叫 “movie_name”,有的地方叫 “name”)。

4.2 步骤 2:写爬虫核心代码(Spider)

Spider 是爬虫的 “大脑”,负责定义 “爬哪些 URL”“怎么解析数据”。我们需要创建一个 Spider 文件,放在spiders/目录下。

步骤 2.1:创建 Spider 文件

在 Anaconda Prompt(Windows)或终端(Mac)中,进入项目根目录douban_spider,执行以下命令:

# 创建Spider:scrapy genspider + Spider名 + 允许爬取的域名
scrapy genspider douban_top250 movie.douban.com
  • douban_top250:Spider 的名字(自定义,后续运行用);
  • movie.douban.com:允许爬取的域名(避免爬虫爬到其他网站,可修改)。

执行后,spiders/目录下会生成douban_top250.py文件。

步骤 2.2:编写 Spider 代码

打开douban_spider/douban_spider/spiders/douban_top250.py,修改代码如下(每一行都有注释,看不懂的地方看注释):

# -*- coding: utf-8 -*-
import scrapy
# 导入我们定义的数据结构
from douban_spider.items import DoubanSpiderItem

class DoubanTop250Spider(scrapy.Spider):
    # Spider的名字(必须唯一,运行时用)
    name = 'douban_top250'
    # 允许爬取的域名(防止爬虫爬到其他网站)
    allowed_domains = ['movie.douban.com']
    # 起始URL(爬虫从这个URL开始爬取)
    start_urls = ['https://movie.douban.com/top250']

    def parse(self, response):
        """
        解析起始URL的响应(豆瓣Top250第1页)
        response:Scrapy的响应对象,包含HTML内容、状态码等
        """
        # 1. 提取当前页面的所有电影项(每个电影项的HTML标签是<div class="item">)
        # 用XPath选择器查找所有.item标签(XPath用法后面会讲)
        movie_items = response.xpath('//div[@class="item"]')

        # 2. 遍历每个电影项,提取数据
        for item in movie_items:
            # 创建数据结构对象(用我们在items.py中定义的DoubanSpiderItem)
            movie_data = DoubanSpiderItem()

            # 2.1 提取排名(XPath://div[@class="item"]/div[@class="pic"]/em/text())
            # .extract_first():提取第一个匹配的结果(避免返回列表)
            movie_data['rank'] = item.xpath('./div[@class="pic"]/em/text()').extract_first()

            # 2.2 提取电影名称(中文名称,XPath:./div[@class="info"]/div[@class="hd"]/a/span[1]/text())
            movie_data['title'] = item.xpath('./div[@class="info"]/div[@class="hd"]/a/span[1]/text()').extract_first()

            # 2.3 提取评分(XPath:./div[@class="info"]/div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]/text())
            movie_data['rating'] = item.xpath('./div[@class="info"]/div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]/text()').extract_first()

            # 2.4 提取上映年份(从描述文本中提取,比如“(1994)”)
            # 先提取描述文本(包含导演、主演、年份)
            desc_text = item.xpath('./div[@class="info"]/div[@class="bd"]/p[1]/text()').extract_first()
            # 从描述文本中提取4位数字(年份),用strip()去除空格
            if desc_text:
                # 分割文本,找到包含年份的部分(如“1994”)
                year = [part.strip() for part in desc_text.split() if part.strip().isdigit() and len(part.strip()) == 4]
                movie_data['year'] = year[0] if year else '未知年份'
            else:
                movie_data['year'] = '未知年份'

            # 3. 把提取到的数据交给Pipeline处理(用yield关键字)
            yield movie_data

        # 4. 处理翻页:找到下一页的URL,继续爬取
        # 下一页链接的XPath://span[@class="next"]/a/@href
        next_page_url = response.xpath('//span[@class="next"]/a/@href').extract_first()
        if next_page_url:
            # 拼接完整的下一页URL(豆瓣的下一页是相对路径,比如“?start=25&filter=”)
            full_next_url = response.urljoin(next_page_url)
            # 用yield scrapy.Request()发起下一页的请求,回调parse方法(继续解析下一页)
            yield scrapy.Request(url=full_next_url, callback=self.parse)

关键代码解释

  1. start_urls:爬虫的起始 URL,Scrapy 会自动对这个 URL 发起请求,请求完成后调用parse方法;
  2. parse方法:核心解析函数,接收响应对象response,提取数据并处理翻页;
  3. XPath 选择器:用于从 HTML 中提取数据,比如//div[@class="item"]表示 “查找所有 class 为 item 的 div 标签”(后面会详细讲 XPath 用法);
  4. yield movie_data:将提取到的数据交给pipelines.py处理(类似 “传球”);
  5. 翻页处理:找到下一页的 URL,用scrapy.Request()发起新请求,回调parse方法(实现循环爬取所有页面)。

4.3 步骤 3:配置项目设置(settings.py)

settings.py是 Scrapy 的 “控制面板”,需要配置 3 个关键参数:User-Agent(模拟浏览器,避免被豆瓣封 IP)、下载延迟(控制爬取速度)、Pipeline 开关(启用数据保存功能)。

打开douban_spider/douban_spider/settings.py,修改以下配置(其他配置暂时默认):

# 1. 模拟浏览器的User-Agent(替换成你自己的浏览器UA,从Chrome开发者工具中复制)
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'

# 2. 遵守robots协议(默认True,改为False,否则豆瓣会禁止爬取)
ROBOTSTXT_OBEY = False

# 3. 下载延迟(单位:秒),控制爬取速度,避免被封IP(建议设置2~5秒)
DOWNLOAD_DELAY = 2

# 4. 启用Pipeline(取消注释,数字越小,优先级越高)
ITEM_PIPELINES = {
   'douban_spider.pipelines.DoubanSpiderPipeline': 300,
}

如何获取自己的 User-Agent?

  1. 打开 Chrome 浏览器,访问任意网页;
  2. F12打开开发者工具,切换到 “Network” 标签;
  3. 刷新页面,点击第一个请求,在 “Request Headers” 中找到 “User-Agent”,复制即可。

4.4 步骤 4:实现数据保存(pipelines.py)

pipelines.py负责处理爬取到的数据,这里我们把数据保存到 CSV 文件(方便用 Excel 打开分析)。

打开douban_spider/douban_spider/pipelines.py,修改代码如下:

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html

import csv

class DoubanSpiderPipeline:
    # 爬虫启动时执行(只执行一次)
    def open_spider(self, spider):
        # 打开CSV文件,准备写入(用utf-8-sig编码,避免Excel打开中文乱码)
        self.csv_file = open('douban_top250.csv', 'w', encoding='utf-8-sig', newline='')
        # 定义CSV表头(和items.py中的字段对应)
        self.fieldnames = ['rank', 'title', 'rating', 'year']
        # 创建CSV写入对象
        self.writer = csv.DictWriter(self.csv_file, fieldnames=self.fieldnames)
        # 写入表头
        self.writer.writeheader()

    # 每爬取到一个item,执行一次(核心方法)
    def process_item(self, item, spider):
        # 将item(Scrapy.Item对象)转换为字典,写入CSV
        self.writer.writerow(dict(item))
        # 返回item(如果有多个Pipeline,会传递给下一个Pipeline)
        return item

    # 爬虫关闭时执行(只执行一次)
    def close_spider(self, spider):
        # 关闭CSV文件
        self.csv_file.close()
        print("爬取完成!数据已保存到douban_top250.csv")

关键方法解释

  • open_spider:爬虫启动时打开 CSV 文件并写入表头(只执行一次);
  • process_item:每爬取到一条数据(一个item),就写入 CSV(执行多次,每条数据执行一次);
  • close_spider:爬虫关闭时关闭 CSV 文件(只执行一次)。

4.5 步骤 5:运行爬虫,查看结果

所有代码写完后,就可以运行爬虫了!

步骤 5.1:执行爬虫命令

在 Anaconda Prompt(Windows)或终端(Mac)中,进入项目根目录douban_spider,执行以下命令:

# 运行爬虫:scrapy crawl + Spider名
scrapy crawl douban_top250

步骤 5.2:查看运行过程

运行后,会看到类似以下的日志(说明爬虫正在正常爬取):

2024-06-15 10:00:00 [scrapy.core.engine] INFO: Spider opened
2024-06-15 10:00:02 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://movie.douban.com/top250> (referer: None)
2024-06-15 10:00:04 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://movie.douban.com/top250?start=25&filter=> (referer: https://movie.douban.com/top250)
...
2024-06-15 10:00:30 [scrapy.core.engine] INFO: Closing spider (finished)
爬取完成!数据已保存到douban_top250.csv

步骤 5.3:查看 CSV 文件

爬虫运行完成后,在项目根目录douban_spider中会生成douban_top250.csv文件。用 Excel 打开,会看到完整的豆瓣 Top250 数据:

ranktitleratingyear
1肖申克的救赎9.71994
2霸王别姬9.61993
3阿甘正传9.51994
............

五、Scrapy 核心技术:XPath 选择器与中间件

前面的实战中用到了 XPath 选择器(提取数据)和中间件(配置 User-Agent),这是 Scrapy 的核心技术,掌握它们才能应对复杂的爬取需求。

5.1 XPath 选择器:Scrapy 的 “数据提取利器”

XPath 是一种在 XML/HTML 文档中查找信息的语言,比 BeautifulSoup 的find()更灵活、更高效,是 Scrapy 推荐的解析方式。

5.1.1 常用 XPath 语法(必学 3 类)

语法类型示例作用
按标签名查找//div查找所有 div 标签(// 表示从根节点开始,不考虑层级)
按属性查找//div[@class="item"]查找 class 为 item 的 div 标签
按文本查找//a[text()="下一页"]查找文本为 “下一页” 的 a 标签
提取文本//div[@class="item"]/text()提取 class 为 item 的 div 标签的文本
提取属性值//a/@href提取所有 a 标签的 href 属性值
层级查找//div[@class="item"]/div/p查找 class 为 item 的 div 下的 div 下的 p 标签
索引查找//a/span[1]查找 a 标签下的第 1 个 span 标签(索引从 1 开始)

5.1.2 实战:用 Chrome 开发者工具快速生成 XPath

不用死记硬背 XPath 语法,用 Chrome 开发者工具能快速生成目标元素的 XPath:

  1. 打开目标网页(如豆瓣 Top250),按F12打开开发者工具;
  2. 切换到 “Elements” 标签,找到要提取的元素(如电影名称的 span 标签);
  3. 右键点击该元素→“Copy”→“Copy XPath”;
  4. 粘贴到 Scrapy 代码中,稍作修改即可(比如把绝对路径改为相对路径,用./开头)。

示例

  • 复制的 XPath(绝对路径):/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[1]/a/span[1]
  • 修改为相对路径(在parse方法中用):./div[@class="info"]/div[@class="hd"]/a/span[1](更简洁,页面结构变化时不易失效)。

5.1.3 Scrapy 中 XPath 的 2 个常用方法

在 Scrapy 的parse方法中,用response.xpath()item.xpath()调用 XPath,返回的是 “选择器对象”,需要用extract()extract_first()提取数据:

  • extract():返回所有匹配结果的列表(如提取多个电影项);
  • extract_first():返回第一个匹配结果(如提取单个电影的名称,避免返回列表)。

示例

# 提取所有电影名称(返回列表)
titles = response.xpath('//div[@class="item"]/div[@class="info"]/div[@class="hd"]/a/span[1]/text()').extract()
print(titles)  # 输出:['肖申克的救赎', '霸王别姬', ...]

# 提取第一个电影名称(返回字符串)
first_title = response.xpath('//div[@class="item"]/div[@class="info"]/div[@class="hd"]/a/span[1]/text()').extract_first()
print(first_title)  # 输出:肖申克的救赎

5.2 中间件:Scrapy 的 “万能扩展工具”

中间件(Middleware)是 Scrapy 的 “插件系统”,分为下载中间件(处理请求 / 响应)和爬虫中间件(处理 item 和请求),最常用的是下载中间件。

5.2.1 为什么需要中间件?

比如你遇到以下问题,就需要用中间件解决:

  • 爬取网站时,需要轮换不同的 User-Agent(避免被识别为爬虫);
  • 遇到 IP 封禁,需要添加 IP 代理池(用不同的 IP 发起请求);
  • 爬取需要登录的网站,需要在请求中添加 Cookie(模拟登录状态)。

这些需求不用修改 Spider 核心代码,只需写一个下载中间件,在settings.py中启用即可。

5.2.2 实战 1:写一个 User-Agent 轮换中间件

很多网站会通过 User-Agent 识别爬虫,固定一个 User-Agent 容易被封,用中间件实现 “每次请求用不同的 User-Agent”。

步骤 1:写中间件代码(middlewares.py)

打开douban_spider/douban_spider/middlewares.py,添加以下代码:

# 导入随机模块(用于随机选择User-Agent)
import random

# 定义User-Agent列表(从浏览器中复制多个,或从网上收集)
USER_AGENT_LIST = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
]

class RandomUserAgentMiddleware:
    """随机轮换User-Agent的下载中间件"""
    def process_request(self, request, spider):
        """
        每次发起请求前执行(核心方法)
        request:即将发起的请求对象
        spider:当前运行的Spider对象
        """
        # 从列表中随机选择一个User-Agent
        random_ua = random.choice(USER_AGENT_LIST)
        # 将随机的User-Agent添加到请求头中
        request.headers.setdefault('User-Agent', random_ua)
        # 打印日志(可选,验证是否生效)
        spider.logger.info(f"使用User-Agent:{random_ua}")

步骤 2:启用中间件(settings.py)

settings.py中找到DOWNLOADER_MIDDLEWARES配置,取消注释并添加我们的中间件:

DOWNLOADER_MIDDLEWARES = {
    # 启用自定义的User-Agent中间件,优先级100(数字越小,优先级越高)
    'douban_spider.middlewares.RandomUserAgentMiddleware': 100,
    # 禁用Scrapy默认的User-Agent中间件(避免冲突)
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
}

步骤 3:验证效果

运行爬虫,查看日志,会看到类似以下的输出(说明 User-Agent 在随机轮换):

2024-06-15 10:30:00 [douban_top250] INFO: 使用User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15
2024-06-15 10:30:02 [douban_top250] INFO: 使用User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36

5.2.3 实战 2:写一个 IP 代理中间件

如果爬取频率过高,豆瓣会封禁你的 IP(返回 403 错误),用 IP 代理中间件可以解决这个问题 —— 每次请求用不同的代理 IP。

步骤 1:准备 IP 代理列表

先找一些可用的 IP 代理(免费代理可以从 “西刺代理”“快代理” 等网站获取,注意验证可用性),格式为 “http://IP: 端口” 或 “https://IP: 端口”。

步骤 2:写代理中间件代码(middlewares.py)

middlewares.py中添加以下代码:

# 定义IP代理列表(替换成你找到的可用代理)
PROXY_LIST = [
    'http://123.45.67.89:8080',
    'http://98.76.54.32:8080',
    'https://11.22.33.44:8443'
]

class RandomProxyMiddleware:
    """随机轮换IP代理的下载中间件"""
    def process_request(self, request, spider):
        # 从列表中随机选择一个代理
        random_proxy = random.choice(PROXY_LIST)
        # 设置代理(格式:{'http': '代理地址', 'https': '代理地址'})
        request.meta['proxy'] = random_proxy
        # 打印日志(验证是否生效)
        spider.logger.info(f"使用代理IP:{random_proxy}")

    def process_exception(self, request, exception, spider):
        """请求发生异常时执行(如代理不可用,切换到下一个代理)"""
        # 打印异常日志
        spider.logger.error(f"代理{request.meta.get('proxy')}不可用,切换代理")
        # 从代理列表中移除不可用的代理
        bad_proxy = request.meta.get('proxy')
        if bad_proxy in PROXY_LIST:
            PROXY_LIST.remove(bad_proxy)
        # 重新发起请求(用新的代理)
        if PROXY_LIST:
            new_proxy = random.choice(PROXY_LIST)
            request.meta['proxy'] = new_proxy
            return request  # 返回请求,Scrapy会重新发起
        else:
            spider.logger.error("没有可用的代理了!")

步骤 3:启用代理中间件(settings.py)

DOWNLOADER_MIDDLEWARES中添加代理中间件:

DOWNLOADER_MIDDLEWARES = {
    'douban_spider.middlewares.RandomUserAgentMiddleware': 100,
    'douban_spider.middlewares.RandomProxyMiddleware': 200,  # 代理中间件,优先级200
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
    # 禁用Scrapy默认的代理中间件(可选)
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': None,
}

步骤 4:验证效果

运行爬虫,查看日志,会看到类似以下的输出(说明代理 IP 在正常使用):

2024-06-15 10:40:00 [douban_top250] INFO: 使用代理IP:http://123.45.67.89:8080
2024-06-15 10:40:02 [douban_top250] INFO: 使用代理IP:http://98.76.54.32:8080

六、实战进阶:爬取需要登录的网站(模拟登录)

很多网站的核心数据需要登录才能获取(比如知乎的个人回答、GitHub 的私有仓库),Scrapy 支持两种模拟登录方式:Cookie 登录Form 表单登录

6.1 方式 1:Cookie 登录(简单,适合新手)

如果网站登录后 Cookie 有效期较长(比如几天),可以直接复制登录后的 Cookie,在 Scrapy 中添加到请求头中,模拟登录状态。

步骤 1:获取登录后的 Cookie

  1. 用 Chrome 浏览器登录目标网站(如知乎);
  2. F12打开开发者工具,切换到 “Network” 标签;
  3. 刷新页面,点击第一个请求(如https://www.zhihu.com/);
  4. 在 “Request Headers” 中找到 “Cookie”,复制完整的 Cookie 字符串。

步骤 2:在 Scrapy 中添加 Cookie

有两种方式添加 Cookie:

方式 A:在 settings.py 中全局添加(所有请求都带 Cookie)

# 在settings.py中添加Cookie
DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
    'Cookie': '你的知乎Cookie'  # 替换成复制的Cookie
}

方式 B:在 Spider 中局部添加(特定请求带 Cookie)

parse方法中,发起请求时添加 Cookie:

def parse(self, response):
    # 定义请求头,包含Cookie
    headers = {
        'User-Agent': self.settings.get('USER_AGENT'),
        'Cookie': '你的知乎Cookie'
    }
    # 发起需要登录的请求
    yield scrapy.Request(
        url='https://www.zhihu.com/people/your-profile',  # 你的知乎个人主页
        headers=headers,
        callback=self.parse_profile  # 解析个人主页的回调函数
    )

def parse_profile(self, response):
    """解析个人主页数据"""
    # 提取个人昵称(示例XPath,需根据实际页面调整)
    nickname = response.xpath('//span[@class="Nickname"]/text()').extract_first()
    print(f"登录成功!当前用户:{nickname}")

6.2 方式 2:Form 表单登录(自动化,适合 Cookie 有效期短的网站)

如果网站 Cookie 有效期短(比如 1 小时),需要每次爬取前自动登录,用scrapy.FormRequest提交登录表单(用户名 + 密码)。

步骤 1:分析登录表单(以 GitHub 为例)

  1. 打开 GitHub 登录页(https://github.com/login);
  2. F12打开开发者工具,切换到 “Network” 标签;
  3. 输入错误的用户名和密码,点击登录,找到名为 “session” 的 POST 请求;
  4. 在 “Form Data” 中查看登录表单的参数(如commitauthenticity_tokenloginpassword)。

步骤 2:编写登录代码(Spider)

创建一个 GitHub 登录的 Spider,代码如下:

# -*- coding: utf-8 -*-
import scrapy

class GithubLoginSpider(scrapy.Spider):
    name = 'github_login'
    allowed_domains = ['github.com']
    start_urls = ['https://github.com/login']  # 登录页URL

    def parse(self, response):
        """解析登录页,提取authenticity_token(CSRF令牌,防止跨站请求伪造)"""
        # 提取authenticity_token(从登录表单的隐藏字段中获取)
        authenticity_token = response.xpath('//input[@name="authenticity_token"]/@value').extract_first()
        if authenticity_token:
            # 提交登录表单
            yield scrapy.FormRequest(
                url='https://github.com/session',  # 登录请求的URL(从Network中复制)
                formdata={
                    'commit': 'Sign in',  # 固定值,从Form Data中复制
                    'authenticity_token': authenticity_token,  # 动态提取的令牌
                    'login': '你的GitHub用户名',  # 替换成你的用户名
                    'password': '你的GitHub密码',  # 替换成你的密码
                    'webauthn-support': 'supported',  # 固定值,从Form Data中复制
                    'webauthn-iuvpaa-support': 'supported'  # 固定值,从Form Data中复制
                },
                callback=self.parse_after_login  # 登录成功后的回调函数
            )

    def parse_after_login(self, response):
        """登录成功后,解析个人主页"""
        # 检查是否登录成功(提取个人昵称)
        nickname = response.xpath('//span[@class="css-truncate css-truncate-target ml-1"]/text()').extract_first()
        if nickname:
            self.logger.info(f"登录成功!当前用户:{nickname.strip()}")
            # 登录成功后,爬取个人仓库(示例)
            yield scrapy.Request(
                url=f'https://github.com/{nickname.strip()}?tab=repositories',
                callback=self.parse_repositories
            )
        else:
            self.logger.error("登录失败!请检查用户名和密码")

    def parse_repositories(self, response):
        """解析个人仓库列表"""
        # 提取仓库名称(示例XPath)
        repo_names = response.xpath('//h3[@class="wb-break-all"]/a/text()').extract()
        self.logger.info(f"个人仓库列表:{[name.strip() for name in repo_names]}")

步骤 3:运行爬虫

执行scrapy crawl github_login,如果日志中显示 “登录成功!当前用户:你的用户名”,说明模拟登录成功,后续可以爬取需要登录的页面。

七、避坑指南:Scrapy 常见错误及解决方案

在实际爬取中,你可能会遇到各种错误,这里总结 6 个最常见的问题及解决方案,帮你快速排查。

7.1 错误 1:爬虫运行后无数据,日志显示 “Crawled (403)”

现象:日志中显示 “Crawled (403) <GET https://movie.douban.com/top250>”,没有提取到数据。原因:网站识别你是爬虫,返回 403 禁止访问(缺少 User-Agent 或 Cookie)。解决方案

  1. settings.py中设置正确的 User-Agent(从浏览器中复制,不要用默认的 Scrapy UA);
  2. 如果还是 403,添加 Cookie(模拟登录状态);
  3. 启用 IP 代理中间件,切换 IP。

7.2 错误 2:XPath 选择器提取不到数据(返回 None)

现象:用extract_first()提取数据时返回 None,日志中没有报错。常见原因及解决方案

  1. XPath 语法错误(比如标签名写错、属性值不对):

    • 用 Chrome 开发者工具验证 XPath(在 “Elements” 标签按Ctrl+F,粘贴 XPath,看是否能匹配到元素);
    • 检查是否把 “绝对路径” 写成了 “相对路径”(在循环中用./开头,比如./div[@class="item"])。
  2. 页面动态加载(Scrapy 爬取的 HTML 是静态的,JS 加载的数据不在其中):

    • 用 Chrome 开发者工具的 “View Page Source” 查看静态 HTML,确认数据是否在其中;
    • 如果数据是 JS 动态加载的,改用 Scrapy-Splash(处理动态页面)或分析 API 接口。

7.3 错误 3:Pipeline 不生效(数据没保存到 CSV)

现象:爬虫运行完成,但没有生成 CSV 文件,日志中没有 “爬取完成!数据已保存到 douban_top250.csv”。原因

  1. 没有在settings.py中启用 Pipeline(ITEM_PIPELINES配置被注释);
  2. Pipeline 的优先级设置过高或过低(数字不在 1~1000 之间);
  3. process_item方法中没有return item(导致数据没有传递到 Pipeline)。解决方案
  4. 确保settings.pyITEM_PIPELINES配置已启用,且优先级正确:
    ITEM_PIPELINES = {
        'douban_spider.pipelines.DoubanSpiderPipeline': 300,
    }
    
  5. 检查process_item方法,确保最后有return item

7.4 错误 4:爬取速度过快,被网站封 IP

现象:爬虫刚开始能正常爬取,几分钟后返回 403 或 503 错误,更换 IP 后恢复正常。原因:Scrapy 默认爬取速度快(多线程),短时间内发起大量请求,被网站识别并封禁 IP。解决方案

  1. settings.py中设置DOWNLOAD_DELAY = 3(每 3 秒发一次请求,根据网站反爬强度调整);
  2. 启用 IP 代理中间件,轮换 IP;
  3. 启用 User-Agent 轮换中间件,避免被识别为爬虫;
  4. settings.py中设置并发请求数:
    CONCURRENT_REQUESTS = 5  # 同时发起的请求数,默认16,改小为5~10
    

7.5 错误 5:模拟登录失败(返回登录页)

现象:用FormRequest提交登录表单后,回调函数parse_after_login解析的还是登录页 HTML,没有个人数据。原因

  1. 登录表单参数错误(比如缺少authenticity_tokencommit值不对);
  2. 密码错误或账号有验证码(GitHub 登录如果开启两步验证,需要额外处理);
  3. 请求头不完整(缺少 Referer、Host 等)。解决方案
  4. 重新分析登录请求的 Form Data,确保所有参数都正确(尤其是authenticity_token需要动态提取);
  5. 检查用户名和密码是否正确,关闭账号的两步验证(或处理验证码);
  6. FormRequest中添加完整的请求头:
    yield scrapy.FormRequest(
        url='https://github.com/session',
        formdata=...,
        headers={
            'Referer': 'https://github.com/login',
            'Host': 'github.com'
        },
        callback=self.parse_after_login
    )
    

7.6 错误 6:CSV 文件中文乱码(Excel 打开显示 “???”)

现象:CSV 文件用记事本打开中文正常,但用 Excel 打开中文显示乱码。原因:CSV 文件编码是utf-8,Excel 默认用gbk编码打开,导致乱码。解决方案

  1. pipelines.py中用utf-8-sig编码保存 CSV(不是utf-8):
    self.csv_file = open('douban_top250.csv', 'w', encoding='utf-8-sig', newline='')
    
  2. 用 Excel 打开 CSV 时,选择 “数据”→“自文本 / CSV”,选择文件后,编码选择 “UTF-8”,再导入。

八、总结与进阶学习建议

8.1 本文核心知识点总结

  1. 环境搭建:用 Anaconda 或 pip 安装 Scrapy,验证安装并创建项目;
  2. 核心组件:掌握 Spider(爬取规则)、Item(数据结构)、Pipeline(数据保存)、中间件(扩展功能)的用法;
  3. 数据提取:学会用 XPath 选择器从 HTML 中提取数据,用 Chrome 开发者工具快速生成 XPath;
  4. 反爬防护:通过 User-Agent 轮换、IP 代理、下载延迟应对网站反爬;
  5. 模拟登录:用 Cookie 登录或 Form 表单登录获取需要登录的网站数据;
  6. 实战能力:能独立完成 “爬取豆瓣 Top250”“爬取 GitHub 个人仓库” 等项目,解决常见错误。

8.2 进阶学习方向

  1. 处理动态页面:Scrapy 只能爬取静态 HTML,动态 JS 加载的数据需要学Scrapy-Splash(集成 Splash 渲染引擎)或Scrapy-Playwright(集成 Playwright 模拟浏览器);
  2. 分布式爬取:学Scrapy-Redis,将多个 Scrapy 爬虫组成分布式集群,爬取大规模数据(如百万级网页);
  3. 数据存储进阶:除了 CSV,学MySQLMongoDB,在 Pipeline 中实现数据保存到数据库,支持查询和分析;
  4. API 爬取:很多网站的动态数据来自 API 接口(如知乎、抖音),学分析 API 请求参数(如 sign、token),用 Scrapy 爬取 API 数据;
  5. 监控与调度:学Scrapyd(部署和管理 Scrapy 爬虫)、Airflow(定时调度爬虫),实现爬虫的自动化运行和监控。

8.3 学习建议

  1. 多练实战:从简单的静态网站开始(如豆瓣 Top250、博客园),逐步挑战复杂网站(如电商、社交平台),不要只看理论;
  2. 善用文档:Scrapy 官方文档(https://docs.scrapy.org/)是最好的学习资料,遇到问题先查文档;
  3. 分析别人的代码:在 GitHub 上搜索 “Scrapy 爬虫案例”(如 “scrapy douban”),学习别人的项目结构和代码逻辑;
  4. 记录问题:把遇到的错误(如 403 封禁、XPath 提取不到数据)和解决方案记录下来,形成自己的 “避坑手册”;
  5. 遵守规则:爬取网站时尊重robots.txt协议,不要爬取隐私数据或商业数据,避免法律风险。

最后,Scrapy 的核心是 “框架思维”—— 它帮你封装了重复的工作,让你专注于核心业务逻辑。刚开始可能觉得组件多、配置复杂,但一旦掌握,你会发现它是爬取大规模数据的 “终极工具”。坚持多练、多查、多总结,你很快就能成为 Scrapy 高手!

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值