写爬虫的时候需要解决的一个很重要的问题就是需要判断得来的新链接是否之前已经爬过。如果已经爬过,则没有必要再爬。我之前采用的是非常笨拙的方法,就是把所有已经爬过的地址放在mysql的一张表中,但是问题在于,这样每一个新链接到来,都会查询一次是否该链接在表格中。这样的话,单单查询过程就会耗费磁盘大量的读写性能。而且实际运行的过程中也发现,磁盘的读取速率是写入速率的10倍以上,这显然是不可接受的。
一个可行的改进方案是使用内存数据库作为mysql的缓存。这里选用的是redis,是一种键值型数据库。通过将需要频繁查询的内容读入Redis,这样可以直接通过redis查询是否该链接已经存在,从而避免了直接从磁盘读取数据,提高读写性能。
——————————资料库————————————
●redis官网 点我
●然而,redis官方并不支持windows(你们这是对软狗的歧视,哼!(╯‵□′)╯︵┻━┻)所以像我这样想在win下使用的,可以去找Microsoft Open Tech group在github下的一个64位版本。 点我点我
●基础设置教程:点我点我点我
●指令教程(bloomfilter主要通过setbit和getbit来实现)点我
——————————↑↑↑↑↑↑————————————
有个问题是,把所有数据都读入内存是不可行的,因为数据量太大,而内存太小。所以这就需要bloomfilter出场了. 简单的说,bloomfilter是通过多个hash函数,将字符串映射到不同的比特位。通过查询相应的比特位上的值是否全为1,来判断该数据是否已存入数据库中。值得注意的是,bloomfilter是有出错概率的。简单的说就是:
如果bloomfilter判断不存在,则一定不存在
如果bloomfilter判断存在,则可能不存在
看完发现简直是为爬虫量身定制啊有没有!对于爬虫而言,出错的代价微乎其微,大不了少爬几个就是了,反正不会重复爬。
bloomfilter介绍:这里
对于python2.7来说,好像有个叫pybloomfilter的库可以用。但那个是单线程的,当需要多线程合作时就不行了,而且不太适合3.4版本。所以我对网上别人写的针对3.4版本的小程序稍加改进,使之能够适合redis.
import redis
class SimpleHash():
def __init__(self,cap,seed):
self.cap=cap
self.seed=seed
def hash(self,value):
ret=0
for i in range(value.__len__()):
ret+=self.seed*ret+ord(value[i])
return ((self.cap-1) & ret)
class BloomFilter():
def __init__(self):
self.bit_size=1<<25
self.seeds=[5,7,11,13,31,37,61]
self.r=redis.StrictRedis(host='127.0.0.1',port=6379,db=0)
self.hashFunc=[]
for i in range(self.seeds.__len__()):
self.hashFunc.append(SimpleHash(self.bit_size,self.seeds[i]))
def isContains(self,str_input,name):
if str_input==None:
return False
if str_input.__len__()==0:
return False
ret=True
for f in self.hashFunc:
loc=f.hash(str_input)
ret=ret & self.r.getbit(name,loc)
return ret
def insert(self,str_input,name):
for f in self.hashFunc:
loc=f.hash(str_input)
self.r.setbit(name,loc,1)
uid=['alskdjflkasjdf','kajdsklfjlkasdf','lhjkkjhrwqer','alskdjflkasjdf']
bf=BloomFilter()
err_time=0
for id in uid:
if bf.isContains(id,'test'):
err_time+=1
else:
bf.insert(id,'test')
print(err_time)