
Scrapy框架【初识】
更新: 2025/2/24 字数: 0 字 时长: 0 分钟
Scrapy 是一个网络爬虫库,它允许用户以声明式的方式定义如何爬取网站数据,并提供了灵活高效的机制来处理爬取的数据。Scrapy 极大地提高了网络数据采集和效率和可靠性,它被广泛应用于各种领域,包括市场研究、竞争情报、数据聚合和搜索引擎索引等。
Scrapy 是一个纯 Python 实现的基于 Twisted 异步网络编程框架的爬虫框架,目前也是全世界开发爬虫最流行的框架,在招聘爬虫开发的岗位中,基本都会要求熟练使用 Scrapy 框架,因此我们需要好好钻研和掌握。回顾前面的学习,爬虫的工作流程可以总结为以下 5 个步骤:
- 设定抓取目标(种子页面 / 起始页面)并获取网页。
- 当下载页面出现错误时,按照指定的重试次数尝试重新下载页面。
- 通过正则表达式、XPath、CSS 等工具从下载页面中提取数据信息和链接信息。
- 将有用的数据信息进行持久化存储,以备后续的处理。
- 对链接信息进行进一步的处理,获取页面并重复上面的动作。
在 Scrapy 当中,我们可以理解为将上面的每一个步骤都单独写成了一个模块,每个模块只负责自己步骤的事情,这样模块之间的耦合程度低(相互影响小),可扩展性极强,可以灵活完成各种需求,我们只需要定制开发几个模块就可以轻松实现一个爬虫。
建议
所谓的“框架”,包含了两层含义:一种是“框”,含义就是限定范围,在使用框架时,我们所有的操作都必须符合框架的规则。另一种是“架”,含义就是基础功能,几乎所有的框架都是一个包含有基础功能的半成品,后续我们只需要在框架的基础功能继续新的开发就行了。
Scrapy 安装
首先我们来安装 Scrapy 框架,在命令行中执行如下命令:
pip install scrapy
成功安装了后,在命令行执行 scrapy
命令就会输出 Scrapy 版本和内置命令信息:
Scrapy 架构
Scrapy 架构由以下八大组件构成:
Engine
引擎,负责整个系统的数据流处理、信号传递、事务触发,是整个框架的核心。Spiders
爬虫,定义爬取的逻辑和网页解析规 ,主要负责解析响应并生成提取结果和新的请求。Spide Middlewares
爬虫中间件,位于Engine
和Spiders
之间的钩子框架,主要处理Spiders
输入的响应和输出的结果以及新的请求。Scheduler
调度器,负责接受Engine
发过来的请求,在Engine
再次请求的时候将请求提供给Engine
。Downloader
下载器,负责发送网络请求并下载返回网页内容。Downloader Middlewares
下载器中间件,位于Engine
和Downloader
之间的钩子框架,主要处理Engine
与Downloader
之间的请求及响应。Item
项目,定义数据的结构字段。Item Pipelne
项目管道,负责清洗、验证和存储由Spiders
从网页中提取的Item
项目。
Scrapy 数据流
Scrapy 的数据流总共有九步:
- 启动爬虫项目时,
Engine
根据爬取的站点找到处理该站点的Spider
,这时Spider
就会生成最初需要爬取的页面对应的一个或多个初始Requests
请求。 Engine
把从Spider
获取的这些Requests
都交给Scheduler
进行调度。Engine
向Scheduler
索取下一个要处理的Request
,Scheduler
根据其调度逻辑选择合适的Request
发送给Engine
。Engine
将Scheduler
发来的Request
转发给Downloader
,转发的过程中Request
会经由许多定义好的Downloader Middlewares
进行加工处理(添加请求头、添加代理等)。Downloader
将Request
发送给目标服务器,得到对应的Response
响应,然后将其返回给Engine
,这个返回的过程中Response
同样会经由许多定义好的Downloader Middlewares
进行加工处理(判断状态码、判断完整性)。Engine
从Downloader
接收到的Response
中包含了爬取的内容,Engine
将此Response
发送给对应的Spider
进行处理,发送的过程中Response
会经由许多定义好的Spider Middlewares
进行加工处理。Spider
对接收到Response
进行解析,返回从内容中提取的一个或多个的Item
数据以及一个或多个的Request
后续请求,然后将Item
、Request
发送给Engine
,发送的过程中Item
、Request
会经由许多定义好的Spider Middlewares
进行加工处理。Engine
将Spider
发送过来的Item
转发给定义好的Item Pipelne
进行数据处理或存储,将Request
转发给Scheduler
等待下一次被调度。- 重复第二步到第八步直到
Scheduler
中没有更多地Request
,这个时候Engine
就会关闭Spider
,整个爬取过程结束。
Scrapy 项目
创建初始项目
首先,在命令行中执行下面命令,在当前路径下创建一个名称为 test1
的 Scrapy 项目:
scrapy startproject test1(项目名称)
test1(存放整个项目的文件夹)
-test1(项目的根目录,即导包的基准目录)
-spiders(存放不同Spider文件的文件夹)
__init__.py
__init__.py
items.py(定义Item数据结构)
middlewares.py(定义Spider中间件、Downloader中间件)
pipelines.py(定义Item Pipeline管道)
settings.py(项目的全局配置)
scrapy.cfg(项目的配置文件路径、部署相关信息等内容)
生成爬虫文件
执行下面命令进入到项目中,创建一个名称为 quotes
的爬虫:
# 进入test1项目
cd test1
# 创建一个名称为quotes的爬虫,采集的网络域名为quotes.toscrape.com
scrapy genspider quotes quotes.toscrape.com
创建的爬虫文件会自动存放到 spiders 文件夹里,可以看到在 spiders 文件夹里面多出一个名称为 quotes.py
的爬虫文件。内容如下:
import scrapy
# QuotesSpider类通过命令建立,它继承自Scrapy提供的Spider类,即scrapy.Spider。
class QuotesSpider(scrapy.Spider):
# 爬虫名称,具有唯一性,用来区分不同的爬虫。
name = 'quotes'
# 域名过滤,用于过滤掉那些不在指定域名范围内的请求,若不需过滤可置为[]空列表(启动时自动生效)。
allowed_domains = ['quotes.toscrape.com']
# 起始链接,Spider启动时所爬取的URL列表(启动时自动生效)。
start_urls = ['http://quotes.toscrape.com/']
# 默认情况下,负责解析start_urls中链接返回的response响应,提取数据或者进一步生成要处理的请求。
def parse(self, response):
pass
补充说明一点,上面的 QuotesSpider
类是继承自 scrapy.Spider
父类,因此里的 parse
方法会重写父类里面的 parse
方法,但由于重写方法的参数不一致,PyCharm 觉得这样写有风险,因此在方法里面显示淡黄色。
为了保持一致,我们进入到 scrapy.Spider
父类中查看 parse
方法,发现它还有一个 **kwargs
参数:
我们将 **kwargs
参数添加到重写的 parse
方法就会发现黄色消失了:
建议
爬虫中 start_urls
起始链接是根据我们给定的域名参数自动生成的,如果和我们要抓取的起始页面链接不同,可以修改 start_urls
列表中 URL 的内容。
添加全局配置
在前面介绍过 settings.py
文件的作用,就是针对当前项目中的所有爬虫进行全局配置,即默认使用的配置。常见的配置参数如下:
# 爬虫并发数,默认16
CONCURRENT_REQUESTS = 8
# 爬虫请求头
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36'
# 下载超时时间(单位秒)
DOWNLOAD_TIMEOUT = 15
# 下载延迟时间(请求之间的时间间隔,单位秒)
DOWNLOAD_DELAY = 3
# 随机下载延迟,默认为True(开启)
RANDOMIZE_DOWNLOAD_DELAY = True
# Robot协议检查,默认为True(开启)
ROBOTSTXT_OBEY = False
# 自动重定向,默认为True(开启)
REDIRECT_ENABLED = True
# 当响应的状态码为403、404时,不会过滤(默认不会过滤状态码为200的响应)
HTTPERROR_ALLOWED_CODES = [403, 404]
# 当响应的状态码为500、503时,重新请求
RETRY_HTTP_CODES = [500, 503]
# 日志输出等级,低于当前等级的日志不输出(DEBUG、INFO、警告、ERROR、CRITICAL)
LOG_LEVEL = 'INFO'
建议
**当我们开启 RANDOMIZE_DOWNLOAD_DELAY
随机下载延迟后,Scrapy 将在 DOWNLOAD_DELAY
下载延迟的 0.5 到 1.5 倍的范围内随机选择一个值作为实际的下载延迟。**如果你设置了 DOWNLOAD_DELAY
为 2 秒,而且 RANDOMIZE_DOWNLOAD_DELAY
为 True
,那么实际的下载延迟将在 1 到 3 秒之间随机选择一个值。这种机制的目的是为了在平均下载延迟的基础上,引入一些随机性,以模拟更自然的网络请求行为,避免对服务器造成过于规律的负载。
重要
需要注意的是,settings.py
文件只会在爬虫启动的时候,进行一次全局配置,后续不在调用。所以针对爬虫通用的配置我们可以写入到该文件中,例如数据库的连接配置、Item 数据处理配置等。但一些相对灵活的参数,例如爬虫并发数、爬虫请求头、下载响应时间等,通过中间件或者爬虫局部配置来设置会更加符合采集情况。
定义数据字段
前面讲过 items.py
文件的作用是定义 Item 数据结构字段,创建 Item 需要继承 scrapy.Item
类,并定义类型为 scrapy.Field
的字段,也就是需要爬取的字段名称。代码如下:
import scrapy
# 定义三个需要采集的字段
class Test1Item(scrapy.Item):
# 文本内容,一个字符串
text = scrapy.Field()
# 作者,一个字符串
author = scrapy.Field()
# 标签,字符串组成的列表
tags = scrapy.Field()
重要
我们可以将 Test1Item
类中定义的 Item 数据字段看作一个内容为 {'text': None, 'author': None, 'tags': None}
的字典。如果要在字典中新增键,就需要在 Test1Item
类中新定义 Item 数据字段。另外就是,要保存的数据内容必须对应一个 Test1Item
类中定义的 Item 数据字段,这样做的目的就是要规范数据字段的定义以及数据的采集。
解析响应内容
定义好了数据字段,回到 quotes.py
爬虫文件中。前面我们讲过在 QuotesSpider
类中的 start_urls
起始链接就是爬虫启动后最先爬取的链接,然后 Scrapy 会将爬取到的结果包装为一个 Response 响应对象,里面不仅包含了 URL(响应链接)、Status(响应状态码)、Text(响应内容)等内容,甚至还可以直接调用 XPath、CSS 选择器进行数据提取。接着,Scrapy 会将 Response 响应对象赋值给 parse
方法中的 response
参数,这时 parse
方法就可以看作解析 start_urls
起始链接响应结果的方法。在 parse
方法中,我们可以直接对 response
参数包含的内容进行解析,提取出我们需要的 Item 数据,或者找出下一个请求的链接来生成请求。不过这里我们先解析 Response 响应的内容,给定义好的 Item 数据字段进行赋值并返回:
def parse(self, response):
# 这里用CSS选择器来提取内容
quotes = response.css('.quote')
# 根据网页代码标签来遍历
for quote in quotes:
# 实例化Item使用方法和字典类似
item = Test1Item()
# 给Item数据字段中的text文本内容赋值
item['text'] = quote.css('.text::text').extract_first()
# 给Item数据字段中的author作者赋值
item['author'] = quote.css('.author::text').extract_first()
# 给Item数据字段中的tags标签赋值
item['tags'] = quote.css('.tags .tag::text').extract()
# 最后将Item返回
yield item
注意
注意这里实例化的 Item
对象,它只能以 Test1Item
数据结构里面已经定义的 Item 数据字段为键进行赋值,假如想对 item['date']
进行赋值,由于在 Test1Item
数据结构里面并没有定义 date
字段,是会报 KeyError: 'Test1Item does not support field: date'
错误的。
构造后续请求
上面的代码中只解析了第一页的 Response 响应对象,如果要采集后面页码的内容,就需要构造后续的 Resquest 请求对象。构造 Resquest 请求对象需要用到 scrapy.Request
方法,里面有两个必须传递的参数:
url
后续的请求链接。callback
回调函数,当爬取url
请求链接获取到 Response 响应对象之后,Engine
会将 Response 响应对象作为实参传递给回调函数中的response
形参,进行后续的解析或生成下一轮请求。
# 获取下一页的href属性(例如:/page/2)
next = response.css('.pager .next a::attr("href")').extract_first()
# 将href属性构造为url链接(例如:http://quotes.toscrape.com/page/2)
url = response.urljoin(next)
# 通过url和callback变量构造一个新的Request,由于下一页的结构和当前解析页类似,因此仍然使用parse方法来解析后续的Response响应对象。
yield scrapy.Request(url=url, callback=self.parse)
重要
需要特别注意的是,在爬虫文件中,不管是返回 Item 数据对象,还是返回 Requset 请求对象,都是通过 yiled
关键字进行返回的。假如使用 return
关键字进行返回,就会造成返回一个对象就结束函数了。
运行爬虫项目
接下来,在爬虫项目所在的路径下,输入下面命令,即可运行爬虫:
# quotes就是爬虫的名字,即爬虫文件里面name变量
scrapy crawl quotes
保存结果文件
如果想将上面的结果保存成 JSON 文件,可以执行下面命令:
scrapy crawl quotes -o quotes.json # 所有item在一行
scrapy crawl quotes -o quotes.jsonlines # 一个item占一行
输出格式还支持很多种,例如 csv、xml、pickle、marsha
等,还支持 ftp、s3
等远程输出:
scrapy crawl quotes -o quotes.csv
scrapy crawl quotes -o quotes.xml
scrapy crawl quotes -o quotes.pickle
scrapy crawl quotes -o quotes.marshal
scrapy crawl quotes -o ftp://user:[email protected]/path/to/quotes.csv