Scrapy框架入门
网络爬虫专题 · 掌握Scrapy爬虫框架
专题:Python网络爬虫系统学习
关键词:Python, 网络爬虫, Scrapy, 爬虫框架, Spider, Pipeline, 中间件, 异步爬虫, XPath
一、Scrapy概述
Scrapy是Python生态中最流行的开源爬虫框架,由Scrapinghub公司维护,广泛应用于数据采集、信息监控和自动化测试等领域。它基于Twisted事件驱动框架构建,采用异步非阻塞I/O模型,能够在单线程内高效处理大量并发请求,极大提升爬取效率。
与Requests+BeautifulSoup的手动组合方案相比,Scrapy提供了完整的爬虫生命周期管理,包括请求调度、并发控制、数据解析、结果持久化和去重策略等。开发者只需关注爬虫逻辑本身,无需操心底层网络通信细节。
核心特点:异步架构、插件化中间件、强大的选择器、自动限速与重试、内置去重机制、支持分布式扩展。
安装方式
Scrapy的安装非常简便,推荐使用pip包管理器。在终端中直接执行以下命令即可完成安装:
pip install scrapy
安装完成后,可以在终端中运行 scrapy version 验证安装是否成功。如果看到类似 Scrapy 2.11.x 的版本号输出,说明已经安装成功。需要注意的是,Scrapy依赖的Twisted框架在某些Python版本或操作系统上可能需要预编译支持,在Windows环境下建议使用官方Python发行版以避免兼容性问题。
二、Scrapy架构
理解Scrapy的架构是高效开发的前提。Scrapy遵循组件化设计理念,各模块职责清晰,通过中间件机制实现高度的可扩展性。整体架构由五个核心组件和两个中间件层构成。
核心组件
- Engine(引擎):是整个框架的大脑,负责控制数据流在各个组件之间流转。它接收Spider发出的请求,协调Scheduler和Downloader的工作,并将Spider解析到的Item传递给Pipeline处理。引擎本身不可扩展,但其工作流程可被中间件拦截和修改。
- Scheduler(调度器):负责管理请求队列。引擎发来的请求会先进入调度器,调度器根据优先级排序后依次出队。调度器内置了去重过滤器(DupeFilter),默认使用RFPDupeFilter基于请求指纹进行去重,避免重复爬取同一个URL。
- Downloader(下载器):负责发送HTTP/HTTPS请求并获取响应内容。它基于Twisted的异步HTTP客户端实现,可以同时维护数百个并发连接。下载器支持自动处理Cookie、重定向、压缩、认证等HTTP标准特性。
- Spider(爬虫):是开发者编写业务逻辑的地方。Spider从起始URL开始爬取,解析响应内容(HTML、JSON、XML等),提取结构化数据并封装为Item对象,同时还可以生成新的Request来跟进链接,实现深度爬取。
- Item Pipeline(管道):负责处理爬虫产出的Item。典型的处理操作包括数据清洗(去除空白、格式化)、验证(字段完整性检查)、去重(基于数据库或集合)和数据持久化(存入CSV、JSON、数据库)。Pipeline依次执行,按优先级排序。
中间件
Scrapy提供两种中间件类型:Downloader Middleware 在引擎和下载器之间,用于处理请求/响应(如代理切换、User-Agent轮换、自定义请求头);Spider Middleware 在引擎和爬虫之间,用于处理Spider的输入(Response)和输出(Item/Request)。
数据流图
Engine → Scheduler → Engine → Downloader Middlewares → Downloader → Downloader Middlewares → Engine → Spider Middlewares → Spider → Spider Middlewares → Engine → Item Pipeline
简化的数据流向:Spider产生Request → Engine调度 → Downloader下载 → Spider解析Response → Pipeline存储数据。
三、创建Scrapy项目
Scrapy通过命令行工具提供了一套完整的项目脚手架,可以帮助开发者快速创建标准化的项目结构。项目一旦创建,目录组织、配置文件、组件注册等都由框架统一管理。
创建项目
使用 startproject 命令创建新项目:
scrapy startproject tutorial
cd tutorial
项目目录结构
创建完成后会生成以下目录和文件:
tutorial/
scrapy.cfg # 项目配置文件
tutorial/ # Python模块包
__init__.py
items.py # Item定义文件
middlewares.py # 中间件定义
pipelines.py # Pipeline定义
settings.py # 项目设置
spiders/ # 爬虫目录
__init__.py
各个文件的作用:items.py 定义爬取的数据结构;pipelines.py 实现数据后处理逻辑;middlewares.py 编写自定义中间件;settings.py 集中管理所有配置项;spiders/ 目录存放所有爬虫类。
创建爬虫
进入项目目录后,使用 genspider 命令创建爬虫:
scrapy genspider quotes quotes.toscrape.com
此命令会在 spiders/ 目录下生成 quotes.py 文件,其中自动填充了爬虫名称和域名限制。生成的爬虫类继承自 scrapy.Spider,开发者只需填充 parse() 方法即可。
运行爬虫
使用 crawl 命令启动爬虫:
scrapy crawl quotes
如果需要将输出保存到文件,可以使用 -o 参数:
scrapy crawl quotes -o quotes.json
scrapy crawl quotes -o quotes.csv
四、Spider开发
Spider是Scrapy中最核心的组件,包含了爬虫的所有业务逻辑。Scrapy提供了多种内置Spider基类以满足不同场景的需求,最常用的是 scrapy.Spider。
Spider基本结构
一个最基本的Spider包含三个必须属性:name(爬虫唯一标识)、allowed_domains(允许爬取的域名白名单)、start_urls(起始URL列表),以及一个核心方法 parse()。
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
allowed_domains = ["quotes.toscrape.com"]
start_urls = ["https://quotes.toscrape.com/"]
def parse(self, response):
for quote in response.css("div.quote"):
yield {
"text": quote.css("span.text::text").get(),
"author": quote.css("small.author::text").get(),
}
next_page = response.css("li.next a::attr(href)").get()
if next_page:
yield response.follow(next_page, callback=self.parse)
parse()方法
parse() 是Spider的默认回调方法,接收 Response 对象作为参数。Response对象封装了服务器返回的所有信息,包括HTTP状态码、响应头和响应体。开发者在该方法中使用选择器提取所需数据,并通过 yield 关键字返回Item或Request。
注意:parse()是一个生成器函数,yield Item会自动进入Pipeline处理,yield Request会重置到引擎调度队列。这种设计使得Scrapy可以高效地处理大量数据,而不会因为单个页面解析耗时过长而阻塞其他请求。
跟进链接
网络爬虫的核心能力之一是自动发现和跟进链接。Scrapy通过 response.follow() 方法简化了这一过程。相比手动拼接URL,response.follow() 会自动处理相对路径和绝对路径的转换。如果需要传递额外参数到回调函数,可以使用 cb_kwargs 参数:
def parse_author(self, response, author_name):
yield {
"name": author_name,
"birthday": response.css(".author-born-date::text").get(),
}
在发起请求时传入参数:
yield response.follow(
author_url,
callback=self.parse_author,
cb_kwargs={"author_name": author_name}
)
对于需要更多控制的场景,也可以手动构造 scrapy.Request 对象,通过 meta 字典传递参数:
yield scrapy.Request(
url=author_url,
callback=self.parse_author,
meta={"author_name": author_name}
)
五、Item与Item Pipeline
当爬虫需要爬取结构化数据时,使用Item可以带来更好的类型安全和代码可读性。Pipeline则提供了集中化的数据后处理能力,与爬虫逻辑完全解耦。
Item类定义
在 items.py 中定义数据模型,每个字段使用 scrapy.Field() 声明:
import scrapy
class QuoteItem(scrapy.Item):
text = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()
crawl_time = scrapy.Field()
Item的行为类似于Python字典,支持键值存取、序列化和反序列化。在Spider中使用时,直接实例化后按字段赋值即可。
Pipeline处理流程
Pipeline是一个实现了 process_item() 方法的Python类。方法接收Item和Spider两个参数,必须返回Item对象(或抛出 DropItem 异常以丢弃无效数据)。Pipeline典型的应用包括:
- 数据清洗:去除字符串首尾空白、统一大小写、转换日期格式
- 数据验证:检查必填字段是否缺失、数值范围是否合理
- 数据去重:使用集合或数据库记录已处理的Item指纹
- 数据存储:写入CSV、JSON、MySQL、MongoDB等存储介质
Pipeline示例
class CleanPipeline:
def process_item(self, item, spider):
item["text"] = item["text"].strip()
item["author"] = item["author"].strip()
return item
class JsonPipeline:
def open_spider(self, spider):
self.file = open("output.json", "w", encoding="utf-8")
self.file.write("[\n")
def close_spider(self, spider):
self.file.write("\n]")
self.file.close()
def process_item(self, item, spider):
line = json.dumps(dict(item), ensure_ascii=False)
self.file.write(f" {line},\n")
return item
注册Pipeline并设置优先级
在 settings.py 中通过 ITEM_PIPELINES 字典注册Pipeline类,数字越小优先级越高:
ITEM_PIPELINES = {
"tutorial.pipelines.CleanPipeline": 300,
"tutorial.pipelines.JsonPipeline": 800,
}
多个Pipeline按照优先级从小到大的顺序依次执行,前一个Pipeline处理完的Item会传递给下一个。如果某个Pipeline抛出 DropItem 异常,后续Pipeline将不再处理该Item,从而实现数据过滤。
六、Scrapy选择器
Scrapy内置了强大的Selector模块,同时支持CSS选择器和XPath表达式,底层封装了lxml解析库,具有高性能和容错能力。选择器对象可以直接在Response上调用,非常便捷。
CSS选择器
CSS选择器语法简洁,适合提取具有class或id属性的元素:
# 选取class为quote的所有div
response.css("div.quote")
# 选取第一个匹配元素内的文本
response.css("span.text::text").get()
# 选取属性值
response.css("a::attr(href)").getall()
# 链式选取
response.css("div.quote").css("span.text::text").get()
XPath选择器
XPath功能更为强大,支持文本匹配、位置筛选、轴操作等高级功能:
# 选取所有span元素的文本
response.xpath("//span/text()").getall()
# 按文本内容筛选
response.xpath("//small[contains(text(), 'Author')]/text()").get()
# 选取第二个元素的文本
response.xpath("(//div[@class='quote'])[2]/span/text()").get()
# 使用@选取属性
response.xpath("//a/@href").getall()
提取方法
Scrapy提供了多种提取方法以适应不同的使用场景:
.get():返回第一个匹配结果,没有匹配返回None
.getall():返回所有匹配结果的列表,不会为None
.extract():等同于getall(),旧版API
.extract_first():等同于get(),旧版API
.re(regex):使用正则表达式提取匹配内容
.re_first(regex):返回第一个正则匹配结果
选择器嵌套
选择器支持在结果集上继续选择,这在处理复杂嵌套结构时非常有用:
quotes = response.css("div.quote")
for quote in quotes:
text = quote.css("span.text::text").get()
author = quote.xpath(".//small[@class='author']/text()").get()
tags = quote.css("a.tag::text").getall()
print(f"{author}: {text} ({tags})")
注意XPath嵌套时,前面的 . 表示相对路径,从当前节点继续搜索,如果不加 . 则从文档根节点重新开始搜索,可能得到预期之外的结果。
七、Settings配置
Scrapy的绝大多数行为都可以通过 settings.py 文件配置。合理的配置能够显著提升爬虫的稳定性、效率和可维护性。以下是几个最常用的配置项:
| 配置项 | 说明 | 默认值 | 建议值 |
| ROBOTSTXT_OBEY | 是否遵守robots.txt协议 | True | 开发时设为False |
| CONCURRENT_REQUESTS | 最大并发请求数 | 16 | 8-32 |
| DOWNLOAD_DELAY | 请求间隔时间(秒) | 0 | 0.5-3.0 |
| COOKIES_ENABLED | 是否启用Cookie | True | 一般保持默认 |
| DEFAULT_REQUEST_HEADERS | 默认请求头 | {} | 模拟浏览器 |
| USER_AGENT | 用户代理字符串 | Scrapy/版本号 | 真实浏览器UA |
| ITEM_PIPELINES | Pipeline注册表 | {} | 按需注册 |
| DOWNLOAD_TIMEOUT | 下载超时时间(秒) | 180 | 10-30 |
| RETRY_ENABLED | 是否自动重试 | True | 保持默认 |
| DEPTH_LIMIT | 爬取最大深度 | 0(不限) | 按需设置 |
一个典型的settings.py配置示例如下:
ROBOTSTXT_OBEY = False
CONCURRENT_REQUESTS = 16
DOWNLOAD_DELAY = 1.0
COOKIES_ENABLED = False
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
DEFAULT_REQUEST_HEADERS = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
}
ITEM_PIPELINES = {
"tutorial.pipelines.CleanPipeline": 300,
}
最佳实践:在开发调试阶段建议将ROBOTSTXT_OBEY设为False,DOWNLOAD_DELAY设为0,便于快速测试。上线部署时务必根据目标网站的承受能力设置合理的延时和并发数,避免对目标服务器造成压力。同时建议开启DOWNLOAD_TIMEOUT和限制重试次数,防止异常请求长时间占用资源。