标题很唬人吧,标题党就是本喵了哈哈哈哈哈哈哈。好了回归正题,在咱们进行一些爬虫爬取数据的时候,若是保证去重呢,今天和你们聊一聊使用布隆过滤器去重。html
首先什么是布隆过滤器呢,让咱们依旧来看看百度百科。python
看傻了吧,其实我刚开始也给直接干懵了。实际上上边所说的二进制向量就是一个位数组(好比redis中就提供了一个2^32长度的位数组,大小是512MB),而一系列随机映射函数实际上就是多个哈希函数。那么先和你们来扯扯哈希吧redis
哈希函数通常也叫散列函数,就是能够把任意长度的输入,经过散列算法,变成固定长度数输出,输出的结果就是散列值。常见的哈希函数有md五、sha一、sha256等,也能够将哈希函数理解成一种加密手段,可是哈希函数是不可逆的,就是你不能够经过输出域获得散列值逆向推出输入域的值。(由于它里边使用很厉害的科学计算致使它不可逆。)而后说说哈希的几个特性吧算法
哈希函数的用途很是很是多,举个栗子数组
看这个,当你下载完英雄联盟的时候,如何肯定你完整的下载成功了?dom
这个时候官方会把这个文件丢到哈希函数(md5)中加密,获得如上图所示的散列值,而后当你下载完成的时候,把你下载好的文件用一样的哈希函数加密,若是获得的值和官方所提供的值同样的话,这时候你绝对是完整的下载了这个文件。这种文件校验使用哈希函数比较常见。ide
因此我这样说大家应该听得懂吧(小声bb....)函数
嗯!既然你们都懂了那么接下来进入主题说说布隆过滤器 (来自本喵不要脸的自问自答哈哈哈哈哈哈~)大数据
首先来讲说布隆过滤器的流程ui
我猜你看完以后确定有一些懵逼,这个时候你把位数组想象成一张白纸
而后假设当前尚未添加过数据,你经过了4个哈希函数而且取模获得了4个值(这4个值是必定在这个 位数组长度范围以内的,由于进行了取模操做),这4个值就在这张白纸的某些位置,把这4个位置涂黑
因此当你添加了好多数据的时候,这张白纸会有好多的小黑点(我就不画了,画的丑,还累。。。。)
如何判断重复呢,当你经过多个哈希计算并取模得出来的值所对应的位置在这张纸上都是黑的,这时候就认为这条数据是重复的。只要有一个位置为白,那么就不重复,继续添加,把位置为白的涂黑,已是黑色的位置就不用管了。
因此判断数据重复只须要看看位数组中对应的下标位置是否都为1,都为1,就重复,这条数据就能够不要了。
我估摸着大家 都看明白了吧......emmmm无论大家明不明白,我接着说!
当咱们采用布隆过滤器进行去重的时候,是必定存在失误率的(由于哈希碰撞的特性),可是可以保证重复的数据必定能判断出来!(这个意思就是会有很小的几率当你数据没有重复的时候,我就说你重复了!就不让你添加数据!义正词严!!!)
而且布隆过滤器还有个特色,就是它只关注数据量的多少,不考虑单个数据样本的大小(这个由哈希的性质1,输入域无限,输出域有限决定的),因此布隆过滤器对于大数据大样本的海量数据去重特别有效。
这个时候就会发现这个失误率和位数组的长度m、和哈希函数的个数k息息相关。这个时候如何设计布隆过滤器呢?不用担忧!已经有科学家帮咱们研究出了公式,咱们只须要根据本身样本数量和预计失误率计算出你须要的东西啦!就是这么得劲!
经过样本数据个数n,和失误率p,能够计算出m(位数组的长度)
根据m、n,计算哈希函数的个数k
经过m、k、n的值,计算出实际真实的失误率
所获得的数组长度和函数个数都向上取整,好比计算出来的k是8.23个 ,那么就直接取9个哈希函数,这样失误率也会比预期的有所降低
因此说,布隆过滤器能够经过很小的代价来进行大量数据的去重,大概就这样!
扯蛋扯了这么多,也该上代码了哈!
位数组,我这里就直接使用redis中提供的bit了哈,首先须要多个不一样的哈希函数(这个是在麻省理工上找的哈)
#
#**************************************************************************
#* *
#* General Purpose Hash Function Algorithms Library *
#* *
#* Author: Arash Partow - 2002 *
#* URL: http://www.partow.net *
#* URL: http://www.partow.net/programming/hashfunctions/index.html *
#* *
#* Copyright notice: *
#* Free use of the General Purpose Hash Function Algorithms Library is *
#* permitted under the guidelines and in accordance with the MIT License. *
#* http://www.opensource.org/licenses/MIT *
#* *
#**************************************************************************
def rs_hash(key):
a = 378551
b = 63689
hash_value = 0
for i in range(len(key)):
hash_value = hash_value * a + ord(key[i])
a = a * b
return hash_value
def js_hash(key):
hash_value = 1315423911
for i in range(len(key)):
hash_value ^= ((hash_value << 5) + ord(key[i]) + (hash_value >> 2))
return hash_value
def pjw_hash(key):
bits_in_unsigned_int = 4 * 8
three_quarters = (bits_in_unsigned_int * 3) / 4
one_eighth = bits_in_unsigned_int / 8
high_bits = 0xFFFFFFFF << int(bits_in_unsigned_int - one_eighth)
hash_value = 0
test = 0
for i in range(len(key)):
hash_value = (hash_value << int(one_eighth)) + ord(key[i])
test = hash_value & high_bits
if test != 0:
hash_value = ((hash_value ^ (test >> int(three_quarters))) & (~high_bits))
return hash_value & 0x7FFFFFFF
def elf_hash(key):
hash_value = 0
for i in range(len(key)):
hash_value = (hash_value << 4) + ord(key[i])
x = hash_value & 0xF0000000
if x != 0:
hash_value ^= (x >> 24)
hash_value &= ~x
return hash_value
def bkdr_hash(key):
seed = 131 # 31 131 1313 13131 131313 etc..
hash_value = 0
for i in range(len(key)):
hash_value = (hash_value * seed) + ord(key[i])
return hash_value
def sdbm_hash(key):
hash_value = 0
for i in range(len(key)):
hash_value = ord(key[i]) + (hash_value << 6) + (hash_value << 16) - hash_value;
return hash_value
def djb_hash(key):
hash_value = 5381
for i in range(len(key)):
hash_value = ((hash_value << 5) + hash_value) + ord(key[i])
return hash_value
def dek_hash(key):
hash_value = len(key);
for i in range(len(key)):
hash_value = ((hash_value << 5) ^ (hash_value >> 27)) ^ ord(key[i])
return hash_value
def bp_hash(key):
hash_value = 0
for i in range(len(key)):
hash_value = hash_value << 7 ^ ord(key[i])
return hash_value
def fnv_hash(key):
fnv_prime = 0x811C9DC5
hash_value = 0
for i in range(len(key)):
hash_value *= fnv_prime
hash_value ^= ord(key[i])
return hash_value
def ap_hash(key):
hash_value = 0xAAAAAAAA
for i in range(len(key)):
if (i & 1) == 0:
hash_value ^= ((hash_value << 7) ^ ord(key[i]) * (hash_value >> 3))
else:
hash_value ^= (~((hash_value << 11) + ord(key[i]) ^ (hash_value >> 5)))
return hash_value
复制代码
接下来才是bloom的实现了
from BloomFilter.GeneralHashFunctions import *
import redis
import pickle
import base64
class BloomFilterRedis(object):
# 哈希函数列表
hash_list = [rs_hash, js_hash, pjw_hash, elf_hash, bkdr_hash, sdbm_hash, djb_hash, dek_hash]
def __init__(self, key, host='127.0.0.1', port=6379):
self.key = key
self.redis = redis.StrictRedis(host=host, port=port, charset='utf-8')
def random_generator(self, hash_value):
''' 将hash函数得出的函数值映射到[0, 2^32-1]区间内 '''
return hash_value % (1 << 32)
def do_filter(self, item, save=True):
""" 过滤,判断是否存在 :param item: :return: """
flag = True # 默认存在
for hash in self.hash_list:
# 计算哈希值
hash_value = hash(item)
# 获取映射到位数组的下标值
index_value = self.random_generator(hash_value)
# 判断指定位置标记是否为 0
if self.redis.getbit(self.key, index_value) == 0:
# 若是不存在须要保存,则写入
if save:
self.redis.setbit(self.key, index_value, 1)
flag = False
return flag
class Stu(object):
def __init__(self, name, age):
self.name = name
self.age = age
if __name__ == '__main__':
bloom = BloomFilterRedis("bloom_obj")
# 对对象进行去重,必须实现序列化
data = pickle.dumps(Stu("xiaohong", 18))
data1 = base64.b64encode(data).decode()
ret = bloom.do_filter(data1)
print(ret)
data = pickle.dumps(Stu("xiaohong", 18))
data1 = base64.b64encode(data).decode()
ret = bloom.do_filter(data1)
print(ret)
bloom = BloomFilterRedis("bloom_url")
ret = bloom.do_filter("http://www.baidu.com")
print(ret)
ret = bloom.do_filter("http://www.baidu.com")
print(ret)
复制代码
好了 ,代码也撸上来了,欢迎各位小伙伴一块儿提意见哈哈哈哈哈哈,那么今天就到这里,我先撤!!!