天天看點

Scrapy如何借助于BloomFilter實作增量爬取

一、增量爬取的思路:即儲存上一次狀态,本次抓取時與上次比對,如果不在上次的狀态中,便視為增量,儲存下來。對于scrapy來說,上一次的狀态是抓取的特征資料和上次爬取的 request隊列(url清單),request隊列可以通過request隊列可以通過scrapy.core.scheduler的pending_requests成員得到,在爬蟲啟動時導入上次爬取的特征資料,并且用上次request隊列的資料作為start url進行爬取,不在上一次狀态中的資料便儲存。

二、選用BloomFilter原因:對爬蟲爬取資料的儲存有多種形式,可以是資料庫,可以是磁盤檔案等,不管是資料庫,還是磁盤檔案,進行掃描和存儲都有很大的時間和空間上的開銷,為了從時間和空間上提升性能,故選用BloomFilter作為上一次爬取資料的儲存。儲存的特征資料可以是資料的某幾項,即監控這幾項資料,一旦這幾項資料有變化,便視為增量持久化下來,根據增量的規則可以對儲存的狀态資料進行限制。比如:可以選網頁更新的時間,索引次數或是網頁的實際内容,cookie的更新等,BloomFilter的概念和原理可以參照:

http://blog.csdn.net/jiaomeng/article/details/1495500

BloomFilter的windows版和linux版的api接口有差别,故分開闡述:

三,執行個體代碼解析:

1、windows版:

classEbayContentPipeline(object):

   def __init__(self):

        brandName = GlobalVar.get_brand()

        self.file =codecs.open(brandName + time.strftime('%Y-%m-%d %X',time.localtime()).replace(':','-')+".json",'w',encoding='utf-8')

   #打開spider時,将bloomfilter加載

   def open_spider(self,spider):

        brandName = GlobalVar.get_brand()

        isexists = os.path.exists(brandName+'.blm')

        if isexists == True:

            self.bf =BloomFilter.fromfile(open(brandName+'.blm','rb'))

        else:

            self.bf = BloomFilter(100000000,0.001)

   def process_item(self,item, spider):

        line = json.dumps(dict(item),ensure_ascii=False) + "\n"

        if spider.name in['ebay_content']:

            if item['goodsprice_dollar'] == [] or item['goodsname']== []:

                #不做存儲操作,直接傳回

                return item

            else:

                #按商品名字和商品價格作為增量的基準

                token = item['goodsname'][0]+item['goodsprice_dollar'][0]

               m = hashlib.md5()

                m.update(token)

                encodeStr = m.hexdigest()

                flag = self.bf.add(encodeStr)

             #目前item沒有在bloomfilter中,便将其收集下來,視為增量

                if flag == False:

                    self.file.write(line)

                return item

   def close_spider(self,spider):

        self.file.close()

        brandName = GlobalVar.get_brand()

        self.bf.tofile(open(brandName+ '.blm','wb'))

增量處理主要在pipeline中處理,本例中在spider打開時,判斷布隆檔案是否存在,存在即從檔案打開形成bloomfilter,不存在便直接建立bloomfilter,在爬蟲關閉時需要儲存狀态,将布隆過濾器所存的狀态資料持久化到磁盤,供下一次使用。本例的增量規則選用了商品的名稱和商品的價格,即下次爬取到了不在上一次的商品或是上一次的商品價格在本次爬取時價格發生了變化,就認為是增量,需要将該部分資料儲存下來。為了壓縮存儲将商品名稱和商品價格組成的特征token串,采用md5加密,壓縮成128bit(16byte)的格式串作為該條資料的狀态特征,放入BloomFilter儲存。

BloomFilter檔案的大小與放入串本身大小沒有必然的關系,主要跟初始化建立時的容量參數有關,bloomfilter存儲的是m個長度的位數組,不是特征串本身,故調整串的大小對bloomfilter檔案的大小沒有影響。注意采用md5加密時,每次加密都要重新生成md5對象,保證同一個字串生成的md5串一樣,如果用一個md5對象來update同一個串,加密後生成的封包是不同的。

2、linux版

import codecs

importjson

importos

importtime

frompybloomfilter import BloomFilter

importscrapy.signals

fromtwisted.internet import defer, reactor

importglobalvar as GlobalVar

importsys

reload(sys)

sys.setdefaultencoding('utf-8')

class EbaycontentPipeline(object):

   def __init__(self):

        brandName = GlobalVar.get_brand()

        self.file = codecs.open(brandName +time.strftime('%Y-%m-%d %X',time.localtime()).replace(':','-')+".json",'w',encoding='utf-8')

   def open_spider(self,spider):

        brandName = GlobalVar.get_brand()

        isexists =os.path.exists(brandName+'.bloom')

        if isexists == True:

                self.bf =BloomFilter.open(brandName+'.bloom')

        else:

                self.bf =BloomFilter(100000000,0.001,brandName+'.bloom')

   def process_item(self, item, spider):

        line = json.dumps(dict(item),ensure_ascii=False) + "\n"

        if spider.name in['ebay_content_increase']:

            if item['goodsprice_dollar'] == []or item['goodsname'] == []:

                return item

            else:

                token =item['goodsname'][0]+item['goodsprice_dollar'][0]

                flag = self.bf.add(token)

                if flag == False:

                    self.file.write(line)

                return item

   def close_spider(self,spider):

        self.file.close()

後續将剖析scrapy源碼,歡迎感興趣的朋友一塊兒研究學習!