Scrapy 是 Python 生态中最成熟、最专业的异步爬虫框架,不是单一的功能库,而是一整套爬虫解决方案。它帮我们封装好了请求调度、并发下载、数据提取、去重、数据存储等重复工作,让我们只需要专注写「数据提取逻辑」,就能快速开发高性能、结构化的爬虫项目。
如果把 requests + BeautifulSoup 比作自己手搓的自行车,适合短距离、简单场景;那 Scrapy 就是全自动的汽车,适合大规模、批量爬取、结构化采集的专业场景。
本文从概念、安装、项目结构到完整实战,全程通俗讲解 + 可运行代码 + 逐行解释,零基础也能一步步跑通第一个 Scrapy 爬虫。
⚠️ 合规提示:本文仅用于技术学习,请遵守目标网站的
robots协议与用户协议,控制请求频率,不要恶意爬取他人网站数据。
一、先搞懂:Scrapy 是什么?
1. 通俗理解
你可以把 Scrapy 理解成一个「爬虫工厂」:
- 你只需要告诉它:要爬哪些网站、要提取什么数据、数据怎么处理
- 工厂自动帮你完成:发请求、下载页面、调度任务、并发提速、去重、存数据
- 不用自己写循环发请求、不用自己管理多线程、不用自己处理异常重试,框架都帮你做好了
2. 和 requests + BeautifulSoup 的区别
| 对比项 | requests + BeautifulSoup | Scrapy 框架 |
|---|---|---|
| 定位 | 轻量工具,灵活自由 | 专业框架,工程化 |
| 并发 | 需要自己写多线程 / 协程 | 原生异步高并发,自动调度 |
| 功能 | 只有请求 + 解析 | 自带去重、管道、中间件、导出等全套功能 |
| 学习成本 | 低,上手快 | 稍高,需要熟悉项目结构与规则 |
| 适用场景 | 少量页面、简单爬虫、临时需求 | 批量爬取、结构化采集、长期项目 |
3. 核心优势
- ✅ 高性能异步:基于 Twisted 异步框架,并发能力强,爬取速度远快于同步手写代码
- ✅ 结构化强:通过 Item 定义数据字段,规范整齐,不容易乱
- ✅ 功能齐全:自带自动去重、重试、Cookie 管理、User-Agent 轮换等
- ✅ 扩展性强:中间件、管道可以自由扩展,比如存数据库、分布式爬取
- ✅ 导出方便:一行命令导出 JSON、CSV、XML 等格式
二、环境安装
Scrapy 是第三方库,通过 pip 安装即可。
安装命令
打开终端 / 命令行,执行:
pip install scrapy
注意:Windows 安装如果报错,大概率是缺少 VC++ 依赖,可以先安装
pywin32,或者直接用 Anaconda 环境安装更省心。
验证安装
安装完成后,终端输入:
scrapy version
输出版本号(比如 Scrapy 2.11.0)就说明安装成功。
三、核心组件与工作流程(新手必懂)
Scrapy 采用组件化设计,每个组件各司其职,配合完成整个爬取流程。新手不用深究底层,先搞懂每个组件是干嘛的、数据怎么流转的即可。
1. 五大核心组件(工厂类比)
表格
| 组件 | 中文名 | 通俗职责 |
|---|---|---|
| Engine | 引擎 | 工厂厂长,总管所有流程,协调各个组件工作 |
| Scheduler | 调度器 | 任务排队员,管理所有待爬取的网址,按顺序发给下载器 |
| Downloader | 下载器 | 采购员,负责发请求下载网页源码 |
| Spider | 爬虫 | 解析员,拿到网页后提取目标数据、发现新的待爬网址 |
| Pipeline | 管道 | 加工仓,对爬下来的数据做清洗、去重、存数据库等处理 |
2. 简单工作流程
- 你在 Spider 里写好起始网址,交给引擎
- 引擎把网址发给调度器,排进待爬队列
- 调度器把下一个网址发给下载器,下载网页源码
- 下载器把网页源码返回给 Spider,你写的解析代码提取数据
- Spider 提取出两类内容,交回引擎:
- 数据 Item:交给 Pipeline 做后续处理 / 存储
- 新网址:交给调度器,加入队列继续爬
- 循环往复,直到队列里没有网址,爬虫结束
💡 新手可以先记住:我们写代码主要改两个地方
- Spider 爬虫文件:写网址、写提取逻辑
- Pipeline 管道文件:写数据处理、存储逻辑 其他组件框架默认都做好了,新手不用改。
四、Scrapy 项目结构
Scrapy 是按项目管理的,不是单个 py 文件,所有代码都在统一的项目目录里。
1. 创建项目
终端进入你想放代码的文件夹,执行:
scrapy startproject 项目名
比如我们创建一个爬取名言的项目:
scrapy startproject quotetutorial
执行后会自动生成一个 quotetutorial 文件夹,目录结构如下:
python
quotetutorial/
scrapy.cfg # 项目配置文件(一般不用改)
quotetutorial/ # 项目核心代码包
__init__.py
items.py # 【重点】定义你要爬的数据结构(字段)
middlewares.py # 中间件(新手一般不动)
pipelines.py # 【重点】数据管道,清洗/存储数据
settings.py # 【重点】全局配置:请求头、并发、延迟、管道开关等
spiders/ # 【重点】存放所有爬虫文件
__init__.py
2. 核心文件说明
| 文件 | 作用 | 新手优先级 |
|---|---|---|
spiders/ 文件夹 |
放爬虫代码,每个爬虫对应一个文件,写网址和提取逻辑 | ⭐⭐⭐⭐⭐ |
items.py |
定义要爬取的字段,相当于数据模板,让数据更规范 | ⭐⭐⭐⭐ |
pipelines.py |
处理爬下来的数据,比如清洗、去重、存数据库 | ⭐⭐⭐ |
settings.py |
全局配置,改请求头、下载延迟、开启管道都在这里 | ⭐⭐⭐⭐⭐ |
middlewares.py |
中间件,比如代理、User-Agent 轮换,进阶再学 | ⭐⭐ |
五、第一个完整实战:爬取名言网站
我们用专门的爬虫练习网站 https://quotes.toscrape.com/ 做实战,目标是爬取每页的名言、作者、标签。 这个网站无反爬、结构清晰,非常适合新手练手。
步骤 1:创建项目
终端执行:
python
scrapy startproject quotetutorial
cd quotetutorial
步骤 2:定义数据结构(items.py)
Item 就是你要爬的数据的「模板」,提前定义好要哪些字段,避免爬的时候漏字段、字段名写错,数据更规范。
打开 quotetutorial/items.py,修改成如下代码:
python
import scrapy
class QuoteItem(scrapy.Item):
# 定义三个字段:名言内容、作者、标签
text = scrapy.Field() # 名言内容
author = scrapy.Field() # 作者
tags = scrapy.Field() # 标签(列表)
代码解释
- 继承
scrapy.Item就是一个标准的数据类 scrapy.Field()用来声明字段,类似字典的 key- 后面爬虫里提取完数据,就把数据塞进这个 Item 里,交给管道处理
步骤 3:编写爬虫文件(核心)
在 spiders 文件夹里新建一个文件 quote_spider.py,这就是我们的爬虫文件,所有网址和提取逻辑都写在这里。
完整代码
python
import scrapy
from quotetutorial.items import QuoteItem # 导入我们定义好的数据类
class QuoteSpider(scrapy.Spider):
# 爬虫的名字:运行爬虫时用这个名字,一个项目可以有多个爬虫,名字不能重复
name = "quote"
# 允许爬取的域名:防止爬虫爬到别的网站去,安全限制
allowed_domains = ["quotes.toscrape.com"]
# 起始网址:爬虫启动后最先请求的网址,可以写多个
start_urls = ["https://quotes.toscrape.com/"]
# 解析函数:下载器拿到网页后,自动调用这个方法解析
# response 就是下载好的响应对象,包含网页源码和各种方法
def parse(self, response):
# 第一步:提取当前页所有名言条目
# response.css() 用CSS选择器提取元素,返回选择器列表
quote_items = response.css("div.quote")
# 遍历每个条目,提取数据
for quote in quote_items:
# 创建Item对象,装数据
item = QuoteItem()
# ::text 是Scrapy CSS选择器的语法:提取标签内的文本
# get() 提取第一个结果的字符串
item["text"] = quote.css("span.text::text").get().strip("""")
item["author"] = quote.css("small.author::text").get()
# getall() 提取所有结果,返回列表
item["tags"] = quote.css("a.tag::text").getall()
# 【关键】yield 交出数据,交给框架传给管道
yield item
逐行核心解释
-
name = "quote"爬虫的唯一标识,运行爬虫的时候用scrapy crawl quote就是通过这个名字找到对应爬虫。 -
start_urls起始网址列表,框架会自动给这些网址发请求,下载完页面自动调用parse方法解析。 -
parse(self, response)解析回调函数,是爬虫的核心:- 参数
response是下载好的响应对象,包含网页全部内容 - 所有数据提取、新网址发现都在这个函数里写
- 函数必须用
yield返回数据或新请求,不能用 return
- 参数
-
response.css()提取数据 Scrapy 内置了 CSS 选择器和 XPath 两种提取方式,新手选自己熟悉的即可:css("标签名::text"):提取标签内的文本css("标签名::attr(href)"):提取标签的属性值,比如 href、src.get():拿到第一个匹配结果的字符串.getall():拿到所有匹配结果,返回列表
补充:也可以用 XPath 写法,比如
response.xpath('//span[@class="text"]/text()').get(),效果完全一样。 -
yield item这是 Scrapy 最核心的语法。你可以把yield理解成「上交」:- 把整理好的 Item 上交,框架会自动传给管道处理
- 不用自己存、不用自己管理列表,框架统一调度
- 这是生成器的用法,也是 Scrapy 异步的基础
步骤 4:修改基础配置(settings.py)
打开 quotetutorial/settings.py,修改几个新手必改的配置,避免被网站拦截、出现乱码等问题。
找到对应位置,修改 / 添加:
python
# 1. 关闭 robots 协议遵守(练习用,真实项目建议遵守)
ROBOTSTXT_OBEY = False
# 2. 设置 User-Agent,模拟浏览器,不然会被拦截
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"
# 3. 设置下载延迟,每秒发一次请求,文明爬虫,避免封IP
DOWNLOAD_DELAY = 1
# 4. 导出JSON时中文正常显示,不用Unicode
FEED_EXPORT_ENCODING = "utf-8"
步骤 5:运行爬虫
终端进入项目根目录(有 scrapy.cfg 的那一层),执行:
scrapy crawl quote
运行后终端会打印日志,你会看到爬虫自动请求页面、提取数据。
步骤 6:导出数据到文件
如果想把爬下来的数据直接保存成 JSON 或 CSV 文件,不用写管道,加个参数就行:
python
# 导出为 JSON 文件
scrapy crawl quote -o quotes.json
# 导出为 CSV 文件
scrapy crawl quote -o quotes.csv
运行后项目根目录会生成对应的文件,打开就能看到结构化的数据。
六、进阶 1:实现自动翻页
上面的代码只能爬第一页,我们可以让爬虫自动识别「下一页」链接,继续爬,直到所有页爬完。
修改 quote_spider.py 的 parse 方法
在函数末尾加上翻页逻辑:
python
def parse(self, response):
# ===== 上面的提取数据代码不变 =====
quote_items = response.css("div.quote")
for quote in quote_items:
item = QuoteItem()
item["text"] = quote.css("span.text::text").get().strip("""")
item["author"] = quote.css("small.author::text").get()
item["tags"] = quote.css("a.tag::text").getall()
yield item
# ===== 新增:翻页逻辑 =====
# 提取下一页的链接
next_url = response.css("li.next a::attr(href)").get()
# 如果有下一页
if next_url:
# 构造完整网址(相对路径转绝对路径)
# response.urljoin() 自动拼接域名,不用自己拼
full_url = response.urljoin(next_url)
# 【关键】yield 一个新的Request,交给调度器继续爬
# callback=self.parse 表示下载完还用当前这个parse函数解析
yield scrapy.Request(url=full_url, callback=self.parse)
代码解释
- 先找到下一页按钮的
href属性,拿到相对链接 - 用
response.urljoin()自动拼接成完整的绝对网址 yield scrapy.Request(...)把新网址上交,框架会自动去下载,下载完再调用parse解析- 循环往复,直到某一页没有下一页链接,爬虫自动结束
再次运行 scrapy crawl quote -o all_quotes.json,就能爬完网站所有 10 页数据。
七、进阶 2:数据管道 Pipeline
当数据需要清洗、去重、存数据库时,就可以写在 Pipeline 里。管道可以有多个,按优先级顺序执行。
示例:做一个简单的数据清洗管道
打开 pipelines.py,我们写一个管道:去除空数据、打印保存提示。
python
class QuoteCleanPipeline:
# 爬虫启动时执行一次
def open_spider(self, spider):
print("爬虫启动,开始处理数据...")
# 每个Item都会经过这个方法处理
def process_item(self, item, spider):
# 清洗:如果名言内容为空,就丢弃
if not item.get("text"):
raise DropItem("空数据,丢弃")
# 给作者名字去掉首尾空格
item["author"] = item["author"].strip()
# 必须return item,把数据交给下一个管道
return item
# 爬虫结束时执行一次
def close_spider(self, spider):
print("爬虫结束,数据处理完成")
关键:在 settings.py 中开启管道
管道写完默认是不生效的,必须在 settings.py 里开启并设置优先级:
python
ITEM_PIPELINES = {
# 格式:"管道类路径": 优先级数字
# 数字越小,优先级越高,越先执行
"quotetutorial.pipelines.QuoteCleanPipeline": 300,
}
开启后,每个爬取到的 Item 都会自动经过这个管道处理。
💡 拓展:你还可以写更多管道,比如保存到 MySQL、MongoDB、去重管道等,分别设置不同的优先级即可。
八、常用提取方法汇总
Scrapy 的 response 对象提供了丰富的提取方法,新手掌握 CSS 和 XPath 两种即可。
1. CSS 选择器(简单易上手)
| 写法 | 作用 |
|---|---|
response.css("div.title") |
选择 class 为 title 的 div |
response.css("#header") |
选择 id 为 header 的元素 |
css("a::text").get() |
提取 a 标签的文本 |
css("a::attr(href)").get() |
提取 a 标签的 href 属性 |
.get() |
取第一个匹配结果,字符串 |
.getall() |
取所有匹配结果,列表 |
2. XPath 选择器(功能更强)
和 Selenium 的 XPath 语法完全一致:
python
# 提取名言文本
text = response.xpath('//span[@class="text"]/text()').get()
# 提取下一页链接
next_url = response.xpath('//li[@class="next"]/a/@href').get()
3. 小技巧
- 不知道怎么写选择器?浏览器 F12 找到元素,右键 → 复制 → 复制选择器 / 复制 XPath,直接粘贴使用
- 调试时可以用
scrapy shell 网址进入交互模式,实时测试选择器是否正确,非常适合新手调试
九、新手高频易错点总结
1. 运行爬虫找不到项目
必须在项目根目录 (有 scrapy.cfg 的文件夹)下执行 scrapy crawl 命令,不然会报错。
2. parse 方法用 return 不用 yield
Scrapy 的解析函数必须用 yield 返回 Item 或 Request,用 return 数据不会被框架接收。 可以简单理解:yield 是 "逐个上交",符合框架的异步调度逻辑。
3. 管道写了不生效
必须在 settings.py 的 ITEM_PIPELINES 里配置开启,不然管道永远不会执行。
4. 中文导出乱码
在 settings.py 里添加 FEED_EXPORT_ENCODING = "utf-8",导出 JSON/CSV 就会正常显示中文。
5. 相对链接不拼接域名
拿到的 href 是相对路径(比如 /page/2/),不能直接请求,必须用 response.urljoin(next_url) 拼接成完整网址。
6. 爬不到数据,先看源码
先打印 response.text,确认拿到的网页源码和浏览器里的是不是一样:
- 源码里有内容 → 是选择器写错了
- 源码里没有内容 → 是 JS 动态渲染的页面,Scrapy 直接爬不了,需要配合 Selenium 或者抓接口
7. 请求太快被封 IP
一定要加 DOWNLOAD_DELAY 设置延迟,控制请求频率,文明爬取。