1. 程式人生 > >Scrapy學習——CrawlSpider詳解

Scrapy學習——CrawlSpider詳解

  首先,說是詳解,其實也並不是多麼深入,只是自己的一些學習筆記。其次,本文適合一邊翻原始碼,一邊閱讀。

  從CrawlSpider的原始碼(crawl.py)中我們可以看到,CrawlSpider是繼承Spider類的。在scrapy的官方文件中對Spider的描述如下:

  1. 以初始的URL初始化Request,並設定回撥函式。 當該request下載完畢並返回時,將生成response,並作為引數傳給該回調函式。
    spider中初始的request是通過呼叫 start_requests() 來獲取的。 start_requests() 讀取 start_urls 中的URL, 並以 parse 為回撥函式生成 Request 。

  2. 在回撥函式內分析返回的(網頁)內容,返回 Item 物件或者 Request 或者一個包括二者的可迭代容器。 返回的Request物件之後會經過Scrapy處理,下載相應的內容,並呼叫設定的callback函式(函式可相同)。

  3. 在回撥函式內,您可以使用 選擇器(Selectors) (您也可以使用BeautifulSoup, lxml 或者您想用的任何解析器) 來分析網頁內容,並根據分析的資料生成item。

  4. 最後,由spider返回的item將被存到資料庫(由某些 Item Pipeline 處理)或使用 Feed exports 存入到檔案中。

  因此,CrawlSpider也是以 start_requests()

作為程式入口,之後回撥 parse() 函式。但需要注意的是,CrawlSpider已經過載了parse()函式,因此,我們不能再重寫parse()函式,否則程式將會出錯。

def parse(self, response):
    return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)

def parse_start_url(self, response):
    return []

def process_results(self, response, results)
:
return results

  我們可以看到,在 CrawlSpider 過載的 parse() 函式中,它的返回是 _parse_response() 函式,而這個函式就是 CrwalSpider 的重點!其原始碼如下:

def _parse_response(self, response, callback, cb_kwargs, follow=True):
    if callback:
        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

    if follow and self._follow_links:
        for request_or_item in self._requests_to_follow(response):
            yield request_or_item

  對比兩段原始碼,我們不難發現,_parse_response() 函式中的回撥函式 callback 就是 parse_start_url() 方法,我們可以 過載 parse_start_url() 方法,進行邏輯處理
  此外,_parse_response() 函式還呼叫 process_results() 方法對 parse_start_url() 返回的陣列做進一步的處理,因此,我們也可以 過載process_results() 做進一步的邏輯處理。
  接著就是整個CrawlSpider的重中之重了!
  在_parse_response() 傳遞的引數中,我們可以在末尾看到 follow=True ,這表示 CrawlSpider 會對請求做進一步跟蹤。其呼叫的 _requests_to_follow () 原始碼如下:

def _requests_to_follow(self, response):
     if not isinstance(response, HtmlResponse):
         return
     seen = set()
     for n, rule in enumerate(self._rules):
         links = [lnk for lnk in rule.link_extractor.extract_links(response)
                  if lnk not in seen]
         if links and rule.process_links:
             links = rule.process_links(links)
         for link in links:
             seen.add(link)
             r = self._build_request(n, link)
             yield rule.process_request(r)

  方法進行以下操作:
1. 檢測傳入的引數是不是 response,如果不是則直接返回。
2. 根據傳入的引數 self._rules 做進一步的處理
3. 對links進行去重處理
4. 呼叫 self._build_request(n, link) 對link 進行下載