文章目录
Scrapy 是一个基于 Python 开发的爬虫框架,是当前 Python 爬虫生态中最流行的爬虫框架,该框架提供了非常多爬虫相关的基础组件,架构清晰,可拓展性极强。
之前大多是基于 requests 或 aiohttp 来实现爬虫的整个逻辑的,可以发现,在整个过程中,我们需要实现爬虫相关的所有操作,例如爬取逻辑,异常处理,数据解析,数据存储等,但其实这些步骤很多都是通用或者重复的。既然如此,我们可以将这些步骤的逻辑分离出来,把其中通用的功能做成一个个基础的组件。
在抽离处基础组件之后,每次爬虫只需要在这些组件基础上加上特定的逻辑就可以实现爬取的流程了,而不用再把爬虫中每个细小的流程都实现一遍。
Scrapy 框架几乎是 Python 爬虫学习和工作过程中必须掌握的框架
一、Scrapy 框架介绍

- Scrapy Engine(引擎): 负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。
- Item: 是一个抽象的数据结构,定义了爬取结果的数据结构,爬去的数据会被赋值成 Item 对象,每个 Item 就是一个类,类里面定义了爬取结果的数据字段,可以理解为它用来规定爬取数据的存储格式。
- Scheduler(调度器): 它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。
- Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,
- Spider(爬虫):它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器).
- Item Pipeline(管道):它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方。
- Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。
- Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)
1.1 数据流
详细可以看这篇文章:Scrapy 入门教程 | 菜鸟教程 (runoob.com),这里十分生动的表示了 Scrapy 个组件的交流:
- 启动爬虫项目时,Engine 根据要爬取的目标站点找到处理该站点的 Spider,Spider 会生成最初需要爬取的页面对应的一个或多个 Request,然后发给 Engine。
- Engine 从 Spider 中获取这些 Request,然后把它们交给 Scheduler 等待被调度
- Engine 向 Scheduler 索取下一个要处理的 Request,这时候 Scheduler 根据其调度逻辑选择合适的 Request 发送给 Engine
- Engine 将 Scheduler 发来的 Request 转发给 Downloader 进行下载执行,将 Request 发送给 Downloader 的过程会经由许多定义好的 Downloader Middlewares 的处理
- Downloader 将 Request 发送给目标服务器,得到对应的 Response,然后将其返回给 Engine。将 Response 返回 Engine 的过程同样会经由许多定义好的 Downloader Middlewares 的处理。
- Engine 从 Downloader 处接收到的 Response 里包含了爬取的目标站点的内容,Engine 会将此 Response 发送给对应的 Spider 进行处理,将 Response 发送给 Spider 的过程中会经由定义好的 Spider Middlewares 的处理
- Spider 处理 Response,解析 Response 的内容,这时候 Spider 会产生一个或多个爬取结果 Item 或者后续要爬取的目标页面对应的一个或多个 Request,然后再将这些 Item 或 Request 发送给 Engine 进行处理,将 Item 或 Request 发送给 Engine 的过程会经由定义好的 Spider Middlewares 的处理
- Engine 将 Spider 发回的一个或多个 Item 转发给定义好的 Item Pipelines 进行数据处理或存储的一系列操作,将 Spider 发回的一个或多个 Request 转发给 Scheduler 等待下一次被调度。
重复第2步到第8步,直到 Scheduler 中没有更多的 Request,这时候 Engine 会关闭 Spider,整个爬取过程结束。 从整体上来看,各个组件都只专注于一个功能,组件和组件之间的耦合度非常低,也非常容易扩展。再由 Engine 将各个组件组合起来,使得各个组件各司其职,互相配合,共同完成爬取工作。另外加上 Scrapy 对异步处理的支持,Scrapy 还可以最大限度地利用网络带宽,提高数据爬取和处理的效率。
1.2 项目结构
需要先安装 Scrapy 框架,可以直接使用 pip 安装
pip install scrapy
安装完毕后,可以使用命令行来创建一个爬虫项目,这里创建一个名为 news 的项目
scrapy startproject news
执行完毕后,当前目录下就会出现一个名为 news 的文件夹,该文件夹就对应一个 Scrapy 爬虫项目,接着进入 news 文件夹,然后创建一个名称为 sina 的 Spider,
# 进入news 文件夹
cd .\news
# 创建 Spider 名称为 sina 域名为 news.sina.com.cn
scrapy genspider sina news.sina.com.cn
最终会得到如下的一个文件结构

各个文件的功能描述如下:
- scrapy.cfg: Scrapy项目的配置文件,其中定义了项目的配置文件路径、部署信息等
- items.py: 定义了Item数据结构,所有Item的定义都可以放这里
- pipelines.py: 定义了Item Pipeline的实现,所有的Item Pipeline的实现都可以放在这里
- settings.py: 定义了项目的全局配置
- middlewares.py: 定义了Downloader Middlewares和Spider Middlewares的实现
- spiders: 里面包含了一个个 Spider 的实现,每个 Spider 都对应一个 Python 文件
1.3 Scrapy 入门
这里以 Scrapy 推荐的官方练习项目为例子进行爬取,抓取的目标站点为 https://quotes.toscrape.com/
创建一个项目名为 demo 的项目,spider 命名为 example,得到spider文件 example.py 如下
# example.py
import scrapy
class ExampleSpider(scrapy.Spider):
name = "example"
allowed_domains = ["quotes.toscrape.com"]
start_urls = ["https://quotes.toscrape.com/"]
def parse(self, response):
pass
name 是每个项目唯一的名字,用于区分不同的 Spider
allowed_domains 是允许爬取的域名,如果初始或者后续的请求链接不是这个域名下的,则会被过滤掉
start_urls 包含了 spider 在启动时爬取的 URL 列表,初始请求是由它来定义的
parse 是 Spider 的一个方法,在默认情况下,start_urls 里面的链接构成请求完成下载后得到一个 response,parse 方法就会调用,response 作为参数;
进入到 Items.py 文件,如下
import scrapy
class DemoItem(scrapy.Item):
# define the fields for your item here like:
text = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()
这里 Item 类似于一个字典,但是必须使用 scrapy.Field()
来定义;对于 Response 的解析,其接口如下所示:
- url:Request URL;
- status:Response 状态码,一般情况下请求成功状态码为200;
- headers:Response Headers,是一个字典,字段是一一对应的;
- body:Response Body,这个通常就是访问页面之后得到的源代码结果了,比如里面包含的是HTML或者JSON字符串,但注意其结果是 bytes 类型。与requests模块请求后得到的响应属性content类似;
- request:Response 对应的 Request 对象;
- certificate:是twisted.internet.ssl.Certifucate类型的对象,通常代表一个SSL证书对象;
- ip_address:是一个ipaddress.IPv4Address或IPv6Address类型的对象,代表服务器的IP地址;
- urljoin:是对URL的一个处理方法,可以传入当前页面的相对URL,该方法处理后返回的就是绝对URL,urljoin 其实使用的就是: from urllib.parse import urljoin 可以去看源码;
- follow/follow_all:是一个根据URL来生成后续Request的方法,和直接构造Request不同的是,该方法接收的url可以是相对URL,不必一定是绝对URL,因为follow方法中有做url拼接的操作;
- text: 同body属性,但结果是str类型;
- encoding: Response的编码,默认是utf-8;
- selector: 根据Response的内容构造而成的Selector对象,利用它我们可以进一步调用xpath、css等方法进行结果的提取;
- xpath()方法: 传入XPath进行内容提取,等同于调用selector的xpath方法;
- css()方法: 传入CSS选择器进行内容提取,等同于调用selector的css方法;
- json()方法: 可以直接将text属性转换为JSON对象;
与 requests 的 Response 主要的不同在于其不需要再导入 lxml 或者 bs4 来进行解析,里面自带有解析的工具;在了解如何解析 Response 之后,我们可以将 example.py 修正如下:
import scrapy
from ..items import DemoItem
class ExampleSpider(scrapy.Spider):
name = "example"
allowed_domains = ["quotes.toscrape.com"]
start_urls = ["https://quotes.toscrape.com/"]
def parse(self, response, **kwargs):
quotes = response.css(".quote")
for quote in quotes:
item = DemoItem()
item['text'] = quote.css(".text::text").extract_first("")
item['author'] = quote.css(".author::text").extract_first("")
item['tags'] = quote.css(".tags .tag::text").extract()
yield item
目前只获取到首页的内容,我们需要获取到下一页的内容,可以在当前页面中寻找信息构建下一个 Request,Request 的构造参数梳理如下:
- url: Request 的页面链接,即 Request URL。
- callback:Request 的回调方法,通常这个方法需要定义在 Spider 类里面,并且需要对应一个 response 参数,代表 Request 执行请求后得到的 Response 对象。如果这个 callback 参数不指定,默认会使用 Spider 类里面的 parse 方法。
- method:Request 的方法,默认是 GET,还可以设置为 POST、PUT、DELETE 等。
- meta:Request 请求携带的额外参数,利用 meta,我们可以指定任意处理参数,特定的参数经由 Scrapy 各个组件的处理,可以得到不同的效果。另外,meta 还可以用来向回调方法传递信息。
- body:Request 的内容,即 Request Body,往往 Request Body 对应的是 POST 请求,我们可以使用 FormRequest 或 JsonRequest 更方便地实现 POST 请求。
- headers:Request Headers,是字典形式。
- cookies:Request 携带的 Cookies,可以是字典或列表形式。
- encoding:Request 的编码,默认是 utf-8。
- prority:Request 优先级,默认是0,这个优先级是给 Scheduler 做 Request 调度使用的,数值越大,就越被优先调度并执行。
- dont_filter:Request 不去重,Scrapy 默认会根据 Request 的信息进行去重,使得在爬取过程中不会出现重复的请求,设置为 True 代表这个 Request 会被忽略去重操作,默认是 False。
- errback:错误处理方法,如果在请求过程中出现了错误,这个方法就会被调用。
- flags:请求的标志,可以用于记录类似的处理。
- cb_kwargs:回调方法的额外参数,可以作为字典传递。
Scrapy 还专门为 POST 请求提供了两个类 —— FormRequest 和 JsonRequest,它们都是 Request 类的子类,我们可以利用 FormRequest 的 formdata 参数传递表单内容,利用 JsonRequest 的 json 参数传递 JSON 内容,其他的参数和 Request 基本是一致的。
第一个 JsonRequest,我们可以观察到页面返回结果的 json 字段就是我们所请求时添加的 data 内容,这说明实际上是发送了 Content-Type 为 application/json 的 POST 请求,这种对应的就是发送 JSON 数据。第二个 FormRequest,我们可以观察到页面返回结果的 form 字段就是我们请求时添加的 data 内容,这说明实际上是发送了 Content-Type 为 application/x-www-form-urlencoded 的 POST 请求,这种对应的就是表单提交。这两种 POST 请求的发送方式我们需要区分清楚,并根据服务器的实际需要进行选择。
example.py 修正如下:
import scrapy
from ..items import DemoItem
class ExampleSpider(scrapy.Spider):
name = "example"
allowed_domains = ["quotes.toscrape.com"]
start_urls = ["https://quotes.toscrape.com/"]
def parse(self, response, **kwargs):
quotes = response.css(".quote")
for quote in quotes:
item = DemoItem()
item['text'] = quote.css(".text::text").extract_first("")
item['author'] = quote.css(".author::text").extract_first("")
item['tags'] = quote.css(".tags .tag::text").extract()
yield item
# 获取下一页,然后构造请求
next = response.css(".pager .next a::attr(href)").extract_first()
url = response.urljoin(next)
# 构造请求
yield scrapy.Request(url=url, callback=self.parse)
运行项目
scrapy crawl example
在运行完 Scrapy 后,只能在控制台上看到结果,需要保存数据,有两种方式:
其一是使用命令行,直接输出格式文件,例如 json, csv, xmlk, pickle, marshal 等等,完成这一任务不需要任何额外的代码,Scrapy 提供的 Feed Exports 可以轻松将抓取到的结果输出
# 保存为json
scrapy crawl example -o example.json
# 保存为一行json
scrapy crawl example -o example.jl # 或
scrapy crawl example -o example.jsonlines
# 保存为 csv
scrapy crawl example -o example.csv
# 保存为 xml
scrapy crawl example -o example.xml
# 保存为 pickle
scrapy crawl example -o example.pickle
# 保存为 marshal
scrapy crawl example -o example.marshal

其二是使用 Item Pipeline,如果要进行更复杂的操作,如将结果保存到数据库之中或者 对 Item 进行筛选操作;Item Pipeline 为项目管道,当 Item 生成后,它会自动被送到 Item Pipeline 处进行处理,可以使用 Item Pipeline 来做如下操作:
- 清洗 HTML 数据
- 验证爬取数据,检测爬取字段
- 查重并丢弃重复内容
- 将爬取结果保存到数据库
Pipeline 管道的基本类模版如下:
class XXXXPipeline(object):
def __init__(self, a, b):
self.a = a
self.b = b
def process_item(self, item, spider):
"""必须有!为每个项管道组件调用此方法"""
pass
@classmethod
def from_crawler(cls, crawler):
"""如果存在,则调用此类方法以从Crawler创建管道实例。它必须返回管道的新实例。
Crawler对象提供对所有Scrapy核心组件(如setting和signal)的访问;
它是管道访问它们并将其功能挂钩到Scrapy的一种方式。类似于初始化a和b"""
return cls(
a=crawler.settings.get("a"),
b=crawler.settings.get("b"),
)
def open_spider(self, spider):
"""如果存在,这个方法是在spider打开时调用的。"""
pass
def close_spider(self, spider):
"""如果存在,这个方法是在spider关闭时调用的。"""
pass
在这里我们可以添加两个 Pipeline,首先是文本处理的 Pipeline ,还有存储数据库的 Pipeline;
from scrapy.exceptions import DropItem
class TextPipeline(object):
def __init__(self):
self.limit = 50
def process_item(self, item, spider):
if item["text"]:
if len(item["text"]) > self.limit:
item["text"] = item["text"][:self.limit].rstrip() + "..."
return item
else:
return DropItem("Missing Text")
class MongoPipeline(object):
def __init__(self, connection_string, database):
self.connection_string = connection_string
self.database = database
@classmethod
def from_crawler(cls, crawler):
return cls(
connection_string=crawler.settings.get('MONGODB_CONNECTION_STRING'),
database=crawler.settings.get('MONGODB_DATABASE')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.connection_string)
self.db = self.client[self.database]
def process_item(self, item, spider):
name = item.__class__.__name__
self.db[name].insert_one(dict(item))
return item
def close_spider(self, spider):
self.client.close()
处理完毕后,我们还需要进入到 settings.py 中配置文件,第一个是 Mongo数据库的配置,由于 MongoPipeline 是使用 from_crawler 来进行初始化的,所以 settings.py 中需要有 MONGODB_CONNECTION_STRING
,MONGODB_DATABASE
这两个字段;其次 Pipeline 有一个先后顺序,键值越小越优先执行,修改 settings.py 内容如下:
# Crawl responsibly by identifying yourself (and your website) on the user-agent
# USER_AGENT = "demo (+http://www.yourdomain.com)"
# Obey robots.txt rules
ROBOTSTXT_OBEY = True
ITEM_PIPELINES = {
"爬虫项目名.pipelines.TextPipeline": 200,
"爬虫项目名.pipelines.MongoPipeline": 300,
}
MONGODB_CONNECTION_STRING = "localhost"
MONGODB_DATABASE = "数据库名"
到这里就处理完毕了!开启爬虫如下:
scrapy crawl example
二、Selector 解析器
在Python3网络爬虫开发实战(3)网页数据的解析提取_etree beautifulsoup parsel-CSDN博客介绍过 Parsel 解析器,parsel 是 Python 最流行的爬虫框架 Scrapy 的底层支持;
而 Selector 在使用上和 Parsel 有一点点区别,那就是原来的 get() 和 getall() 变成了 extract_first() 和 extract();同时,Selector 是可以单独使用的
2.1 XPath 和 CSS 选择器
from scrapy import Selector
html = ''
selector = Selector(text=html)
# css
items = selector.css('css选择器')
# xpath
items = selector.xpath('xpath选择器')
2.2 信息提取
- extract_first:从 selectorlist 对象中提取第一个 Selector 对象,然后输出其中的结果
- extract:从 selectorlist 对象中提取所有的 Selector 对象,然后以列表的形式输出其中的结果
# 提取文本
selector.css('css选择器::text()').extract_first("默认值")
selector.css('css选择器::text()').extract("默认值")
selector.xpath('xpath//text()').extract_first("默认值")
selector.xpath('xpath//text()').extract("默认值")
# 提取属性
selector.css('css选择器::attr(name)').extract_first("默认值")
selector.css('css选择器::attr(href)').extract("默认值")
selector.xpath('xpath/@name()').extract_first("默认值")
selector.xpath('xpath/@href()').extract("默认值")
2.3 正则提取
- 如果选择器中是属性或者文本,那么 re 对属性或者文本进行匹配
- 如果选择器中不是属性和文本,那么 re 对该节点的 html 字符进行匹配
from parsel import Selector
html = ''
selector = Selector(text=html)
result = selector.css('css选择器').re('a.*')
result = selector.xpath('xpath').re('a.*')
result = selector.css('css选择器').re_first('a.*')
result = selector.xpath('xpath').re_first('a.*')
三、Spider 的使用
在 Scrapy 中,网站的链接配置,抓取逻辑,解析逻辑其实都是在 Spider 中配置的,在前一节的实例中,我们发现抓取逻辑也是在 Spider 中完成的。
3.1 Spider 运行流程
Spider 定义了如何爬取某个网站的流程和解析方式,就是做了以下两件事:
- 定义爬取网站的动作
- 分析爬取下来的网页
对于 Spider 类来说,整个爬取循环如下:
- 以初始的 URL 初始化 Request 并设置回调方法,当该 Request 成功请求并返回时,将生成 Response 并将其作为参数传给该回调方法
- 在回调方法内分析返回的网页内容。返回结果可以有两种形式,一种是将解析到的有效结果返回字典或 Item 对象,下一步可直接保存或者经过处理后保存,另一种解析的下一个(如下下一页)链接,可以利用此链接构造 Request 并设置新的回调方法,返回 Request;
- 如果返回的是字典或者 Item 对象,可通过 Feed Exports 等形式存入文件,如果设置了 Pipeline,可以经由 Pipeline 处理(如过滤,修正等)并保存;
- 如果返回的是 Request,那么 Request 执行成功得到 Response 之后会再次传递给 Request 中定义的回调方法,可以再次使用选择器来分析新得到的网页内容,并根据分析的数据生成 Item;
循环进行以上几步,便完成了站点的爬取;
3.2 Spider 类分析
参考文档:Spiders - Scrapy 2.11.2文档 — Spiders — Scrapy 2.11.2 documentation
我们定义的 Spider 继承自 scrapy.Spider 类,这个类是最基本的 Spider 类,其他的 Spider 必须继承这个类;
这个类有一些基础的属性,如下:
- name:爬虫名称,是定义 Spider 名字的字符串,Spider 的名字定义了 Scrapy 如何定位并初始化 Spider,所以它必须是唯一的。 name 是 Spider 最重要的属性,而且是必须的;
- allowed_domains:允许爬取的域名,是一个可选的配置,不在此范围的链接不会被跟进爬取;
- start_urls:起始 URL 列表,当我们没有实现 start_requests 方法的,默认会从这个列表开始抓取;
- custom_settings:一个字典,是专属于本 Spider 的配置,此设置会覆盖项目全局的设置,而且此设置必须在初始化前被更新,所以它必须定义成类变量;Settings — Scrapy 2.11.2 documentation
- crawler:此属性是由 from_crawler 方法设置的,代表的是本 Spider 类对应的 Crawler 对象,Crawler 对象中包含了很多的项目组件,利用它可以获取一些项目的基本配置信息,常见的就是获取项目的设置信息,即 Settings;Core API — Scrapy 2.11.2 documentation
- settings:一个 Settings 对象,利用它我们可以直接获取项目的全局设置变量;Settings — Scrapy 2.11.2 documentation
还有一些基础的,主要的方法,如下:
- start_requests:此方法用于生成初始请求,它必须返回一个可迭代对象,此方法会默认使用 start_urls 里面的每个 URL 来构造 Request,而且 Request 是 GET 请求方式。如果我们想在启动的时候以 POST 方式访问某个站点,可以直接重写这个方法;
- parse:当 Response 没有指定回调方法时,该方法会默认被调用,它负责处理 Response,并从中提取想要的数据和下一步的请求,然后返回,该方法需要返回一个包含 Request 或 Item 的可迭代对象;
- closed:当 Spider 关闭时,该方法被调用,这里一般会定义释放资源的一些操作;
3.3 Request
Requests and Responses — Scrapy 2.11.2 documentation
在 Request 中,Request 对象实质上指的就是 scrapy.http.Request 的一个实例,它包含了 HTTP 请求的基本信息,用这个 Request 类可以构造 Request 对象发送 HTTP 请求,它会被 Engine 交给 Downloader 进行处理执行,返回一个 Response 对象;
scrapy.Requset(**kwargs)
scrapy.http.Requset(**kwargs)
# Content-Type 为 application/json
scrapy.JsonRequest(**kwargs)
scrapy.http.JsonRequest(**kwargs)
# Content-Type 为 application/x-www-form-urlencoded
scrapy.FormRequest(**kwargs)
scrapy.http.FormRequest(**kwargs)
Request 类的构造参数如下:
- url:Request 的页面链接,即 Request URL;
- callback:Request 的回调方法,通常这个方法需要定义在 Spider 类里面,并且需要对应一个 response 参数,代表 Request 执行请求后得到的 Response 对象,如果这个 callback 参数不指定,默认会使用 Spider 类里面的 parse 方法;
- method:Request 的方法,默认是 GET,还可以设置为 POST,PUT,DELETE 等;
- meta:Request 请求携带的额外参数,利用 meta ,我们可以指定任意处理参数,特定的参数经由 Scrapy 各个组件的处理,可以得到不同的效果,另外,meta 还可以用来向回调方法传递信息;
- body:Request 的内容,即 Request Body,往往 Request Body 对应的是 POST 请求,我们可以使用 FormRequest 或 JsonRequest 更方便地实现 POST 请求;
- headers:Request Header,是字典形式;
- cookies:Request 携带的 Cookie,可以是字典或者列表形式;
- encoding:Request 的编码,默认是 UTF-8
- prority:Request 优先级,默认是 0 ,这个优先级是给 Scheduler 做 Request 调度使用的,数值越大,就粤北优先调用执行;
- dont_filter :Request 不去重,Scrapy 默认会根据 Request 的信息进行去重,使得在爬取过程中不会出现重复请求,设置为 True 代表这个 Request 会被忽略去重操作,默认为 False;
- errback:错误处理方法,如果在请求过程中出现了错误,这个方法就会被调用;
- flags:请求的标志,可以用于记录类似的处理;
- cb_kwargs:回调方法的额外参数,可以作为字典传递;
值得注意的是,meta 参数是一个十分有用而且易扩展的参数,它可以以字典的形式传递,包含的信息不受限制,所以很多 Scrapy 的插件会基于 meta 参数做一些特殊处理,在默认情况下,Scrapy 就预留了一些特殊的 key 作为特殊处理;

Scrapy 还专门为 POST 请求提供了两个类 —— FormRequest 和 JsonRequest,它们都是 Request 类的子类,我们可以利用 FormRequest 的 formdata 参数传递表单内容,利用 JsonRequest 的 json 参数传递 JSON 内容,其他的参数和 Request 基本是一致的。
第一个 JsonRequest,我们可以观察到页面返回结果的 json 字段就是我们所请求时添加的 data 内容,这说明实际上是发送了 Content-Type 为 application/json 的 POST 请求,这种对应的就是发送 JSON 数据。第二个 FormRequest,我们可以观察到页面返回结果的 form 字段就是我们请求时添加的 data 内容,这说明实际上是发送了 Content-Type 为 application/x-www-form-urlencoded 的 POST 请求,这种对应的就是表单提交。这两种 POST 请求的发送方式我们需要区分清楚,并根据服务器的实际需要进行选择。
3.4 Response
Request 由 Downloader 执行之后,得到的就是 Response 结果了,它代表的是 HTTP 请求得到的响应结果,同样地我们可以梳理一下其可用的属性和方法,以便做解析处理使用
- url:Request URL;
- request:Response 对应的 Request 对象;
- status:Response 状态码;
- headers:Response Header,响应头;是一个字典,字段是一一对应的;
- body:Response Body,这个通常就是访问页面之后得到的源码结果了,比如里面包含的是 HTML 或者 JSON 字符串,但注意其结果是 bytes 类型;
- certificate:是 twisted.internet.ssl.Certificate 类型的对象,通常代表一个 SSL 证书对象;
- ip_address,是一个 ipaddress.IPv4Address 或 ipaddress.IPv6Address 类型的对象,代表服务器的 IP 地址;
- urljoin:是对 URL 的一个处理方法,可以传入当前页面的相对 URL,该方法处理后返回的就是绝对 URL;
- follow/follow_all:是一个根据 URL 来生成后续 Request 的方法,和直接构造 Request 不同的是,该方法接受的 url 可以是相对 URL,不必一定是绝对 URL;
另外,Response 还有几个常用的子类,如 TextResponse 和 HtmlResponse;HtmlResponse 又是 TextResponse 的子类,实际上回调方法接收的 response 参数就是一个 HtmlResponse 对象,它还有几个常用的方法或属性。
- text:同 body 属性,但结果是 str 类型;
- encoding:Response 的编码,默认是 utf-8;
- selector:根据 Response 的内容构造而成的 Selector 对象;
- xpath/css :等同于调用 selector.xpath/css 方法;
- json:可以将 text 属性转化为 JSON 对象;
四、Download Middleware 的使用
Downloader Middleware 是处于 Scrapy 的 Engine 和 Downloader 之间的处理模块。Engine 把 Scheduler 获取的 Request 发送给 Downloader 的过程中,以及 Downloader 把 Response 发送回 Engine 的过程中,Request 和 Response 都会经过 Downloader Middleware 的处理;也就是说 Downloader Middleware 在整个架构中起作用的位置是以下两个:
- Engine 从 Scheduler 获取 Request 发送给 Downloader Middleware,在 Request 被 Engine 发送给 Downloader Middleware 执行下载之前,Downloader Middleware 可以对 Request 进行修改;
- Downloader 执行 Request 后生成 Response,在 Response 被 Engine 发送给 Spider 之前,Downloader Middleware 可以对 Response 进行修改;
Downloader Middleware 在整个爬虫执行过程中能起到非常重要的作用,功能十分强大,修改 User-Agent,处理重定向,设置代理,失败重试,设置 Cookie 等功能都需要借助它来实现;
需要说明的是,Scrapy 其实已经提供了许多 Downloader Middleware,比如负责失败重试、自动重定向等功能的 Middleware,它们被 DOWNLOADER_MIDDLEWARES_BASE 变量所定义。 DOWNLOADER_MIDDLEWARES_BASE 变量的内容如下所示:
{
'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
}
字典的键名是 Scrapy 内置的 Downloader Middleware 的名称,键值代表了调用的优先级,优先级是一个数字,数字越小代表越靠近Engine,数字越大代表越靠近 Downloader 。默认情况下,Scrapy 已经为我们开启了 DOWNLOADER_MIDDLEWARES_BASE 所定义的 Downloader Middleware,比如 RetryMiddleware 带有自动重试功能,RedirectMiddleware 带有自动处理重定向功能,这些功能默认都是开启的。
Downloader Middleware 固定内部代码如下:
class ScrapyDemoDownloaderMiddleware:
# Not all methods need to be defined. If a method is not defined,
# scrapy acts as if the downloader middleware does not modify the
# passed objects.
@classmethod
def from_crawler(cls, crawler):
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s
def process_request(self, request, spider):
# Called for each request that goes through the downloader
# middleware.
# Must either:
# - return None: continue processing this request
# - or return a Response object
# - or return a Request object
# - or raise IgnoreRequest: process_exception() methods of
# installed downloader middleware will be called
return None
def process_response(self, request, response, spider):
# Called with the response returned from the downloader.
# Must either;
# - return a Response object
# - return a Request object
# - or raise IgnoreRequest
return response
def process_exception(self, request, exception, spider):
# Called when a download handler or a process_request()
# (from other downloader middleware) raises an exception.
# Must either:
# - return None: continue processing this exception
# - return a Response object: stops process_exception() chain
# - return a Request object: stops process_exception() chain
pass
def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)
每个 Downloader Middleware 都可以通过定义 process_request 和 process_reponse 方法来分别处理 Request 和 Response ,被开启的 Downloader Middleware 的 process_request 方法和 process_response 方法会根据优先级顺序调用。
process_request:由于Request是从Engine发送给Downloader的,并且优先级数字越小的Downloader Middleware越靠近Engine,所以优先级数字越小的Downloader Middleware的process_request方法越先被调用。
process_response:process_response方法则相反,由于Response是由Downloader发送给Engine的,优先级数字越大的Downloader Middleware越靠近Downloader,所以优先级数字越大的Downloader Middleware的process_response越先被调用。
如果我们想将自定义的Downloader Middleware添加到项目中,不要直接修改DOWNLOADER_MIDDLEWARES_BASE变量,Scrapy提供了另外一个设置变量DOWNLOADER_MIDDLEWARES,我们直接修改这个变量就可以添加自己定义的Downloader Middleware,以及禁用DOWNLOADER_MIDDLEWARES_BASE里面定义的Downloader Middleware了。
4.1 process_request(request, spider)
Request被Engine发送给Downloader之前,process_request方法就会被调用,也就是在Request从Scheduler里被调度出来发送到Downloader下载执行之前,我们都可以用process_request方法对Request进行处理。
参数
process_request方法的参数有两个。
- request:Request对象,即被处理的Request。
- spider:Spider对象,即此Request对应的Spider对象。
返回值
这个方法的返回值必须为None、Response对象、Request对象三者之一,或者抛出IgnoreRequest异常。返回类型不同,产生的效果也不同,下面归纳一下不同的返回情况。
- None:当返回的是None时,Scrapy将继续处理该Request,接着执行其他Downloader Middleware的process_request方法,一直到Downloader把Request执行得到Response才结束。这个过程其实就是修改Request的过程,不同的Downloader Middleware按照设置的优先级顺序依次对Request进行修改,最后送至Downloader执行。
- Response:当返回为Response对象时,更低优先级的Downloader Middleware的process_request和process_exception方法就不会被继续调用,每个Downloader Middleware的process_response方法转而被依次调用,调用完毕后,直接将Response对象发送给Spider处理。
- Request:当返回为Request对象时,更低优先级的Downloader Middleware的process_request方法会停止执行。这个Request会重新放到调度队列里,其实它就是一个全新的Request,等待被调度。如果Scheduler调度了,那么所有的Downloader Middleware的process_request方法会被重新按照顺序执行。
- IgnoreRequest:如果抛出IgnoreRequest异常,则所有的Downloader Middleware的process_exception方法会依次执行。如果没有一个方法处理这个异常,那么Request的errorback方法就会回调。如果该异常还没有被处理,那么它便会被忽略。
4.2 process_response(request, response, spider)
Downloader执行Request下载之后,会得到对应的Response。Engine便会将Response发送给Spider进行解析,在发送给Spider之前,我们都可以用process_response方法来对Response进行处理。
参数
process_response方法的参数有3个:
- request:Request对象,即此Response对应的Request。
- response:Response对象,即被处理的Response。
- spider:Spider对象,即此Response对应的Spider对象。
返回值
process_response方法的返回值必须为Request对象和Response对象两者之一。或者抛出IgnoreRequest异常。那么对不同的返回情况在下面做一下归纳。
- Request:当返回为Request对象时,更低优先级的Downloader Middleware的process_response方法不会继续调用,该Request对象会重新放到调度队列里等待被调度,相当于一个全新的Request。然后,该Request会被process_request方法顺次处理。
- Response:当返回为Response对象时,更低优先级的Downloader Middleware的process_response方法会继续被调用,对该Response对象进行处理。
- IgnoreRequest:当抛出IgnoreRequest异常时,Request的errorback方法会回调。如果该异常还没有被处理,那么它会被忽略。
4.3 process_exception(request, exception, spider)
当Downloader或process_request方法抛出异常时,例如抛出IgnoreRequest异常,process_exception方法就会被调用。
参数
process_exception方法的参数有3个。
- request:Request对象,即产生异常的Request。
- exception:Exception对象,即抛出的异常。
- spider:Spider对象,即Request对应的Spider。
返回值
方法的返回值必须为None、Response对象、Request对象三者之一。
- None:当返回值为None时,更低优先级的Downloader Middleware的process_exception会被继续顺次调用,直到所有的方法都被调用完毕。
- Response:当返回值为Response时,更低优先级的Downloader Middleware的process_exception不再被继续调用,每个Downloader Middleware的process_response方法转而被依次调用。
- Request:当返回为Request对象时,更低优先级的Downloader Middleware的process_exception也不再被继续调用,该Request对象会重新放到调度队列里面等待被调度,相当于一个全新的Request。然后,该Request又会被process_request方法顺次处理。
关于设置 header,设置代理,返回值等操作可以看:scrapy爬虫框架(四)Downloader Middleware的使用 - 乐之之 - 博客园 (cnblogs.com)
五、Spider Middleware 的使用
Spider Middleware 的作用:
- Downloader生成Reponse之后,Engine会将其发送给Spider进行解析,在Response发送给Spider之前,可以借助Spider Middleware对Response进行处理。
- Spider生成Request之后会被发送至Engine,然后Request会转发到Scheduler,在Request被发送给Engine之前,可以借助Spider Middleware对Request进行处理。
- Spider生成Item之后会被发送至Engine,然后Item会被转发到Item Pipeline,在Item被发送给Engine之前,可以借助Spider Middleware对Item进行处理。
Scrapy框架中其实已经提供了许多Spider Middleware,与Downloader Middleware类似,它们被SPIDER_MIDDLEWARES_BASE变量所定义;SPIDER_MIDDLEWARES_BASE变量的内容如下:
{
'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware":50,
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware':500,
'scrapy.spidermiddlewares.referer.RefererMiddleware':700,
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware': 800,
' scrapy.spidermiddlewares.depth.DepthMiddleware':900,
}
SPIDER_MIDDLEWARES_BASE里定义的Spider Middleware是默认生效的,如果我们要自定义Spider Middleware,可以和Downloader Middleware一样,创建Spider Middleware并将其加入SPIDER_MIDDLEWARES。直接修改这个变量就可以添加自己定义的Spider Middleware,以及禁用SPIDER_MIDDLEWARES_BASE里面定义的Spider Middleware。
这些Spider Middleware的调用优先级和Downloader Middleware也是类似的,数字越小的Spider Middleware是越靠近Engine的