一、增量爬取的思路:即保存上一次状态,本次抓取时与上次比对,如果不在上次的状态中,便视为增量,保存下来。对于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源码,欢迎感兴趣的朋友一块儿研究学习!