scrapy框架中Crawlspider模块源码剖析

一、前言

1、scrapy从Terminal中通过genspider命令创建一个蜘蛛,其中包含四个模块,分别为spider,crawlspider,csvfeedspider和xmlfeedspider,其中spider(basic模块)和crawlspider最为常用。
2、做过web后台开发的都知道,很多网站中定义url都是有一定规则的(如django路由系统中定义的urls规则就是正则表达式), 那么我们就可以根据这个特性来设计爬虫,而不是每次都要用spider分析页面格式,此时我们就可以用crawlspider实现这样的需求。

二、Crawlspider简介

CrawlSpider基于Spider自己独有的特性:

  1. Rules: 这是一个(或多个)Rule对象的列表。每个Rule 定义用于爬网站点的特定行为。规则对象如下所述。如果多个规则匹配相同的链接,则将根据它们在此属性中定义的顺序使用第一个规则。
  2. parse_start_url: 为start_urls响应调用此方法。它允许解析初始响应,并且必须返回 Item对象,Request 对象或包含其中任何一个的iterable。

Rule参数分析

class scrapy.spiders.Rule(link_extractor,callback = None,
cb_kwargs = None,follow = None,process_links = None,process_request = None )

link_extractor:是一个Link Extractor对象,它定义如何从每个已爬网页面中提取链接。

callback:是一个可调用的或一个字符串(在这种情况下,将使用具有该名称的spider对象的方法)为使用指定的link_extractor提取的每个链接调用。此回调接收响应作为其第一个参数,并且必须返回包含Item和/或 Request对象(或其任何子类)的列表。

警告:编写爬网蜘蛛规则时,请避免使用parse回调,因为CrawlSpider使用parse方法本身来实现其逻辑。因此,如果您覆盖该parse方法,则爬网蜘蛛将不再起作用。

cb_kwargs: 是一个包含要传递给回调函数的关键字参数的dict。

follow:是一个布尔值,它指定是否应该从使用此规则提取的每个响应中跟踪链接。如果写了callback,则follow默认为True,否则默认为False。

process_links:是一个可调用的,或一个字符串(在这种情况下,将使用来自具有该名称的蜘蛛对象的方法),将使用指定的每个响应提取的每个链接列表调用该方法link_extractor。这主要用于过滤目的。

process_request :是一个可调用的,或一个字符串(在这种情况下,将使用来自具有该名称的spider对象的方法),该方法将在此规则提取的每个请求中调用,并且必须返回请求或None(以过滤掉请求) 。

import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class MySpider(CrawlSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com']

    rules = (
        # Extract links matching 'category.php' (but not matching 'subsection.php')
        # and follow links from them (since no callback means follow=True by default).
        Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),

        # Extract links matching 'item.php' and parse them with the spider's method parse_item
        Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'),
    )

    def parse_item(self, response):
        self.logger.info('Hi, this is an item page! %s', response.url)
        item = scrapy.Item()
        item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
        item['name'] = response.xpath('//td[@id="item_name"]/text()').extract()
        item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
        return item

这个蜘蛛首先抓取example.com的主页,收集category链接和item链接,使用该parse_item方法解析后者。对于每个item的响应,将使用XPath从HTML中提取一些数据,并将数据返回给pipeline。

三、源码分析

class CrawlSpider(Spider):
    rules = ()
    def __init__(self, *a, **kw):
        super(CrawlSpider, self).__init__(*a, **kw)
        self._compile_rules()
 
    #首先调用parse()来处理start_urls中返回的response对象
    #parse()则将这些response对象传递给了_parse_response()函数处理,并设置回调函数为parse_start_url()
    #设置了跟进标志位True
    #parse将返回item和跟进了的Request对象    
    def parse(self, response):
        return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)
    
    #处理start_url中返回的response,需要重写
    def parse_start_url(self, response):
        return []
 
    def process_results(self, response, results):
        return results
    
    #从response中抽取符合任一用户定义'规则'的链接,并构造成Resquest对象返回
    def _requests_to_follow(self, response):
        if not isinstance(response, HtmlResponse):
            return
        seen = set()
        #抽取之内的所有链接,只要通过任意一个'规则',即表示合法
        for n, rule in enumerate(self._rules):
            links = [l for l in rule.link_extractor.extract_links(response) if l not in seen]
            #使用用户指定的process_links处理每个连接
            if links and rule.process_links:
                links = rule.process_links(links)
            #将链接加入seen集合,为每个链接生成Request对象,并设置回调函数为_repsonse_downloaded()
            for link in links:
                seen.add(link)
                #构造Request对象,并将Rule规则中定义的回调函数作为这个Request对象的回调函数
                r = Request(url=link.url, callback=self._response_downloaded)
                r.meta.update(rule=n, link_text=link.text)
                #对每个Request调用process_request()函数。该函数默认为indentify,即不做任何处理,直接返回该Request.
                yield rule.process_request(r)
    #处理通过rule提取出的连接,并返回item以及request
    def _response_downloaded(self, response):
        rule = self._rules[response.meta['rule']]
        return self._parse_response(response, rule.callback, rule.cb_kwargs, rule.follow)
    
    #解析response对象,会用callback解析处理他,并返回request或Item对象
    def _parse_response(self, response, callback, cb_kwargs, follow=True):
        #首先判断是否设置了回调函数。(该回调函数可能是rule中的解析函数,也可能是 parse_start_url函数)
        #如果设置了回调函数(parse_start_url()),那么首先用parse_start_url()处理response对象,
        #然后再交给process_results处理。返回cb_res的一个列表
        if callback:
            #如果是parse调用的,则会解析成Request对象
            #如果是rule callback,则会解析成Item
            cb_res = callback(response, **cb_kwargs) or ()
            cb_res = self.process_results(response, cb_res)
            for requests_or_item in iterate_spider_output(cb_res):
                yield requests_or_item
        
        #如果需要跟进,那么使用定义的Rule规则提取并返回这些Request对象
        if follow and self._follow_links:
            #返回每个Request对象
            for request_or_item in self._requests_to_follow(response):
                yield request_or_item
 
    def _compile_rules(self):
        def get_method(method):
            if callable(method):
                return method
            elif isinstance(method, basestring):
                return getattr(self, method, None)
 
        self._rules = [copy.copy(r) for r in self.rules]
        for rule in self._rules:
            rule.callback = get_method(rule.callback)
            rule.process_links = get_method(rule.process_links)
            rule.process_request = get_method(rule.process_request)
 
    def set_crawler(self, crawler):
        super(CrawlSpider, self).set_crawler(crawler)
        self._follow_links = crawler.settings.getbool('CRAWLSPIDER_FOLLOW_LINKS', True)

init:主要执行了_compile_rules方法

parse:默认回调方法,不过在这里进行了重写,这里直接调用方法_parse_response,并把parse_start_url方法作为处理response的方法。

parse_start_url:它的主要作用就是处理parse返回的response,比如提取出需要的数据等,该方法也需要返回item、request或者他们的可迭代对象。它就是一个回调方法,和rule.callback用法一样。

_requests_to_follow:阅读源码可以发现,它的作用就是从response中解析出目标url,并将其包装成request请求。该请求的回调方法是_response_downloaded,这里为request的meta值添加了rule参数,该参数的值是这个url对应rule在rules中的下标。

_response_downloaded:该方法是方法_requests_to_follow的回调方法,作用就是调用_parse_response方法,处理下载器返回的response,设置response的处理方法为rule.callback方法。

_parse_response:该方法将resposne交给参数callback代表的方法去处理,然后处理callback方法的requests_or_item。再根据rule.follow and spider._follow_links来判断是否继续采集,如果继续那么就将response交给_requests_to_follow方法,根据规则提取相关的链接。spider._follow_links的值是从settings的CRAWLSPIDER_FOLLOW_LINKS值获取到的。

_compile_rules:这个方法的作用就是将rule中的字符串表示的方法改成实际的方法。

下面附带一张源码的执行流程图,以便于大家理解
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/python_lzt/article/details/83688375
今日推荐