美文网首页
Scrapy的中间件

Scrapy的中间件

作者: 还是那个没头脑 | 来源:发表于2021-06-29 08:16 被阅读0次

    下载器中间件

    引擎engine将request对象交给下载器之前,会经过下载器中间件;此时,中间件提供了一个方法 process_request,可以对 request对象进行设置随机请求头、IP代理、Cookie等;当下载器完成下载时,获得到 response对象,将它交给引擎engine的过程中,再一次经过 下载器中间件;此时,中间件提供了另一个方法 process_response;可以判断 response对象的状态码,来决定是否将 response提交给引擎

    • process_request(request, spider)
    • process_response(request, response, spider)
    • process_exception(request, exception, spider)
    方法1:process_request(request,spider)

    当每个request通过下载中间件时,该方法被调用。

    process_request() 必须返回其中之一: 返回 None 、返回一个 Response 对象、返回一个 Request 对象或raise IgnoreRequest 。

    • 返回 None 时,,Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用, 该request被执行(其response被下载)。

    • 如果其返回 Response 对象,Scrapy将不会调用 任何 其他的 process_request() 或 process_exception() 方法,或相应地下载函数; 其将返回该response。 已安装的中间件的 process_response() 方法则会在每个response返回时被调用。

    • 如果其返回 Request对象,Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。

    • 如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。

    方法2 process_response(request, response, spider)

    process_request() 必须返回以下之一: 返回一个 Response 对象、 返回一个 Request 对象或raise一个 IgnoreRequest 异常。

    • 如果其返回一个 Response (可以与传入的response相同,也可以是全新的对象), 该response会被在链中的其他中间件的 process_response() 方法处理。

    • 如果其返回一个 Request 对象,则中间件链停止, 返回的request会被重新调度下载。处理类似于 process_request() 返回request所做的那样。

    • 如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。

    方法3 process_exception(request, exception, spider)

    process_exception() 应该返回以下之一: 返回 None 、 一个 Response 对象、或者一个 Request 对象。

    • 如果其返回 None ,Scrapy将会继续处理该异常,接着调用已安装的其他中间件的 process_exception() 方法,直到所有中间件都被调用完毕,则调用默认的异常处理。

    • 如果其返回一个 Response 对象,则已安装的中间件链的 process_response() 方法被调用。Scrapy将不会调用任何其他中间件的 process_exception() 方法。

    • 如果其返回一个 Request 对象, 则返回的request将会被重新调用下载。这将停止中间件的 process_exception() 方法执行,就如返回一个response的那样。

    内置的BASE 下载器中间件如下:

    {
        'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,  #爬虫协议
        'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,  #http认证
        'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,  #下载超时中间件
        'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400, #默认的headers
        'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500, #user-agent中间件
        'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,  #调试
        'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560, #ajax抓取
        'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,  #网页数据刷新
        'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590, #http压缩
        'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,  #重定向
        'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700, #cookies
        'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,# 代理
        'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,  #下载状态码
        'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900, #http缓存
    }
    
    禁用中间件

    想禁用 RetryMiddleware中间件,只需要在settings文件种的中间件字典里加入, ‘scrapy.downloadermiddlewares.retry.RetryMiddleware’: None,

    优先级:

    权重越小,越靠近引擎process_request,权重越大,越靠近下载器process_response

    爬虫中间件

    Spider中间件是在引擎及Spider的特定钩子,处理spider的输入(response)和输出(item及requests).

    Spider Midleware 有如下三个作用:

    • 我们可以在 Downmloader生成的Response发送给 Spider之前,也就是在 Response发送给Spide|之前对 Response进行处理。
    • 我们可以在 Spider生成的Request发送给 Scheduler之前,也就是在 Request发送给 Schedule之前对Request 进行处理。
    • 我们可以在 Spider生成的Item发送给 Item Pipeline之前,也就是在 Item发送给Item Pipeline之前对Item 进行处理
    内置的SPIDER_MIDDLEMARES_BASE 爬虫中间件如下
    {
        'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware':50,
        'scrapy.spidermiddlewares.offsite.0ffsiteNiddleware':500,                                
        'scrapy.spidermiddlewares.referer.RefererNiddleware':700,
        'scrapy. spidermiddlewares.urllength.UrlLengthMiddleware':800,            
        'scrapy.spidermiddlewares.depth. epthMiddleware':900,
    }
    
    1、process_spider_input(response,spider)
    • 如果其返回None,Scrapy将会继续处理该response,调用所有其他中间件直到spider处理该response。
    • 如果其抛出一个异常(exception),Scrapy将不会调用任何其他中间件的process_spider_input()方法,并调用request的errback。errback的输出将会以另一个方向被输入到中间链中,使用process_spider_output()方法来处理,当其抛出异常时则带调用process_spider_exception()。

    参数:
    response(Response对象) - 被处理的response
    spider(Spider对象) - 该response对应的spider

    2、process_spider_output(response,result,spider)

    当Spider处理response返回result,该方法被调用.
    该方法必须返回包含request或item对象的可迭代对象iterable.

    参数:
    response(Response对象) - 生成该输出的response
    result(包含Reques或Item对象的可迭代对象(iterable)) - spider返回的result
    spider(Spider对象) - 其结果被处理的spider

    3、process_spider_exception(response,exception,spider)

    当spider或(其他spider中间件的)process_spider_input()抛出异常时,该方法被调用.
    返回None:Scrapy将继续处理该异常,调用中间件链的其他中间件的process_spider_exception;
    返回一个包含Response或item对象的可迭代对象(iterable):中间件;链的process_spider_output方法被调用,其他的process_spider_exception将不会被调用.

    参数:
    response(Response对象) - 异常被抛出时被处理的response
    exception(Exception对象) - 被抛出的异常
    spider(Spider对象) - 抛出异常的spider

    4.process_start_requests(start_requests,spider)

    该方法以spider启动的request为参数被调用,执行的过程类似于process_spider_output(),只不过其没有相关联的response并且必须返回request(不是item)。

    其接受一个可迭代的对象(start_requests参数)且必须返回一个包含Request对象的可迭代对象。

    当在您的spider中间件实现该方法时,您必须返回一个可迭代对象(类似于参数start_requests)且不要遍历所有的start_requests。该迭代器会很大(甚至是无限),进而导致内存溢出。Scrapy引擎再其具有能力处理start_requests时将会拉起request,因此start_requests迭代器会变得无限,而由其它参数来停止spider(例如时间限制或者item/page计数)。

    参数:
    start_requests(b包含Request的可迭代对象) - start requests
    spider(Spider对象) - start request所属的spider

    执行顺序

    1、 爬虫中间件的process_start_requests方法
    2、 爬虫的start_requests方法
    3、 爬虫中间件的process_spider_input方法
    4、 爬虫中间件的process_spider_output方法
    5、 爬虫的parse方法
    6、 爬虫中间件的process_spider_output方法

    常用例子

    1、设置随机UA
    from fake_useragent import UserAgent
    
    class UserAgentMiddleWare(object):
    
        def process_request(self, request, spider):
            request.headers['User-Agent'] = UserAgent().random
    

    或者

    from scrapy import signals
    from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
    import random
    
    
    class RotateUserAgentMiddleware(UserAgentMiddleware):
        # for more user agent strings,you can find it in http://www.useragentstring.com/pages/useragentstring.php
        user_agent_list = [
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
            "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
            "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
            "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
            "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
            "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
            "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
            "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
            "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
            "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
            "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
        ]
    
        def process_request(self, request, spider):
            ua = random.choice(self.user_agent_list)
            if ua:
                # 显示当前使用的useragent
                # print("********Current UserAgent:%s************" % ua)
                # 记录
                spider.logger.info('Current UserAgent: ' + ua)
                request.headers['User-Agent'] = ua
    
    2、设置代理
    class ProxyMiddleware(object):
        def process_request(self, request, spider):
            # 代理服务器(产品官网 www.16yun.cn)
            proxyHost = "****"
            proxyPort = "****"
    
            # 代理验证信息
            proxyUser = "*****"
            proxyPass = "****"
    
            request.meta['proxy'] = "http://{0}:{1}".format(proxyHost, proxyPort)
    
            # 添加验证头
            encoded_user_pass = base64ify(proxyUser + ":" + proxyPass)
            request.headers['Proxy-Authorization'] = 'Basic ' + encoded_user_pass
    
            # 设置IP切换头(根据需求)
            tunnel = random.randint(1, 10000)
            request.headers['Proxy-Tunnel'] = str(tunnel)
    
    3、设置重连

    使用IDE,现在scrapy项目中任意一个文件敲上以下代码:

    from scrapy.downloadermiddlewares.retry import RetryMiddleware
    

    该中间件的源代码如下:

    class RetryMiddleware(object):
     
        # IOError is raised by the HttpCompression middleware when trying to
        # decompress an empty response
        EXCEPTIONS_TO_RETRY = (defer.TimeoutError, TimeoutError, DNSLookupError,
                               ConnectionRefusedError, ConnectionDone, ConnectError,
                               ConnectionLost, TCPTimedOutError, ResponseFailed,
                               IOError, TunnelError)
     
        def __init__(self, settings):
            if not settings.getbool('RETRY_ENABLED'):
                raise NotConfigured
            self.max_retry_times = settings.getint('RETRY_TIMES')
            self.retry_http_codes = set(int(x) for x in settings.getlist('RETRY_HTTP_CODES'))
            self.priority_adjust = settings.getint('RETRY_PRIORITY_ADJUST')
     
        @classmethod
        def from_crawler(cls, crawler):
            return cls(crawler.settings)
     
        def process_response(self, request, response, spider):
            if request.meta.get('dont_retry', False):
                return response
            if response.status in self.retry_http_codes:
                reason = response_status_message(response.status)
                return self._retry(request, reason, spider) or response
            return response
     
        def process_exception(self, request, exception, spider):
            if isinstance(exception, self.EXCEPTIONS_TO_RETRY) \
                    and not request.meta.get('dont_retry', False):
                return self._retry(request, exception, spider)
     
        def _retry(self, request, reason, spider):
            retries = request.meta.get('retry_times', 0) + 1
     
            retry_times = self.max_retry_times
     
            if 'max_retry_times' in request.meta:
                retry_times = request.meta['max_retry_times']
     
            stats = spider.crawler.stats
            if retries <= retry_times:
                logger.debug("Retrying %(request)s (failed %(retries)d times): %(reason)s",
                             {'request': request, 'retries': retries, 'reason': reason},
                             extra={'spider': spider})
                retryreq = request.copy()
                retryreq.meta['retry_times'] = retries
                retryreq.dont_filter = True
                retryreq.priority = request.priority + self.priority_adjust
     
                if isinstance(reason, Exception):
                    reason = global_object_name(reason.__class__)
     
                stats.inc_value('retry/count')
                stats.inc_value('retry/reason_count/%s' % reason)
                return retryreq
            else:
                stats.inc_value('retry/max_reached')
                logger.debug("Gave up retrying %(request)s (failed %(retries)d times): %(reason)s",
                             {'request': request, 'retries': retries, 'reason': reason},
                             extra={'spider': spider})
    

    查看源码我们可以发现,对于返回http code的response,该中间件会通过process_response方法来处理,处理办法比较简单,大概是判断response.status是否在定义好的self.retry_http_codes集合中,通过向前查找,这个集合是一个列表,定义在default_settings.py文件中,定义如下:

    RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408, 429]
    

    先判断http code是否在这个集合中,如果在,就进入retry的逻辑,不在集合中就直接return response。这样就已经实现对返回http code但异常的response的处理了。

    但是对另一种异常的处理方式就不一样了,刚才的异常准确的说是属于HTTP请求error(超时),而另一种异常发生的时候则是如下图这种实实在在的代码异常(不处理的话),比如TimeoutError, DNSLookupError异常,这种异常处理见👇 异常模版

    4、异常模版

    from twisted.internet import defer
    from twisted.internet.error import TimeoutError, DNSLookupError, \
        ConnectionRefusedError, ConnectionDone, ConnectError, \
        ConnectionLost, TCPTimedOutError
    from scrapy.http import HtmlResponse
    from twisted.web.client import ResponseFailed
    from scrapy.core.downloader.handlers.http11 import TunnelError
     
    class ProcessAllExceptionMiddleware(object):
        ALL_EXCEPTIONS = (defer.TimeoutError, TimeoutError, DNSLookupError,
                          ConnectionRefusedError, ConnectionDone, ConnectError,
                          ConnectionLost, TCPTimedOutError, ResponseFailed,
                          IOError, TunnelError)
        def process_response(self,request,response,spider):
            #捕获状态码为40x/50x的response
            if str(response.status).startswith('4') or str(response.status).startswith('5'):
                # 重新发起请求
                return request
            #其他状态码不处理
            return response
        def process_exception(self,request,exception,spider):
            #捕获几乎所有的异常
            if isinstance(exception, self.ALL_EXCEPTIONS):
                #在日志中打印异常类型
                print('Got exception: %s' % (exception))
                # 重新发起请求
                return request
            #打印出未捕获到的异常
            print('not contained exception: %s'%exception)
    

    参考文章:
    官方文档
    如何在scrapy中捕获并处理各种异常
    Scrapy框架——中间件详解

    相关文章

      网友评论

          本文标题:Scrapy的中间件

          本文链接:https://www.haomeiwen.com/subject/ymrhultx.html