Downloader Middleware即下载中间件,它是处于Scrapy的Request和Response之间的处理模块。咱们首先来看看它的架构,以下图所示。
html
Scheduler从队列中拿出一个Request发送给Downloader执行下载,这个过程会通过Downloader Middleware的处理。另外,当Downloader将Request下载完成获得Response返回给Spider时会再次通过Downloader Middleware处理。git
也就是说,Downloader Middleware在整个架构中起做用的位置是如下两个:github
在Scheduler调度出队列的Request发送给Doanloader下载以前,也就是咱们能够在Request执行下载以前对其进行修改。ajax
在下载后生成的Response发送给Spider以前,也就是咱们能够在生成Resposne被Spider解析以前对其进行修改。bash
Downloader Middleware的功能十分强大,修改User-Agent、处理重定向、设置代理、失败重试、设置Cookies等功能都须要借助它来实现。下面咱们来了解一下Downloader Middleware的详细用法。cookie
须要说明的是,Scrapy其实已经提供了许多Downloader Middleware,好比负责失败重试、自动重定向等功能的Middleware,它们被DOWNLOADER_MIDDLEWARES_BASE
变量所定义。
架构
DOWNLOADER_MIDDLEWARES_BASE
变量的内容以下所示:app
{
'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的名称,键值表明了调用的优先级,优先级是一个数字,数字越小表明越靠近Scrapy引擎,数字越大表明越靠近Downloader,数字小的Downloader Middleware会被优先调用。dom
若是本身定义的Downloader Middleware要添加到项目里,DOWNLOADER_MIDDLEWARES_BASE
变量不能直接修改。Scrapy提供了另一个设置变量DOWNLOADER_MIDDLEWARES
,咱们直接修改这个变量就能够添加本身定义的Downloader Middleware,以及禁用DOWNLOADER_MIDDLEWARES_BASE
里面定义的Downloader Middleware。下面咱们具体来看看Downloader Middleware的使用方法。scrapy
Scrapy内置的Downloader Middleware为Scrapy提供了基础的功能,但在项目实战中咱们每每须要单独定义Downloader Middleware。不用担忧,这个过程很是简单,咱们只须要实现某几个方法便可。
每一个Downloader Middleware都定义了一个或多个方法的类,核心的方法有以下三个。
process_request(request, spider)
。
process_response(request, response, spider)
。
process_exception(request, exception, spider)
。
咱们只须要实现至少一个方法,就能够定义一个Downloader Middleware。下面咱们来看看这三个方法的详细用法。
Request被Scrapy引擎调度给Downloader以前,process_request()
方法就会被调用,也就是在Request从队列里调度出来到Downloader下载执行以前,咱们均可以用process_request()
方法对Request进行处理。方法的返回值必须为None、Response对象、Request对象之一,或者抛出IgnoreRequest
异常。
process_request()
方法的参数有以下两个。
request
,是Request对象,即被处理的Request。
spider
,是Spdier对象,即此Request对应的Spider。
返回类型不一样,产生的效果也不一样。下面概括一下不一样的返回状况。
当返回是None时,Scrapy将继续处理该Request,接着执行其余Downloader Middleware的process_request()
方法,一直到Downloader把Request执行后获得Response才结束。这个过程其实就是修改Request的过程,不一样的Downloader Middleware按照设置的优先级顺序依次对Request进行修改,最后送至Downloader执行。
当返回为Response对象时,更低优先级的Downloader Middleware的process_request()
和process_exception()
方法就不会被继续调用,每一个Downloader Middleware的process_response()
方法转而被依次调用。调用完毕以后,直接将Response对象发送给Spider来处理。
当返回为Request对象时,更低优先级的Downloader Middleware的process_request()
方法会中止执行。这个Request会从新放到调度队列里,其实它就是一个全新的Request,等待被调度。若是被Scheduler调度了,那么全部的Downloader Middleware的process_request()
方法会被从新按照顺序执行。
若是IgnoreRequest
异常抛出,则全部的Downloader Middleware的process_exception()
方法会依次执行。若是没有一个方法处理这个异常,那么Request的errorback()
方法就会回调。若是该异常尚未被处理,那么它便会被忽略。
Downloader执行Request下载以后,会获得对应的Response。Scrapy引擎便会将Response发送给Spider进行解析。在发送以前,咱们均可以用process_response()
方法来对Response进行处理。方法的返回值必须为Request对象、Response对象之一,或者抛出IgnoreRequest异常。
process_response()
方法的参数有以下三个。
request
,是Request对象,即此Response对应的Request。
response
,是Response对象,即此被处理的Response。
spider
,是Spider对象,即此Response对应的Spider。
下面概括一下不一样的返回状况。
当返回为Request对象时,更低优先级的Downloader Middleware的process_response()
方法不会继续调用。该Request对象会从新放到调度队列里等待被调度,它至关于一个全新的Request。而后,该Request会被process_request()
方法顺次处理。
当返回为Response对象时,更低优先级的Downloader Middleware的process_response()
方法会继续调用,继续对该Response对象进行处理。
若是IgnoreRequest异常抛出,则Request的errorback()
方法会回调。若是该异常尚未被处理,那么它便会被忽略。
当Downloader或process_request()
方法抛出异常时,例如抛出IgnoreRequest
异常,process_exception()
方法就会被调用。方法的返回值必须为None、Response对象、Request对象之一。
process_exception()
方法的参数有以下三个。
request
,是Request对象,即产生异常的Request。
exception
,是Exception对象,即抛出的异常。
spdier
,是Spider对象,即Request对应的Spider。
下面概括一下不一样的返回值。
当返回为None时,更低优先级的Downloader Middleware的process_exception()
会被继续顺次调用,直到全部的方法都被调度完毕。
当返回为Response对象时,更低优先级的Downloader Middleware的process_exception()
方法再也不被继续调用,每一个Downloader Middleware的process_response()
方法转而被依次调用。
当返回为Request对象时,更低优先级的Downloader Middleware的process_exception()
也再也不被继续调用,该Request对象会从新放到调度队列里面等待被调度,它至关于一个全新的Request。而后,该Request又会被process_request()
方法顺次处理。
以上内容即是这三个方法的详细使用逻辑。在使用它们以前,请先对这三个方法的返回值的处理状况有一个清晰的认识。在自定义Downloader Middleware的时候,也必定要注意每一个方法的返回类型。
下面咱们用一个案例实战来加深一下对Downloader Middleware用法的理解。
新建一个项目,命令以下所示:
scrapy startproject scrapydownloadertest复制代码
新建了一个Scrapy项目,名为scrapydownloadertest。进入项目,新建一个Spider,命令以下所示:
scrapy genspider httpbin httpbin.org复制代码
新建了一个Spider,名为httpbin,源代码以下所示:
import scrapy
class HttpbinSpider(scrapy.Spider):
name = 'httpbin'
allowed_domains = ['httpbin.org']
start_urls = ['http://httpbin.org/']
def parse(self, response):
pass复制代码
接下来咱们修改start_urls
为:[http://httpbin.org/](http://httpbin.org/)
。随后将parse()
方法添加一行日志输出,将response
变量的text
属性输出出来,这样咱们即可以看到Scrapy发送的Request信息了。
修改Spider内容以下所示:
import scrapy
class HttpbinSpider(scrapy.Spider):
name = 'httpbin'
allowed_domains = ['httpbin.org']
start_urls = ['http://httpbin.org/get']
def parse(self, response):
self.logger.debug(response.text)复制代码
接下来运行此Spider,执行以下命令:
scrapy crawl httpbin复制代码
Scrapy运行结果包含Scrapy发送的Request信息,内容以下所示:
{
"args": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding": "gzip,deflate,br",
"Accept-Language": "en",
"Connection": "close",
"Host": "httpbin.org",
"User-Agent": "Scrapy/1.4.0 (+http://scrapy.org)"
},
"origin": "60.207.237.85",
"url": "http://httpbin.org/get"
}复制代码
咱们观察一下Headers,Scrapy发送的Request使用的User-Agent是Scrapy/1.4.0(+http://scrapy.org),这实际上是由Scrapy内置的`UserAgentMiddleware`设置的,`UserAgentMiddleware`的源码以下所示:
from scrapy import signals
class UserAgentMiddleware(object):
def __init__(self, user_agent='Scrapy'):
self.user_agent = user_agent
@classmethod
def from_crawler(cls, crawler):
o = cls(crawler.settings['USER_AGENT'])
crawler.signals.connect(o.spider_opened, signal=signals.spider_opened)
return o
def spider_opened(self, spider):
self.user_agent = getattr(spider, 'user_agent', self.user_agent)
def process_request(self, request, spider):
if self.user_agent:
request.headers.setdefault(b'User-Agent', self.user_agent)复制代码
在from_crawler()
方法中,首先尝试获取settings
里面USER_AGENT
,而后把USER_AGENT
传递给__init__()
方法进行初始化,其参数就是user_agent
。若是没有传递USER_AGENT
参数就默认设置为Scrapy字符串。咱们新建的项目没有设置USER_AGENT
,因此这里的user_agent
变量就是Scrapy。接下来,在process_request()
方法中,将user-agent
变量设置为headers
变量的一个属性,这样就成功设置了User-Agent。所以,User-Agent就是经过此Downloader Middleware的process_request()
方法设置的。
修改请求时的User-Agent能够有两种方式:一是修改settings
里面的USER_AGENT
变量;二是经过Downloader Middleware的process_request()
方法来修改。
第一种方法很是简单,咱们只须要在setting.py里面加一行USER_AGENT
的定义便可:
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'复制代码
通常推荐使用此方法来设置。可是若是想设置得更灵活,好比设置随机的User-Agent
,那就须要借助Downloader Middleware了。因此接下来咱们用Downloader Middleware实现一个随机User-Agent的设置。
在middlewares.py里面添加一个RandomUserAgentMiddleware
的类,以下所示:
import random
class RandomUserAgentMiddleware():
def __init__(self):
self.user_agents = [
'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)',
'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2',
'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101 Firefox/15.0.1'
]
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(self.user_agents)复制代码
咱们首先在类的__init__()
方法中定义了三个不一样的User-Agent,并用一个列表来表示。接下来实现了process_request()
方法,它有一个参数request
,咱们直接修改request
的属性便可。在这里咱们直接设置了request
变量的headers
属性的User-Agent,设置内容是随机选择的User-Agent,这样一个Downloader Middleware就写好了。
不过,要使之生效咱们还须要再去调用这个Downloader Middleware。在settings.py中,将DOWNLOADER_MIDDLEWARES
取消注释,并设置成以下内容:
DOWNLOADER_MIDDLEWARES = {
'scrapydownloadertest.middlewares.RandomUserAgentMiddleware': 543,
}复制代码
接下来咱们从新运行Spider,就能够看到User-Agent被成功修改成列表中所定义的随机的一个User-Agent了:
{
"args": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding": "gzip,deflate,br",
"Accept-Language": "en",
"Connection": "close",
"Host": "httpbin.org",
"User-Agent": "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)"
},
"origin": "60.207.237.85",
"url": "http://httpbin.org/get"
}复制代码
咱们就经过实现Downloader Middleware并利用process_request()
方法成功设置了随机的User-Agent。
另外,Downloader Middleware还有process_response()
方法。Downloader对Request执行下载以后会获得Response,随后Scrapy引擎会将Response发送回Spider进行处理。可是在Response被发送给Spider以前,咱们一样可使用process_response()
方法对Response进行处理。好比这里修改一下Response的状态码,在RandomUserAgentMiddleware
添加以下代码:
def process_response(self, request, response, spider):
response.status = 201
return response复制代码
咱们将response
变量的status
属性修改成201,随后将response
返回,这个被修改后的Response就会被发送到Spider。
咱们再在Spider里面输出修改后的状态码,在parse()
方法中添加以下的输出语句:
self.logger.debug('Status Code: ' + str(response.status))复制代码
从新运行以后,控制台输出了以下内容:
[httpbin] DEBUG: Status Code: 201复制代码
能够发现,Response的状态码成功修改了。
所以要想对Response进行后处理,就能够借助于process_response()
方法。
另外还有一个process_exception()
方法,它是用来处理异常的方法。若是须要异常处理的话,咱们能够调用此方法。不过这个方法的使用频率相对低一些,在此不用实例演示。
本节源代码为:https://github.com/Python3WebSpider/ScrapyDownloaderTest。
本节讲解了Downloader Middleware的基本用法。此组件很是重要,是作异常处理和反爬处理的核心。后面咱们会在实战中应用此组件来处理代理、Cookies等内容。