目录
引言:为什么用 Requests 爬数据越用越累?Scrapy 才是终极解决方案
一、Scrapy 入门:搞懂它为什么比 Requests 更高效
1.2 Scrapy 的核心优势:3 个让你放弃 Requests 的理由
二、环境搭建:3 分钟搞定 Scrapy(Windows/Mac 通用)
2.2 方式 2:pip 安装(适合已有 Python 环境的用户)
四、第一个 Scrapy 爬虫:爬取豆瓣 Top250 电影(完整流程)
5.1 XPath 选择器:Scrapy 的 “数据提取利器”
5.1.2 实战:用 Chrome 开发者工具快速生成 XPath
5.1.3 Scrapy 中 XPath 的 2 个常用方法
5.2.2 实战 1:写一个 User-Agent 轮换中间件
方式 A:在 settings.py 中全局添加(所有请求都带 Cookie)
方式 B:在 Spider 中局部添加(特定请求带 Cookie)
6.2 方式 2:Form 表单登录(自动化,适合 Cookie 有效期短的网站)
7.1 错误 1:爬虫运行后无数据,日志显示 “Crawled (403)”
7.2 错误 2:XPath 选择器提取不到数据(返回 None)
7.3 错误 3:Pipeline 不生效(数据没保存到 CSV)
7.6 错误 6:CSV 文件中文乱码(Excel 打开显示 “???”)

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 是 “爬虫框架”,就像 “自行车” 和 “汽车” 的区别。
| 对比维度 | Requests | Scrapy |
|---|---|---|
| 定位 | 单一请求工具(类似 “自行车零件”) | 完整爬虫系统(类似 “整辆汽车”) |
| 核心功能 | 发送 HTTP 请求、获取响应 | 请求调度、自动化解析、去重、持久化等 |
| 性能 | 单线程,效率低(适合小数据量) | 多线程 / 异步,效率高(适合大数据量) |
| 代码复杂度 | 需手动写循环、去重、保存逻辑 | 只需写核心业务逻辑(如解析数据) |
| 反爬支持 | 需手动实现代理、UA 轮换 | 内置中间件,支持灵活扩展 |
| 适用场景 | 单页面爬取、简单数据提取 | 多页面爬取、大规模数据采集 |
举个例子:爬取豆瓣 Top250 电影(10 页,250 条数据)
- 用 Requests:需要写循环生成 10 个页面 URL→逐个发请求→手动解析每个页面→去重(防止重复爬取)→手动保存到 CSV;
- 用 Scrapy:只需定义 “起始 URL”“解析规则”“保存方式”,框架会自动处理请求调度、翻页、去重,你甚至不用写循环。
1.2 Scrapy 的核心优势:3 个让你放弃 Requests 的理由
-
高性能:多线程 + 异步下载Scrapy 默认用多线程处理请求,同时发起多个 HTTP 请求,比 Requests 的单线程循环快 3~10 倍。比如爬取 100 页数据,Requests 要等前一页请求完成才发下一页,Scrapy 能同时发 5~10 个请求(可配置),效率大幅提升。
-
自动化:少写 80% 重复代码框架自动处理 “请求去重”(避免重复爬取同一 URL)、“翻页调度”(找到下一页链接自动爬取)、“数据清洗”(用 Item 结构化数据),你不用再写 “判断 URL 是否爬过”“循环生成页码” 这类重复代码。
-
高扩展性:应对复杂反爬和需求Scrapy 的 “中间件” 机制支持灵活扩展 —— 想加 IP 代理?加一个下载中间件;想模拟登录?加一个请求中间件;想自定义数据保存方式?加一个 Pipeline。不用修改核心代码,就能实现复杂功能。
二、环境搭建:3 分钟搞定 Scrapy(Windows/Mac 通用)

Scrapy 的安装比 Requests 稍复杂(依赖较多),但按照步骤来,保证你能顺利搭好环境。推荐用Anaconda安装(自动处理依赖),如果没有 Anaconda,也可以用 pip 安装。
2.1 方式 1:Anaconda 安装(推荐,适合新手)
Anaconda 是 Python 的科学计算环境,能自动管理依赖包,避免 “安装失败”“版本冲突” 等问题。
步骤 1:安装 Anaconda(已安装的跳过)
-
Windows 用户:
- 去 Anaconda 官网(https://www.anaconda.com/products/distribution#download-section)下载 Windows 版,默认安装即可(记得勾选 “Add Anaconda to my PATH environment variable”)。
- 安装完成后,打开 “Anaconda Prompt”(类似命令提示符),输入
conda --version,显示 “conda 23.10.0”(版本号可能不同)即成功。
-
Mac 用户:
- 下载 Mac 版 Anaconda,拖到应用程序中完成安装。
- 打开 “终端”,输入
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 的预编译包:
- 去 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”)。
- 打开命令提示符,进入下载目录,输入
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 | 配置项目参数(下载延迟、中间件开关) | 爬虫的 “控制面板”,调节运行参数 |
举个例子:你想爬取 “电影名称、评分、上映年份”
- 先在
items.py中定义这 3 个字段(告诉框架要爬哪些数据); - 在
spiders/中写 Spider 代码(告诉框架从豆瓣 Top250 的 URL 开始爬,用 XPath 提取这 3 个字段); - 在
pipelines.py中写代码(告诉框架把爬取到的数据保存到douban_top250.csv); - 在
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)
关键代码解释:
start_urls:爬虫的起始 URL,Scrapy 会自动对这个 URL 发起请求,请求完成后调用parse方法;parse方法:核心解析函数,接收响应对象response,提取数据并处理翻页;- XPath 选择器:用于从 HTML 中提取数据,比如
//div[@class="item"]表示 “查找所有 class 为 item 的 div 标签”(后面会详细讲 XPath 用法); yield movie_data:将提取到的数据交给pipelines.py处理(类似 “传球”);- 翻页处理:找到下一页的 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?
- 打开 Chrome 浏览器,访问任意网页;
- 按
F12打开开发者工具,切换到 “Network” 标签; - 刷新页面,点击第一个请求,在 “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 数据:
| rank | title | rating | year |
|---|---|---|---|
| 1 | 肖申克的救赎 | 9.7 | 1994 |
| 2 | 霸王别姬 | 9.6 | 1993 |
| 3 | 阿甘正传 | 9.5 | 1994 |
| ... | ... | ... | ... |
五、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:
- 打开目标网页(如豆瓣 Top250),按
F12打开开发者工具; - 切换到 “Elements” 标签,找到要提取的元素(如电影名称的 span 标签);
- 右键点击该元素→“Copy”→“Copy XPath”;
- 粘贴到 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
- 用 Chrome 浏览器登录目标网站(如知乎);
- 按
F12打开开发者工具,切换到 “Network” 标签; - 刷新页面,点击第一个请求(如
https://www.zhihu.com/); - 在 “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 为例)
- 打开 GitHub 登录页(https://github.com/login);
- 按
F12打开开发者工具,切换到 “Network” 标签; - 输入错误的用户名和密码,点击登录,找到名为 “session” 的 POST 请求;
- 在 “Form Data” 中查看登录表单的参数(如
commit、authenticity_token、login、password)。
步骤 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)。解决方案:
- 在
settings.py中设置正确的 User-Agent(从浏览器中复制,不要用默认的 Scrapy UA); - 如果还是 403,添加 Cookie(模拟登录状态);
- 启用 IP 代理中间件,切换 IP。
7.2 错误 2:XPath 选择器提取不到数据(返回 None)
现象:用extract_first()提取数据时返回 None,日志中没有报错。常见原因及解决方案:
-
XPath 语法错误(比如标签名写错、属性值不对):
- 用 Chrome 开发者工具验证 XPath(在 “Elements” 标签按
Ctrl+F,粘贴 XPath,看是否能匹配到元素); - 检查是否把 “绝对路径” 写成了 “相对路径”(在循环中用
./开头,比如./div[@class="item"])。
- 用 Chrome 开发者工具验证 XPath(在 “Elements” 标签按
-
页面动态加载(Scrapy 爬取的 HTML 是静态的,JS 加载的数据不在其中):
- 用 Chrome 开发者工具的 “View Page Source” 查看静态 HTML,确认数据是否在其中;
- 如果数据是 JS 动态加载的,改用 Scrapy-Splash(处理动态页面)或分析 API 接口。
7.3 错误 3:Pipeline 不生效(数据没保存到 CSV)
现象:爬虫运行完成,但没有生成 CSV 文件,日志中没有 “爬取完成!数据已保存到 douban_top250.csv”。原因:
- 没有在
settings.py中启用 Pipeline(ITEM_PIPELINES配置被注释); - Pipeline 的优先级设置过高或过低(数字不在 1~1000 之间);
process_item方法中没有return item(导致数据没有传递到 Pipeline)。解决方案:- 确保
settings.py中ITEM_PIPELINES配置已启用,且优先级正确:ITEM_PIPELINES = { 'douban_spider.pipelines.DoubanSpiderPipeline': 300, } - 检查
process_item方法,确保最后有return item。
7.4 错误 4:爬取速度过快,被网站封 IP
现象:爬虫刚开始能正常爬取,几分钟后返回 403 或 503 错误,更换 IP 后恢复正常。原因:Scrapy 默认爬取速度快(多线程),短时间内发起大量请求,被网站识别并封禁 IP。解决方案:
- 在
settings.py中设置DOWNLOAD_DELAY = 3(每 3 秒发一次请求,根据网站反爬强度调整); - 启用 IP 代理中间件,轮换 IP;
- 启用 User-Agent 轮换中间件,避免被识别为爬虫;
- 在
settings.py中设置并发请求数:CONCURRENT_REQUESTS = 5 # 同时发起的请求数,默认16,改小为5~10
7.5 错误 5:模拟登录失败(返回登录页)
现象:用FormRequest提交登录表单后,回调函数parse_after_login解析的还是登录页 HTML,没有个人数据。原因:
- 登录表单参数错误(比如缺少
authenticity_token、commit值不对); - 密码错误或账号有验证码(GitHub 登录如果开启两步验证,需要额外处理);
- 请求头不完整(缺少 Referer、Host 等)。解决方案:
- 重新分析登录请求的 Form Data,确保所有参数都正确(尤其是
authenticity_token需要动态提取); - 检查用户名和密码是否正确,关闭账号的两步验证(或处理验证码);
- 在
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编码打开,导致乱码。解决方案:
- 在
pipelines.py中用utf-8-sig编码保存 CSV(不是utf-8):self.csv_file = open('douban_top250.csv', 'w', encoding='utf-8-sig', newline='') - 用 Excel 打开 CSV 时,选择 “数据”→“自文本 / CSV”,选择文件后,编码选择 “UTF-8”,再导入。
八、总结与进阶学习建议
8.1 本文核心知识点总结
- 环境搭建:用 Anaconda 或 pip 安装 Scrapy,验证安装并创建项目;
- 核心组件:掌握 Spider(爬取规则)、Item(数据结构)、Pipeline(数据保存)、中间件(扩展功能)的用法;
- 数据提取:学会用 XPath 选择器从 HTML 中提取数据,用 Chrome 开发者工具快速生成 XPath;
- 反爬防护:通过 User-Agent 轮换、IP 代理、下载延迟应对网站反爬;
- 模拟登录:用 Cookie 登录或 Form 表单登录获取需要登录的网站数据;
- 实战能力:能独立完成 “爬取豆瓣 Top250”“爬取 GitHub 个人仓库” 等项目,解决常见错误。
8.2 进阶学习方向
- 处理动态页面:Scrapy 只能爬取静态 HTML,动态 JS 加载的数据需要学Scrapy-Splash(集成 Splash 渲染引擎)或Scrapy-Playwright(集成 Playwright 模拟浏览器);
- 分布式爬取:学Scrapy-Redis,将多个 Scrapy 爬虫组成分布式集群,爬取大规模数据(如百万级网页);
- 数据存储进阶:除了 CSV,学MySQL或MongoDB,在 Pipeline 中实现数据保存到数据库,支持查询和分析;
- API 爬取:很多网站的动态数据来自 API 接口(如知乎、抖音),学分析 API 请求参数(如 sign、token),用 Scrapy 爬取 API 数据;
- 监控与调度:学Scrapyd(部署和管理 Scrapy 爬虫)、Airflow(定时调度爬虫),实现爬虫的自动化运行和监控。
8.3 学习建议
- 多练实战:从简单的静态网站开始(如豆瓣 Top250、博客园),逐步挑战复杂网站(如电商、社交平台),不要只看理论;
- 善用文档:Scrapy 官方文档(https://docs.scrapy.org/)是最好的学习资料,遇到问题先查文档;
- 分析别人的代码:在 GitHub 上搜索 “Scrapy 爬虫案例”(如 “scrapy douban”),学习别人的项目结构和代码逻辑;
- 记录问题:把遇到的错误(如 403 封禁、XPath 提取不到数据)和解决方案记录下来,形成自己的 “避坑手册”;
- 遵守规则:爬取网站时尊重
robots.txt协议,不要爬取隐私数据或商业数据,避免法律风险。
最后,Scrapy 的核心是 “框架思维”—— 它帮你封装了重复的工作,让你专注于核心业务逻辑。刚开始可能觉得组件多、配置复杂,但一旦掌握,你会发现它是爬取大规模数据的 “终极工具”。坚持多练、多查、多总结,你很快就能成为 Scrapy 高手!
1136

被折叠的 条评论
为什么被折叠?



