筆記-爬蟲-scrapy-srcapy-redis組件

筆記-爬蟲-scrapy-srcapy-redis組件

 

1.      簡介

scrapy是一個爬蟲框架,但不支持分佈式,scrapy-redis是爲了更方便的實現scrapy分佈式爬蟲的組件。python

能夠在pypi上找到:https://pypi.org/project/scrapy-redis/mysql

 

1.1.    安裝

可使用pip安裝redis

pip install scrapy-redis算法

pip show scrapy-redissql

目前最新版是0.6.8。mongodb

 

2.      使用

Scrapy-redis提供了下面四種組件(components):(意味着原始scrapy爬蟲這四個部分都要作相應的修改)數據庫

Schedulercentos

Duplication Filter服務器

Item Pipeline網絡

Base Spider

先無論那麼多,先跑一個案例;

 

-----實測------

3.      scrapy-redis實測

3.1.    環境準備

主:虛擬機 centos6.5

從:物理機win8

3.2.    redis安裝配置

見筆記-redis安裝

 

3.3.    centos python環境安裝

centos下已有python環境,安裝參考文檔:筆記-python3環境安裝-centos6.5

安裝相關包:

pip3 install redis,scrapy,scrapy-redis,lxml

包括redis-py,scrapy-redis,

經過pip安裝就能夠了,比較簡單。

 

3.4.    代碼

與scrapy爬蟲代碼大同小異,主要是spider類和settings中設置調度器,去重功能:

 

3.4.1.   item

徹底同樣;

 

3.4.2.   spiders/sina_news.py

spider類的基類改成RedisSpider

from scrapy_redis.spiders import RedisSpider

 

註釋掉start_urls。

新增屬性:

redis_key = ‘sinanewsspider:start_urls’

這個屬性是給redis中建組用的,:做爲組名和key名的間隔。

 

3.4.3.   settings.py

須要設置如下內容:

#使用scrapy_redis調度器

SCHEDULER = "scrapy_redis.scheduler.Scheduler"

 

#使用scrapy_redis的去重處理器

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

 

#不清理Redis隊列

SCHEDULER_PERSIST = True

若是這一項爲True,那麼在Redis中的URL不會被Scrapy_redis清理掉,這樣的好處是:爬蟲中止了再從新啓動,它會從上次暫停的地方開始繼續爬取。可是它的弊端也很明顯,若是有多個爬蟲都要從這裏讀取URL,須要另外寫一段代碼來防止重複爬取。

若是設置成了False,那麼Scrapy_redis每一次讀取了URL之後,就會把這個URL給刪除。這樣的好處是:多個服務器的爬蟲不會拿到同一個URL,也就不會重複爬取。但弊端是:爬蟲暫停之後再從新啓動,它會從新開始爬。

 

#redis服務器地址,主機寫本地,從機寫遠程IP

REDIS_HOST = "192.168.199.129"

#redis端口

REDIS_PORT = 6379

 

其餘設置(可選)

爬蟲請求的調度算法

爬蟲的請求調度算法,有三種狀況可供選擇:

1.隊列

SCHEDULER_QUEUE_CLASS='scrapy_redis.queue.SpiderQueue'

若是不配置調度算法,默認就會使用這種方式。它實現了一個先入先出的隊列,先放進Redis的請求會優先爬取。

2.棧

SCHEDULER_QUEUE_CLASS='scrapy_redis.queue.SpiderStack'

這種方式,後放入到Redis的請求會優先爬取。

3.優先級隊列

SCHEDULER_QUEUE_CLASS='scrapy_redis.queue.SpiderPriorityQueue'

這種方式,會根據一個優先級算法來計算哪些請求先爬取,哪些請求後爬取。這個優先級算法比較複雜,會綜合考慮請求的深度等各個因素。

 

3.4.4.   pipeline

原本就是一個分離的組件,想改就改,不改也沒問題。

scrapy-redis自帶的pipeline是將items寫入redis數據庫中的items中。

前面聲明的redis_key = ‘sinanewsspider:start_urls’

提供了組名,完整的key名爲sinanewsspider:items

 

3.5.    運行

  1. 在主從機上都運行爬蟲,爬蟲進入等待狀態,

由於都要去redis數據庫的sina_news:start_urls中取連接,但如今沒有該KEY,因此都在等待;

  1. 在redis數據庫中添加初始爬取地址:

lpush sinanewsspider:start_urls http://news.sina.com.cn/guide/

  1. 而後就開始爬取了,在案例中設定了一個全局參數pages_max_num限制二級解析的YIELD次數,以此來限定總爬取頁面數;
  2. 爬取完成,主從爬蟲都進入等待狀態。

 

3.6.    理解

scrapy-redis在數據庫中新增三個key,dupefilter,items,requests:

  1. items:很好理解,存放item數據;
  2. dupefilter:
  3. requests:請求,

 

我的理解:爲了實現分佈式爬蟲,須要一個跨平臺的信息傳遞,目前是經過redis的遠程訪問知足這一點,至於爲何是redis而不是其它數據庫或中間件那是另一個問題了;

下一個問題是傳遞什麼信息,最簡單也是數據量最小的是傳遞url地址,但這樣功能不夠豐富,scrapy-redis放進去的是requests;

總之具體實現就是scrapy-redis把請求放到redis數據庫中,爬蟲去數據庫中拿到請求,爬取,再把結果放到items中。

關於dupefilter,它是用於去重,看上去是hash的結果,相似於指紋。

 

4.      scrapy-redis原理理解

4.1.    scrapy-redis分佈式爬取實現原理

由於官方文檔沒什麼內容,下面的內容取自網絡及我的理解。

從爬蟲實現的過程來說,爬蟲分佈式最容易實現的方式是共享請求,也就是「待爬隊列」;

從爬蟲總體的合理設計來說,爬蟲要作的事就是獲得請求,去重,採集,存儲數據四部分,下面一一解釋scrapy-redis的實現方法。

 

  1. 請求發起/獲取

怎麼發起就不廢話了,scrapy是從爬取隊列中獲取請求的,它具體的數據結構就是python自帶的collection.deque,固然是改造事後的啦(後面所說到的deque均是指scrapy改造以後的隊列)。

scrapy-redis提供了一個分佈式解決方法,把deque換成redis數據庫,在同一個redis服務器寫/讀要爬取的request,這樣就讓多個spider共享了請求。

問題來了,換了redis來存放隊列,怎麼去發起/獲取請求?

scrapy中作這個事的是調度器「Scheduler」,它負責對新request入列(加入deque),取出待爬取的request(從deque中出殯)等操做。

另外,在scrapy中,Scheduler爲deque提供了一個比較高級的組織方法,它把待爬隊列按照優先級創建了一個字典結構,好比:

{

priority0:隊列0

priority1:隊列2

priority2:隊列2

}

而後根據request中的priority屬性,來決定該入哪一個隊列。而出列時,則按priority較小的優先出列。爲了管理這個比較高級的隊列字典,Scheduler須要提供一系列的方法。

這樣作的結果就是若是換了redis作隊列,scrapy下的Scheduler就用不了,須要重寫。

待爬隊列+調度器解決了,分佈式爬蟲也就基本能夠運行了。

 

2.去重

爬蟲有一個重要的功能是去重,scrapy中用集合解決;scrapy-redis用dupefilter存放請求的指紋,在進行調度前作對比。

 

3.採集

請求的格式與接口不變,這一部分也不須要變化,與scrapy沒什麼不一樣。

 

4數據存儲

分佈式爬取帶來的一個問題是數據在不一樣的主機上,那麼理論上有兩種方法:

  1. 各存各的,master提供url,slave採集後存在本地,把title或指紋彙總,這樣好處是開銷小,問題是數據彙總麻煩;
  2. slave把採集到的數據實時發送到master或其它主機,這樣數據存放在一塊兒,slave僅作採集。

實際中 通常使用第二種方法,另備一臺mongodb/mysql服務器,用於採集數據存儲。

scrapy-redis的RedisPipeline將數據存入master的redis數據庫中。

 

5.      源碼分析

scrapy-redis官方文檔內容有限,要想理解其實現過程,仍是得看看源碼。

 

5.1.    connection.py

鏈接redis

有一個問題是沒有提供password參數的鏈接模式

在源碼中對於這一部分的處理以下:

url = kwargs.pop('url', None)

    if url:

        return redis_cls.from_url(url, **kwargs)

    else:

        return redis_cls(**kwargs)

它仍是調用redis模塊的函數去鏈接,能夠參照redis模塊中的格式redis://[:password]@localhost:6379/0

注意,在settings中的變量名須要作一個轉換:REDIS_URL

 

5.2.    pipelines.py

主要是將內容推動數據庫的items中,用於解析結果的共享,通常能夠寫入其它服務器數據庫,減小master的壓力。

 

    # 最主要的方法

    def process_item(self, item, spider):

        # 調用了一個線程方法

        return deferToThread(self._process_item, item, spider)

 

    def _process_item(self, item, spider):

        key = self.item_key(item, spider)

        # 序列化item

        data = self.serialize(item)

        # 將item贊成添加到redis隊列裏面, 存到主機

        self.server.rpush(key, data)

        return item

 

5.3.    queue.py

實現了三種隊列,

SpiderQueue = FifoQueue

SpiderStack = LifoQueue

SpiderPriorityQueue = PriorityQueue

LIFO,FIFO很好看懂,優先級隊列的實現沒看懂,調用redis的函數,須要詳細去看redis接口代碼。

 

5.4.    dupefilter.py

主要是實現了判重,根據源代碼來看,scrapy-redis在計算特徵碼時使用了scrapy自己的一個fingerprint接request_fingerprint。

特徵碼保存在redis的組名:dupefilter中。

 

5.5.    scheduler.py

爬取隊列管理,核心部分以下:

def enqueue_request(self, request):

        if not request.dont_filter and self.df.request_seen(request):

            self.df.log(request, self.spider)

            return False

        if self.stats:

            self.stats.inc_value('scheduler/enqueued/redis', spider=self.spider)

        self.queue.push(request)

        return True

 

    def next_request(self):

        block_pop_timeout = self.idle_before_close

        request = self.queue.pop(block_pop_timeout)

        if request and self.stats:

            self.stats.inc_value('scheduler/dequeued/redis', spider=self.spider)

        return request

 

5.6.    spider.py

不在使用scrapy原有的Spider類,重寫的RedisSpider繼承了Spider和RedisMixin這兩個類,RedisMixin是用來從redis讀取url的類。

相關文章
相關標籤/搜索