布隆去重器
import redis
from hashlib import md5
class SimpleHash(object):
def __init__(self, cap, seed):
self.cap = cap
self.seed = seed
def hash(self, value):
ret = 0
for i in range(len(value)):
ret += self.seed * ret + ord(value[i])
return (self.cap - 1) & ret
class BloomFilter(object):
def __init__(self, host='localhost', port=6379, db=4, blockNum=1, key='bloomfilter'):
"""
:param host: the host of Redis
:param port: the port of Redis
:param db: witch db in Redis
:param blockNum: one blockNum for about 90,000,000; if you have more strings for filtering, increase it.
:param key: the key's name in Redis
"""
self.server = redis.Redis(host=host, port=port, db=db)
self.bit_size = 1 << 31 # Redis的String类型最大容量为512M,现使用256M
self.seeds = [5, 7, 11, 13, 31, 37, 61]
self.key = key
self.blockNum = blockNum
self.hashfunc = []
for seed in self.seeds:
self.hashfunc.append(SimpleHash(self.bit_size, seed))
def isContains(self, str_input):
if not str_input:
return False
m5 = md5()
m5.update(str_input.encode('utf-8'))
str_input = m5.hexdigest()
ret = True
name = self.key + str(int(str_input[0:2], 16) % self.blockNum)
for f in self.hashfunc:
loc = f.hash(str_input)
ret = ret & self.server.getbit(name, loc)
return ret
def insert(self, str_input):
m5 = md5()
m5.update(str_input.encode('utf-8'))
str_input = m5.hexdigest()
name = self.key + str(int(str_input[0:2], 16) % self.blockNum)
for f in self.hashfunc:
loc = f.hash(str_input)
self.server.setbit(name, loc, 1)
if __name__=='__main__':
""" 第一次运行时会显示 not exists!,之后再运行会显示 exists! """
bf = BloomFilter()
if bf.isContains('http://www.tencent.com'): # 判断字符串是否存在
print('exists!')
else:
print('not exists!')
bf.insert('http://www.tencent.com')
python redis 布隆过滤器
class BloomFilter(object):
"""
布隆过滤器客户端,具体的我就不实现了,可以百度。
网络上有很多基于redis的实现,稍作修改就可以用于此场景,下面假设几个接口
"""
def exist(self,fp,server,key):
"""
fp: 请求指纹
server: redis客户端连接
key: bloomfilter对应的redis中的键名
判断给定的指纹是否在布隆过滤器中。
有一点需要注意,我们的传参相较于一般的bloomfilter多了server和key,因为我们使用的server和key是不固定的,经常变化。
而一般的实现中server和key基本是不变的,直接作为类属性存储。
如果存在,返回True,否则,返回False
"""
pass
def insert(self,fp,server,key):
"""
参数含义和exist一致
将给定请求指纹加入到指定布隆过滤器中
"""
pass
class RFPDupeFilter(BaseDupeFilter):
"""
大部分跟这个实现不相关的内容我就省略了,
具体参考scrapy_redis的源码
"""
"""
假设我们已经利用from_crawler中获取了相关数据:
# bloomfilter粒度,'d'表示天,'m'表示月
self.filter_granularity='d'
# 去重周期,同时也是bloomfilter的数量
self.filter_num=7
并且初始化了bloomfliter
self.bloomfilter=BloomFilter()
"""
def get_bloomfilter_keys(self):
"""
根据当前时间,产生一个bloomfilter key的列表
假设当前时间为20181208,filter_granularity='d',filter_num=7,则返回['xxx:20181202','xxx:20181203',...,'xxx:20181208'],其中,'xxx'代表RFPDupeFilter的key,一般是 项目名:dupefilter 的形式
假设当前时间为20181208,filter_granularity='m',filter_num=6,则返回['xxx:201807','xxx:201808',...,'xxx:201812']
不难理解,就不实现了
"""
pass
def _exist(self,fp,key):
"""
判断给定指纹是否存在于给定key对应的bloomfilter中
"""
return self.bloomfilter.exsit(fp,self.server,key)
def exist(self,fp):
"""
判断给定指纹是否在bloomfilter中
"""
# 为了应对日期变化,比如从12.08到12.09过度之类的,需要每次都重新计算当前有效的bloomfilter的键名列表
bloomfilter_keys=self.get_bloomfilter_keys()
for key in bloomfilter:
# 任何一个存在就说明已经请求过,就可以退出了
if self._exist(self,fp,key):
return True
# 全都不存在,返回false
return False
def insert(self,fp):
"""
将给定指纹写入到布隆过滤器中
"""
# 获取当天时间对应的bloomfilter键名
key=self.get_bloomfilter_keys()[-1]
# 写入
self.bloomfilter.insert(fp,self.server,key)
def request_seen(self, request):
# 计算request的指纹
fp = self.request_fingerprint(request)
# 判断指纹是否已经存在
if self.exist(fp):
# 已存在
return True
# 不存在,加入到指纹集合中
self.insert(fp)
原去重
def request_seen(self, request):
"""Returns True if request was already seen.
Parameters
----------
request : scrapy.http.Request
Returns
-------
bool
"""
fp = self.request_fingerprint(request)
# This returns the number of values added, zero if already exists.
added = self.server.sadd(self.key, fp)
return added == 0
修改后
import time
# 过期时间,真正实现中应该写在settings部分,并通过`from_crawler`方法获取,这里为了说明方便,就随意一点了
# 过期时间设置成了30天
expire_time=60*60*24*30
def request_seen(self, request):
# 计算request的指纹
fp = self.request_fingerprint(request)
# 尝试获取redis中该指纹的过期时间
fp_expire_time=self.server.hget(self.key,fp)
# 如果指纹在散列表中并且已经过期,或者不在散列表中
if (fp_expire_time is None) or (int(fp_expire_time)<int(time.time())):
self.server.hset(self.key,fp,int(time.time())+expire_time)
return False
else:
return True