middleware

from collections import defaultdict
import logging
import pprint

from scrapy.exceptions import NotConfigured
from scrapy.utils.misc import load_object
from scrapy.utils.defer import process_parallel, process_chain, process_chain_both

logger = logging.getLogger(__name__)


class MiddlewareManager(object):
    """Base class for implementing middleware managers"""

    component_name = 'foo middleware'

    def __init__(self, *middlewares):
        self.middlewares = middlewares
        self.methods = defaultdict(list)
        for mw in middlewares:
            # 遍历所有的中间件,并调用_add_middleware方法:
            # 管理器中添加中间件的'open_spider'和'close_spider'方法
            self._add_middleware(mw)

    @classmethod
    def _get_mwlist_from_settings(cls, settings):
        raise NotImplementedError

    @classmethod
    def from_settings(cls, settings, crawler=None):
        # 调用_get_mwlist_from_settings方法从配置文件中生成中间件列表,这个方法需要子类实现
        mwlist = cls._get_mwlist_from_settings(settings)
        middlewares = []
        enabled = []
        for clspath in mwlist:
            try:
                mwcls = load_object(clspath)
                # 依次加载中间件模块并构造对象,构造顺序是先尝试调用from_cralwer,再尝试调用from_settings,最后都没有再调用__init__
                if crawler and hasattr(mwcls, 'from_crawler'):
                    mw = mwcls.from_crawler(crawler)
                elif hasattr(mwcls, 'from_settings'):
                    mw = mwcls.from_settings(settings)
                else:
                    mw = mwcls()
                middlewares.append(mw)
                enabled.append(clspath)
            except NotConfigured as e:
                if e.args:
                    clsname = clspath.split('.')[-1]
                    logger.warning("Disabled %(clsname)s: %(eargs)s",
                                   {'clsname': clsname, 'eargs': e.args[0]},
                                   extra={'crawler': crawler})

        logger.info("Enabled %(componentname)ss:
%(enabledlist)s",
                    {'componentname': cls.component_name,
                     'enabledlist': pprint.pformat(enabled)},
                    extra={'crawler': crawler})
        return cls(*middlewares)

    # 这个from_cralwer方法是基类MiddlewareManger的方法
    # 通过crawler的配置生成对象
    @classmethod
    def from_crawler(cls, crawler):
        return cls.from_settings(crawler.settings, crawler)

    '''
    网络蜘蛛中间件管理器通过自定义'_add_middleware'方法还添加了'process_spider_input','process_spider_output','process_spider_exception','process_start_request'方法,这些方面后面的分析中都会乃至。
    def _add_middleware(self, mw):
    super(SpiderMiddlewareManager, self)._add_middleware(mw)
    if hasattr(mw, 'process_spider_input'):
        self.methods['process_spider_input'].append(mw.process_spider_input)
    if hasattr(mw, 'process_spider_output'):
        self.methods['process_spider_output'].insert(0, mw.process_spider_output)
    if hasattr(mw, 'process_spider_exception'):
        self.methods['process_spider_exception'].insert(0, mw.process_spider_exception)
    if hasattr(mw, 'process_start_requests'):
        self.methods['process_start_requests'].insert(0, mw.process_start_requests)'''

    # 可以看到,就是向methods字典里依次添加中间件的'open_spider'和'close_spider'方法。
    def _add_middleware(self, mw):
        if hasattr(mw, 'open_spider'):
            self.methods['open_spider'].append(mw.open_spider)
        if hasattr(mw, 'close_spider'):
            self.methods['close_spider'].insert(0, mw.close_spider)

    def _process_parallel(self, methodname, obj, *args):
        return process_parallel(self.methods[methodname], obj, *args)

    def _process_chain(self, methodname, obj, *args):
        return process_chain(self.methods[methodname], obj, *args)

    def _process_chain_both(self, cb_methodname, eb_methodname, obj, *args):
        return process_chain_both(self.methods[cb_methodname], 
                                  self.methods[eb_methodname], obj, *args)

    def open_spider(self, spider):
        return self._process_parallel('open_spider', spider)

    def close_spider(self, spider):
        return self._process_parallel('close_spider', spider)
# -*- coding: utf-8 -*-

# Define here the models for your spider middleware
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html

from scrapy import signals


class ScrapySpiderMiddleware(object):

    def open_spider(self):
        pass

    def process_spider_input(self, response, spider):
        '''
        当response通过spider中间件时,该方法被调用,处理该response。
        如果其跑出一个异常(exception),Scrapy将不会调用任何其他中间件的 process_spider_input() 方法,并调用request的errback。
        '''
        return None

    def process_spider_output(self, response, result, spider):
        '''
        当Spider处理response返回result时,该方法被调用。处理item数据
        '''
        for i in result:
            yield i

    def process_spider_exception(self, response, exception, spider):
        '''
         process_spider_input()抛出异常时, Scrapy调用 process_spider_exception() 。
         None:Scrapy将继续处理该异常,调用中间件链中的其他中间件的
         可迭代对象(Response 或 Item 对象的可迭代对象(iterable)):如果其返回一个可迭代对象,则中间件链的 process_spider_output() 方法被调用
         Request 对象:则返回的request将会被重新调用下载。这将停止中间件的 process_exception() 方法执行,就如返回一个response的那样。
         如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。
        '''
        pass

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)


'''
SPIDER_MIDDLEWARES_BASE
{
    过滤出所有失败(错误)的HTTP response,因此spider不需要处理这些request。
    返回值为200-300之间的值为成功的resonse。
    您可以通过 spider的 handle_httpstatus_list:404  提交处理的状态码
    HTTPERROR_ALLOWED_CODES=[201,202]
    HTTPERROR_ALLOW_ALL:Flase True(默认忽略,)
    'scrapy.contrib.spidermiddleware.httperror.HttpErrorMiddleware': 50,
    
    过滤出不属于spider负责的Request的URL
    allowed_domains (设置允许爬的域)  baidu.com
    dont_filter (设置不允许爬的域)    baidu.com/newspage
    'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': 500,
    
    根据生成Request的Response的URL来设置Request Referer 字段。
    REFERER_ENABLED:True False(默认开启)
    'scrapy.contrib.spidermiddleware.referer.RefererMiddleware': 700,
    
    设置URL长度
    URLLENGTH_LIMIT:2083 允许爬取URL最长的长度.
    'scrapy.contrib.spidermiddleware.urllength.UrlLengthMiddleware': 800,
    
    用于追踪每个Request在被爬取的网站的深度的中间件。 其可以用来限制爬取深度的最大深度或类似的事情。
    DEPTH_LIMIT - 爬取所允许的最大深度,如果为0,则没有限制。
    DEPTH_STATS - 是否收集爬取状态。
    DEPTH_PRIORITY - 是否根据其深度对requet安排优先级
    'scrapy.contrib.spidermiddleware.depth.DepthMiddleware': 900,
}
'''


class ScrapyDownloaderMiddleware(object):

    def open_spider(self):
        pass

    def process_request(self, request, spider):
        '''
        当每个request通过下载中间件时,该方法被调用。
        None:Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用, 该request被执行(其response被下载)。
        Response 对象:Scrapy将不会调用 任何 其他的 process_request() 或 process_exception() 方法,或相应地下载函数; 其将返回该response。
        Request 对象:Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。
        如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。
        '''
        return None

    def process_response(self, request, response, spider):
        '''
        当每个response通过下载中间件时,该方法被调用。
        Response 对象:该response会被在链中的其他中间件的 process_response() 方法处理。
        Request 对象:中间件链停止, 返回的request会被重新调度下载。处理类似于 process_request() 返回request所做的那样。
        如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。
        '''
        return response

    def process_exception(self, request, exception, spider):
        '''
        当下载处理器(下载中间件)抛出异常时, Scrapy调用 process_exception() 。
        None:Scrapy将会继续处理该异常,接着调用已安装的其他中间件的 process_exception() 方法,直到所有中间件都被调用完毕,则调用默认的异常处理。
        Response 对象:则已安装的中间件链的 process_response() 方法被调用。Scrapy将不会调用任何其他中间件的 process_exception() 方法。
        Request 对象:则返回的request将会被重新调用下载。这将停止中间件的 process_exception() 方法执行,就如返回一个response的那样。
        如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。
       '''
        pass

    def spider_opened(self, spider):
        # 关闭爬虫的时候会被调用
        spider.logger.info('Spider opened: %s' % spider.name)


'''
DOWNLOADER_MIDDLEWARES_BASE
{
    是否遵循Robots协议
    ROBOTSTXT_OBEY:True False(默认遵循)
    scrapy.contrib.downloadermiddleware.robotstxt.RobotsTxtMiddleware': 100,
    
    完成爬取过程中在HTTP认证过程
    当一个客户端向HTTP服务器进行数据请求时,如果客户端未被认证,则HTTP服务器将通过基本认证过程对客户端的用户名及密码进行验证,以决定用户是否合法
    from scrapy.contrib.spiders import CrawlSpider
    class SomeIntranetSiteSpider(CrawlSpider):
    http_user = 'someuser'   用户名
    http_pass = 'somepass'   密码
    name = 'intranet.example.com'   域名
    'scrapy.contrib.downloadermiddleware.httpauth.HttpAuthMiddleware': 300,
    
    设置 DOWNLOAD_TIMEOUT 指定的request下载超时时间. 
    DOWNLOAD_TIMEOUT:180(下载超时时间)
    'scrapy.contrib.downloadermiddleware.downloadtimeout.DownloadTimeoutMiddleware': 350,
    
    用于覆盖spider的默认user agent的中间件
    USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36'(设置请求头)
    'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': 400,
    
    爬取进程会收集失败的页面并在最后,spider爬取完所有正常(不失败)的页面后重新调度。
    RETRY_ENABLED:True False(默认开启)
    RETRY_TIMES:2(默认下载次数,包括第一次)
    'scrapy.contrib.downloadermiddleware.retry.RetryMiddleware': 500,
    
    设置 DEFAULT_REQUEST_HEADERS 指定的默认request header。
    {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',}
    'scrapy.contrib.downloadermiddleware.defaultheaders.DefaultHeadersMiddleware': 550,
    
    根据meta-refresh html标签处理request重定向。
    METAREFRESH_ENABLED:True False(默认开启)
    REDIRECT_MAX_METAREFRESH_DELAY:100(跟进重定向的最大 meta-refresh 延迟(单位:秒)
    'scrapy.contrib.downloadermiddleware.redirect.MetaRefreshMiddleware': 580,
    
    # 提供了对压缩(gzip, deflate)数据的支持。
    COMPRESSION_ENABLED:True Flase(默认开启)
    'scrapy.contrib.downloadermiddleware.httpcompression.HttpCompressionMiddleware': 590,
    
    根据response的状态处理重定向的request。
    REDIRECT_ENABLED:True False (默认开启)
    REDIRECT_MAX_TIMES:20(重定向的最大次数。)
    'scrapy.contrib.downloadermiddleware.redirect.RedirectMiddleware': 600,
    
    添加cookie信息   
    COOKIES_ENABLED:True False
    'scrapy.contrib.downloadermiddleware.cookies.CookiesMiddleware': 700,
    
    对request设置HTTP代理的支持。您可以通过在 Request 对象中设置 proxy 元数据来开启代理。
    'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 750,
    
    该中间件为所有HTTP request及response数据
    每个request及其对应的response都被缓存。 当相同的request发生时,其不发送任何数据,直接返回response。
    HTTPCACHE_ENABLED:True False  是否开启
    HTTPCACHE_EXPIRATION_SECS:0(秒)超过这个时间的缓存request将会被重新下载。如果为0,则缓存的request将永远不会超时
    HTTPCACHE_DIR:httpcache 缓存目录
    'scrapy.contrib.downloadermiddleware.httpcache.HttpCacheMiddleware': 800,
}
'''
原文地址:https://www.cnblogs.com/yoyo1216/p/10131373.html