我们从默认的下载中间件入手来分析,先看下默认的下载中间件:
DOWNLOADER_MIDDLEWARES_BASE = { # Engine side 'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100, 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300, 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 400, 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 500, 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 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.chunked.ChunkedTransferMiddleware': 830, 'scrapy.downloadermiddlewares.stats.DownloaderStats': 850, 'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900, # Downloader side }
1.RobotsTxtMiddleware:
scray/downloadermiddlewares/robotstxt.py:
class RobotsTxtMiddleware(object): DOWNLOAD_PRIORITY = 1000 def __init__(self, crawler): if not crawler.settings.getbool('ROBOTSTXT_OBEY'): raise NotConfigured self.crawler = crawler self._useragent = crawler.settings.get('USER_AGENT') self._parsers = {} @classmethod def from_crawler(cls, crawler): return cls(crawler)
前面分析中间件管理器时知道,管理器构造中间件时会优先调用中间件的from_crawler方法,因此构造RobotsTxtMiddleware时只是调用其构造函数。
配置文件中如果未定义是否遵守机器人协议‘ROBOTSTXT_OBEY',则抛出未配置异常。这会使Robots中间件构造失败,因此也不会处理网站的机器人文件。
然后还会从配置中获取'USER_AGENT'配置,
最后定义了一个_parsers字典,用来存储已经分析过Robts.txt文件的网站信息。
然后分析其’process_request'方法,这个方法前面讲过会在下载之前由下载中间件管理器调用。
def process_request(self, request, spider): if request.meta.get('dont_obey_robotstxt'): return d = maybeDeferred(self.robot_parser, request, spider) d.addCallback(self.process_request_2, request, spider) return d def process_request_2(self, rp, request, spider): if rp is not None and not rp.can_fetch(self._useragent, request.url): logger.debug("Forbidden by robots.txt: %(request)s", {'request': request}, extra={'spider': spider}) raise IgnoreRequest()
首先判断request是否在meta中设置了'dont_obey_robotstx',如果设置了则什么也不做。
然后调用robot_parser方法下载和分析robot.txt文件,并给返回的Deferred对象安装了一个'process_request_2'方法。
robot_parser会去下载robot.txt文件,并会返回一个rp对象。
scrapy使用的是第三方库 six.moves.urllib.robotparser来分析robot.txt的,返回的rp就是这个对象。
这个对象提供了can_fetch方法用来判断是否允许爬取指定的URL。
因此,给robt_parser返回的Deferred对象安装的process_request_2函数用rp来判断是否允许爬取request.url
再来看下'robot_parser'方法,函数比较长,代码分析通过注释讲解
def robot_parser(self, request, spider): url = urlparse_cached(request) netloc = url.netloc /*获取request对应的网站*/ if netloc not in self._parsers: /*还没有分析过则继续*/ self._parsers[netloc] = Deferred() robotsurl = "%s://%s/robots.txt" % (url.scheme, url.netloc) robotsreq = Request( robotsurl, priority=self.DOWNLOAD_PRIORITY, /*robots的request的优化级为1000*/ meta={'dont_obey_robotstxt': True} /*对于robot.txt文件不遵守robot协议*/ ) dfd = self.crawler.engine.download(robotsreq, spider) /*调用engine的download方法下载robot.txt,注意调用这个方法不会走入网页的解析和scraper流程。*/ dfd.addCallback(self._parse_robots, netloc) /*下载完成后调用'_parse_robots方法*/ dfd.addErrback(self._logerror, robotsreq, spider) dfd.addErrback(self._robots_error, netloc) if isinstance(self._parsers[netloc], Deferred): /*如果分析结果是一个Deferred对象,说明之前的robot还未处理完成,则再构造一个deferred对象d返回,当分析结果的Defered对象完成时,激活返回的d. 这样就会调用'process_request_2'方法了*/ d = Deferred() def cb(result): d.callback(result) return result self._parsers[netloc].addCallback(cb) return d else: /*不是Deferred对象则已经分析完成了,直接返回对应的RobotFileParser对象*/ return self._parsers[netloc]
接下来看看下载成功后调用的'_parse_robots'方法:
def _parse_robots(self, response, netloc): rp = robotparser.RobotFileParser(response.url) /*调用第三方库生成RobotFileParser对象。*/ body = '' if hasattr(response, 'text'): /*处理网页内容编码*/ body = response.text else: # last effort try try: body = response.body.decode('utf-8') except UnicodeDecodeError: # If we found garbage, disregard it:, # but keep the lookup cached (in self._parsers) # Running rp.parse() will set rp state from # 'disallow all' to 'allow any'. pass rp.parse(body.splitlines()) /*分析网页内容*/ rp_dfd = self._parsers[netloc] /*获取Deferred对象*/ self._parsers[netloc] = rp /*将字典中存储的替换为生成的RobotFileParser对象*/ rp_dfd.callback(rp) /*用rp对象激活deferred,这样就会调用为deferred添加的'process_request_2'方法了*/
这样就分析完了RobotsTxtMiddleware中间件的源码,它用来分析被爬取的网站的robot.txt文件,用来决定是否要对request继续爬取。当然也可以通过给Request指定
’dont_obey_robotstxt‘meta属性来不遵守robot协议。