scrapy源码分析(十二)---------下载中间件RobotsTxtMiddleware

来源:互联网 发布:淘客微博群发软件 编辑:程序博客网 时间:2024/05/16 06:49

上一节分析了下载器的源码,知道了一个request经过middleware到handler下载返回response,response再经过middleware,最后由scraper处理的流程。

其中正是middleware的存在使我们对下载和解析的控制有很大的灵活性,我们可以自定义中间件来个性化我们的需求。这一节就分析一下middleware在整个下载流程中所发挥的关键作用。


我们从默认的下载中间件入手来分析,先看下默认的下载中间件:

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协议。

1 0