標題很唬人吧,標題黨就是本喵了哈哈哈哈哈哈哈。好了迴歸正題,在咱們進行一些爬蟲爬取數據的時候,若是保證去重呢,今天和你們聊一聊使用布隆過濾器去重。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)
複製代碼
好了 ,代碼也擼上來了,歡迎各位小夥伴一塊兒提意見哈哈哈哈哈哈,那麼今天就到這裏,我先撤!!!