美文网首页
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