1. 程式人生 > >scrapy框架中Crawlspider模組原始碼剖析

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中的字串表示的方法改成實際的方法。

下面附帶一張原始碼的執行流程圖,以便於大家理解
在這裡插入圖片描述