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, } '''