scrapy下载中间件源码浅析

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

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

猜你喜欢

转载自www.cnblogs.com/weiwu1578/p/9036995.html