分散式爬蟲總結和使用

weixin_34128411發表於2018-12-09

使用scrapy-redis:
Scrapy_redis在scrapy的基礎上實現了更多,更強大的功能,具體體現在:reqeust去重,爬蟲持久化,和輕鬆實現分散式

安裝scrapy-redis:

pip3 install scrapy-redis

Scrapy-redis提供了下面四種元件(components):(意味著四個模組都要做相應的修改)

1.Scheduler
2.Duplication Filter
3.Item Pipeline
4.Base Spider

scrapy-redis工作流程

9128390-f12b67c489a52e59.png
image.png

Scheduler:

Scrapy中跟“待爬佇列”直接相關的就是排程器Scheduler,它負責對新的request進行入列操作(加入Scrapy queue),取出下一個要爬取的request(從Scrapy queue中取出)等操作。它把待爬佇列按照優先順序建立了一個字典結構.
比如:
{
優先順序0 : 佇列0
優先順序1 : 佇列1
優先順序2 : 佇列2
}
然後根據request中的優先順序,來決定該入哪個佇列,出列時則按優先順序較小的優先出列。為了管理這個比較高階的佇列字典,Scheduler需要提供一系列的方法。但是原來的Scheduler已經無法使用,所以使用Scrapy-redis的scheduler元件。

Duplication Filter:

Scrapy中用集合實現這個request去重功能,Scrapy中把已經傳送的request指紋放入到一個集合中,把下一個request的指紋拿到集合中比對,如果該指紋存在於集合中,說明這個request傳送過了,如果沒有則繼續操作。這個核心的判重功能是這樣實現的:

def request_seen(self, request):
        # 把請求轉化為指紋  
        fp = self.request_fingerprint(request)
        # 這就是判重的核心操作  ,self.fingerprints就是指紋集合
        if fp in self.fingerprints:
            return True  #直接返回
        self.fingerprints.add(fp) #如果不在,就新增進去指紋集合
        if self.file:
            self.file.write(fp + os.linesep)

在scrapy-redis中去重是由Duplication Filter元件來實現的,它通過redis的set 不重複的特性,巧妙的實現了Duplication Filter去重。scrapy-redis排程器從引擎接受request,將request的指紋存⼊redis的set檢查是否重複,並將不重複的request push寫⼊redis的 request queue。

Item Pipeline:

引擎將(Spider返回的)爬取到的Item給Item Pipeline,scrapy-redis 的Item Pipeline將爬取到的 Item 存⼊redis的 items queue。
修改過Item Pipeline可以很方便的根據 key 從 items queue 提取item,從⽽實現 items processes叢集。

Base Spider:

不在使用scrapy原有的Spider類,重寫的RedisSpider繼承了Spider和RedisMixin這兩個類,RedisMixin是用來從redis讀取url的類。
當我們生成一個Spider繼承RedisSpider時,呼叫setup_redis函式,這個函式會去連線redis資料庫,然後會設定signals(訊號):
一個是當spider空閒時候的signal,會呼叫spider_idle函式,這個函式呼叫schedule_next_request函式,保證spider是一直活著的狀態,並且丟擲DontCloseSpider異常。
一個是當抓到一個item時的signal,會呼叫item_scraped函式,這個函式會呼叫schedule_next_request函式,獲取下一個request。

專案說明

使用scrapy-redis的example來修改 先從github上拿到scrapy-redis的示例,然後將裡面的example-project目錄移到指定的地址:
clone github scrapy-redis原始碼檔案

git clone https://github.com/rolando/scrapy-redis.git

拿官方的專案範例:

mv scrapy-redis/example-project ~/scrapyredis-project

我們clone到的 scrapy-redis 原始碼中有自帶一個example-project專案,這個專案包含3個spider,分別是dmoz, myspider_redis,mycrawler_redis。

一.dmoz (class DmozSpider(CrawlSpider))
注意:這裡只用到Redis的去重和儲存功能,並沒有實現分散式

這個爬蟲繼承的是CrawlSpider,它是用來說明Redis的持續性,當我們第一次執行dmoz爬蟲,然後Ctrl + C停掉之後,再執行dmoz爬蟲,之前的爬取記錄是保留在Redis裡的。

分析起來,其實這就是一個 scrapy-redis 版 CrawlSpider 類,需要設定Rule規則,以及callback不能寫parse()方法。 執行方式:

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


class DmozSpider(CrawlSpider):
    """Follow categories and extract links."""
    name = 'dmoz'
    allowed_domains = ['dmoz.org']
    start_urls = ['http://www.dmoz.org/']
  
   #定義了一個url的提取規則,將滿足條件的交給callback函式處理
    rules = [
        Rule(LinkExtractor(
            restrict_css=('.top-cat', '.sub-cat', '.cat-item')
        ), callback='parse_directory', follow=True),
    ]

    def parse_directory(self, response):
        for div in response.css('.title-and-desc'):
          #這裡將獲取到的內容交給引擎
            yield {
                'name': div.css('.site-title::text').extract_first(),
                'description': div.css('.site-descr::text').extract_first().strip(),
                'link': div.css('a::attr(href)').extract_first(),
            }

二.myspider_redis (class MySpider(RedisSpider))
這個爬蟲繼承了RedisSpider, 它能夠支援分散式的抓取,採用的是basic spider,需要寫parse函式。 其次就是不再有start_urls了,取而代之的是redis_key,scrapy-redis將key從Redis裡pop出來,成為請求的url地址。

from scrapy_redis.spiders import RedisSpider


class MySpider(RedisSpider):
    """Spider that reads urls from redis queue (myspider:start_urls)."""
    name = 'myspider_redis'
    #手動設定允許爬取的域
    allowed_domains = ['設定允許爬取的域']
    # 注意redis-key的格式:
    redis_key = 'myspider:start_urls'

    # 可選:等效於allowd_domains(),__init__方法按規定格式寫,使用時只需要修改super()裡的類名引數即可,一般不用
    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))

        # 修改這裡的類名為當前類名
        super(MySpider, self).__init__(*args, **kwargs)

    def parse(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        }

注意: RedisSpider類 不需要寫start_urls:

  • scrapy-redis 一般直接寫allowd_domains來指定需要爬取的域,也可以從在構造方法init()裡動態定義爬蟲爬取域範圍(一般不用)。
  • 必須指定redis_key,即啟動爬蟲的命令,參考格式:redis_key = 'myspider:start_urls'
  • 根據指定的格式,start_urls將在 Master端的 redis-cli 裡 lpush 到 Redis資料庫裡,RedisSpider 將在資料庫裡獲取start_urls

執行方式:
1.通過runspider方法執行爬蟲的py檔案(也可以分次執行多條),爬蟲(們)將處於等待準備狀態:

scrapy runspider myspider_redis.py

or

scrapy crawl myspider_redis

2.在Master端的redis-cli輸入push指令,參考格式(指定起始url):

lpush myspider:start_urls http://www.dmoz.org/

3.Slaver端爬蟲獲取到請求,開始爬取。

三.mycrawler_redis (class MyCrawler(RedisCrawlSpider))
這個RedisCrawlSpider類爬蟲繼承了RedisCrawlSpider,能夠支援分散式的抓取。因為採用的是crawlSpider,所以需要遵守Rule規則,以及callback不能寫parse()方法。
同樣也不再有start_urls了,取而代之的是redis_key,scrapy-redis將key從Redis裡pop出來,成為請求的url地址。

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

from scrapy_redis.spiders import RedisCrawlSpider


class MyCrawler(RedisCrawlSpider):

    """Spider that reads urls from redis queue (myspider:start_urls)."""

    name = 'mycrawler_redis'

    allowed_domains = ['設定允許爬取的域']
    redis_key = 'mycrawler:start_urls'

    rules = (
        # follow all links
        Rule(LinkExtractor(), callback='parse_page', follow=True),
    )

    # __init__方法必須按規定寫,使用時只需要修改super()裡的類名引數即可(一般不用)
    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))

        # 修改這裡的類名為當前類名
        super(MyCrawler, self).__init__(*args, **kwargs)

    def parse_page(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        }

注意: 同樣的,RedisCrawlSpider類不需要寫start_urls:

執行方式:

1.scrapy runspider myspider_redis.py
2.lpush myspider:start_urls http://www.dmoz.org/
3.Slaver端爬蟲獲取到請求,開始爬取。

總結:

1 如果只是用到Redis的去重和儲存功能,就選第一種;
2 如果要寫分散式,則根據情況,選擇第二種、第三種;
3 通常情況下,會選擇用第三種方式編寫深度聚焦爬蟲。

相關文章