下载器中间件(Downloader Middleware)
下载器中间件是介于Scrapy的request/response处理的钩子框架。是用于全局修改Scrapy request和response的一个轻量、底层的系统。
它处于 引擎(crawler.engine) 和 下载器(crawler.engine.download())之间的一层组件,支持多个下载中间件被加载运行。
我们要这个骚东西有何用?
首先不能否定的是,可以干。 1. 当引擎传递请求给下载器的过程中,下载中间件可以对请求进行处理(例如增加http header信息,增加proxy信息等); 2. 当下载器完成http请求,传递响应给引擎的过程中,下载中间件可以对响应进行处理(例如进行gzip的解压等)。 关于自定义中间件的意义: 1. 在process_request内,自定义下载不用scrapy的下载。 2. 对请求进行二次加工,例如: 设置请求头 设置cookie 添加代理:scrapy自带的代理组件 from scrapy.downloadermiddlewares.httpproxy import HttpProxyMiddleware from urllib.request import getproxies
激活下载器中间件:
要激活下载器中间件,将其加入到DOWNLOAD设置中。该设置是一个字典(dict),键为中间件类的路径,值为其中间件的顺序(order)。
看例子:在你的settings文件里面
DOWNLOADER_MIDDLEWARES = { ‘Baidu.middlewares.BaiduDownloaderMiddleware‘: 543, }
注意了宝宝们,downloader_middlewares设置会与Scrapy定义的downloader_middlewares_base设置合并(非覆盖),然后根据顺序(order)进行怕徐,最后得到启用中间件的有序列表:第一个中间件是最靠近引擎的,最后一个是最靠近下载器的。
附:
DOWNLOADER_MIDDLEWARES_BASE = { # Engine side ‘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, # Downloader side }
补充:
如果你想充分利用好你的中间件,请注意一下中间件顺序。
在setting中,可以自定义中间件,接受各种request、response、 exception消息 比如有的人想在请求超时时 做一些处理, 有的人想为request设置代理 DOWNLOADER_MIDDLEWARES = { ‘discountSpider.middlewares.ProcessMiddleware‘:90, ‘discountSpider.middlewares.ProxyMiddleware‘: 750, ‘scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware‘: 751, ‘discountSpider.middlewares.RandomUserAgent‘: 400, ‘scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware‘: None, } 然而,中间件的顺序是很重要的 如果是想截取process_request,则越前面越早收到通知,然后顺着向后依次通知,没什么意外的话是这样。 但是如果是截取process_response,获得request请求完成后,返回response的消息,如果是正常下载完成的话,是第一个中间件收到通知,然后顺序向后通知。 但是往往会发生很多意外,比如请求超时,比如请求被retry,那么是不会发送通知到第一个的,而是根据scrapy默认中间件的位置发送, 分配中间件的顺序请查看 DOWNLOADER_MIDDLEWARES_BASE 设置,而后根据您想要放置中间件的位置选择一个值。由于每个中间件执行不同的动作,您的中间件可能会依赖于之前(或者之后)执行的中间件,因此顺序是很重要的。 比如DOWNLOADER_MIDDLEWARES_BASE中 ‘scrapy.contrib.downloadermiddleware.retry.RetryMiddleware‘: 500, 如果你把自定义的中间件序号设为100,那么当下载器发生错误504 500错误,需要retry,则会将response状态为retry的消息通知后面中间件,你的中间件的process_response将无法收到消息,因为他是从500序号开始向后通知,所需你将自己的中间件序号改为500以后,则可以收到消息。 又比如 ‘scrapy.contrib.downloadermiddleware.downloadtimeout.DownloadTimeoutMiddleware‘: 350,如果你想收到下载页面超时的消息,请将中间件放到350以后,process_response才能收到消息。因为下载发生超时后,scrapy会直接找到scrapy.contrib.downloadermiddleware.downloadtimeout.DownloadTimeoutMiddleware模块的位置350,通知他,然后消息依次通知后面的比如400,500,550,700等位置的中间件。如果你的中间件位置在100之类的,那是收不到消息的。 参考链接:https://www.jianshu.com/p/3b7507e7fc65
关于如何分配中间件的顺序请查看 DOWNLOADER_MIDDLEWARES_BASE
设置,而后根据您想要放置中间件的位置选择一个值。由于每个中间件执行不同的动作,您的中间件可能会依赖于之前(或者之后)执行的中间件,因此顺序是很重要的。
如果您想禁止内置的(在 DOWNLOADER_MIDDLEWARES_BASE
中设置并默认启用的)中间件,您必须在项目的 DOWNLOADER_MIDDLEWARES 设置中定义该中间件,并将其值赋为 None。例如,如果您想要关闭 user-agent 中间件:
DOWNLOADER_MIDDLEWARES = { ‘myproject.middlewares.CustomDownloaderMiddleware‘: 543, ‘scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware‘: None, } # 这里有疑问,我直接注掉和赋值为None,区别在哪?
最后,请注意,有些中间件需要通过特定的设置来启用。更多内容请查看相关中间件文档。
好了,既然我们已经学会了激活它,现在就到此为止吧。你已经可以下山了!!!
??
??
??
??
??
??
开个玩笑,别当真。
其实自定义一个属于我们自己的下载器中间件不难,甚至有点简单。
每个中间件组件是一个定义了一个或者多个方法的python类。
从三个点去突破。
process_request(request,spider):每个request通过下载中间件是,该方法会被调用。并且该方法必,算了。一会在代码里详细写注释吧。??
还是说了吧,须返回:None、Response对象、Request对象、raise IgnoreRequest其中之一!!!
process_response:代码里见
process_exception:捕捉异常用。
看码:
class BaiduDownloaderMiddleware(object): # 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. scrapy中的许多类都实现了该方法 通过调用该方法来生成下载器中间件管理器对象 那再补充两句: 每一个spider对应一个crawler,一个crawler会使用一个engine 一个CrawlerProcess中可以创建多个crawler进行多种爬取。 """ 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 """ 当每个request通过下载器中间件时,process_request被调用 :param request: 要处理的request :param spider: 该request对应的spider :return: 必须返回四者其一!!! None--> scrapy继续处理该request,会执行其他中间件的相应方法。 直到合适的下载器处理函数(download handler)被调用该request被执行,该request被执行(其response被下载)。 Request对象--> Scrapy停止调用 process_request()方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。 Response对象--> 已经安装的中间件的process_response()方法会在每个response返回时调用。 raise IgnoreRequest异常--> 停止process_request的执行,开始执行process_exception """ return None def process_response(self, request, response, spider): """ 当下载器完成http请求,传递响应给引擎的时候调用。 :param request: 发起响应的请求 :param response: 正在处理的响应 :param spider: 这个响应所对应的spider :return: 如果返回一个reponse对象(可以与传入的reponse相同,也可以是一个全新的对象),那么响应将继续与process_response()链中的下一个中间件一起处理。 如果返回一个request对象,那么中间件链将暂停,并且返回的请求将被重新安排在将来被下载。这与返回请求的行为是一样的process_request()。 如果raise一个IgnoreRequest异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则在下面爬虫中间件会说这个东西。配合着process_spider_input()和process_spider_output()来讲。 """ print(‘reponse1‘) return response def process_exception(self, request, exception, spider): """ 当下载处理器(download handler)或 process_request()(下载中间件)抛出异常(包括 IgnoreRequest 异常)时,Scrapy 调用 process_exception() 。 :param request: 产生异常的request :param exception: 抛出的异常 :param spider: request对应的spider :return: 当下载处理器(download handler)或 process_request()(下载中间件)抛出异常(包括 IgnoreRequest 异常)时,Scrapy 调用 process_exception()。 process_exception()应该返回以下之一: 返回 None、一个 Response 对象、或者一个 Request 对象。 如果其返回 None,Scrapy 将会继续处理该异常,接着调用已安装的其他中间件的 process_exception()方法,直到所有中间件都被调用完毕,则调用默认的异常处理。 如果其返回一个 Response 对象,则已安装的中间件链的 process_response()方法被调用。Scrapy 将不会调用任何其他中间件的 process_exception() 方法。 如果其返回一个 Request 对象, 则返回的 request 将会被重新调用下载。这将停止中间件的 process_exception()方法执行,就如返回一个 response 的那样。 """ return None def spider_opened(self, spider): # 爬取的准备工作,创建engine的关键组件 spider.logger.info(‘Spider opened: %s‘ % spider.name)
#1、与middlewares.py同级目录下新建proxy_handle.py import requests def get_proxy(): return requests.get("http://127.0.0.1:5010/get/").text def delete_proxy(proxy): requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy)) #2、middlewares.py from Amazon.proxy_handle import get_proxy,delete_proxy class DownMiddleware1(object): def process_request(self, request, spider): """ 请求需要被下载时,经过所有下载器中间件的process_request调用 :param request: :param spider: :return: None,继续后续中间件去下载; Response对象,停止process_request的执行,开始执行process_response Request对象,停止中间件的执行,将Request重新调度器 raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception """ proxy="http://" + get_proxy() request.meta[‘download_timeout‘]=20 request.meta["proxy"] = proxy print(‘为%s 添加代理%s ‘ % (request.url, proxy),end=‘‘) print(‘元数据为‘,request.meta) def process_response(self, request, response, spider): """ spider处理完成,返回时调用 :param response: :param result: :param spider: :return: Response 对象:转交给其他中间件process_response Request 对象:停止中间件,request会被重新调度下载 raise IgnoreRequest 异常:调用Request.errback """ print(‘返回状态吗‘,response.status) return response def process_exception(self, request, exception, spider): """ 当下载处理器(download handler)或 process_request() (下载中间件)抛出异常 :param response: :param exception: :param spider: :return: None:继续交给后续中间件处理异常; Response对象:停止后续process_exception方法 Request对象:停止中间件,request将会被重新调用下载 """ print(‘代理%s,访问%s出现异常:%s‘ %(request.meta[‘proxy‘],request.url,exception)) import time time.sleep(5) delete_proxy(request.meta[‘proxy‘].split("//")[-1]) request.meta[‘proxy‘]=‘http://‘+get_proxy() return request
官方文档:https://doc.scrapy.org/en/latest/topics/downloader-middleware.html