根据给定的 判断依据 和给定的 去重容器,将原始数据逐一进行判断,判断去重容器中是否有该数据。如果没有那就把该数据对应的判断依据添加去重容器中,同时标记该数据是不重复数据;如果有就不添加,同时标记该数据是重复数据。
重点:
理解:1、节省存储空间 2、比对更加快捷高效
临时去重容器:指如利用 list、set 等编程语言的数据结构存储去重数据,一旦程序关闭或重启后,去重容器中的数据就被回收了。使用与实现简单方便;但无法共享、无法持久化。
持久化去重容器指如利用 redis、mysql 等数据库存储去重数据。持久化、共享;但使用与实现相对复杂
信息摘要 hash 算法指可以将任意长度的文本、字节数据,通过一个算法得到一个固定长度的文本。如 MD5(128位)、SHA1(160位) 等。
特征:只要源文本不同,计算得到的结果,必然不同(摘要)。 摘要:摘要算法主要用于比对信息源是否一致,因为只要源发生变化,得到的摘要必然不同;而且通常结果要比源短很多,所以称为
摘要
。正因此,利用信息摘要算法能大大降低去重容器的存储空间使用率,并提高判断速度,直由于其强唯—性的特征,几乎不存在误判。
注意: hash 算法得出的结果其实本质上就是一串数值,如 md5 的128位指的是二进制的长度,十六进制的长度是32位。一个十六进制等于四个二进制。
基类的实现:
# 基于信息摘要算法进行数据的去重判断和存储
# 1. 基于内存的存储
# 2. 基于redis的存储
# 3. 基于mysql的存储
import hmac
class BaseFilter(object):
'''基于信息摘要算法进行数据的去重判断和存储'''
def __init__(self,
digest_mod="MD5",
redis_host="localhost",
redis_port=6379,
redis_db=0,
redis_key="filter",
mysql_url=None,
mysql_table_name="filter"):
self.redis_host = redis_host
self.redis_port = redis_port
self.redis_db = redis_db
self.redis_key = redis_key
self.mysql_url = mysql_url
self.mysql_table_name = mysql_table_name
# self.key =
self.digest_mod = digest_mod
self.storage = self._get_storage()
def _safe_data(self, data):
'''
:param data: 给定的原始数据
:return: 二进制类型的字符串数据
'''
if isinstance(data, bytes):
return data
elif isinstance(data, str):
return data.encode()
else:
raise Exception("please provide a string...")
def _get_hash_value(self, data):
'''
根据给定的数据,返回的对应信息摘要hash值
:param data: 给定的原始数据(二进制类型的字符串数据)
:return: hash值
'''
data = self._safe_data(data)
key = 'amoxiang666'.encode("utf8")
hash_obj = hmac.new(key, data, digestmod=self.digest_mod)
hash_value = hash_obj.hexdigest()
return hash_value
def save(self, data):
'''
根据data计算出对应的指纹进行存储
:param data: 给定的原始数据
:return: 存储的结果
'''
hash_value = self._get_hash_value(data)
return self._save(hash_value)
def _save(self, hash_value):
'''
存储对应的hash值(交给对应的子类去继承)
:param hash_value: 通过信息摘要算法求出的hash值
:return: 存储的结果
'''
pass
def is_exists(self, data):
'''
判断给定的数据对应的指纹是否存在
:param data: 给定的原始数据
:return: True or False
'''
hash_value = self._get_hash_value(data)
return self._is_exists(hash_value)
def _is_exists(self, hash_value):
'''
判断对应的hash值是否已经存在(交给对应的子类去继承)
:param hash_value: 通过信息摘要算法求出的hash值
:return: 判断的结果(True or False)
'''
pass
def _get_storage(self):
'''
返回对应的一个存储对象(交给对应的子类去继承)
:return:
'''
pass
from .mysql_filter import MySQLFilter
from .memory_filter import MemoryFilter
from .redis_filter import RedisFilter
普通内存版本:
# 基于python中的 集合数据结构进行去重判断依据的存储
from . import BaseFilter
class MemoryFilter(BaseFilter):
'''基于python中的 集合数据结构进行去重判断依据的存储'''
def _get_storage(self):
return set()
def _save(self, hash_value):
'''
利用set进行存储
:param hash_value:
:return:
'''
return self.storage.add(hash_value)
def _is_exists(self, hash_value):
if hash_value in self.storage:
return True
return False
Redis 持久化版:
# 基于redis的持久化存储的去重判断依据的实现
import redis
from . import BaseFilter
class RedisFilter(BaseFilter):
'''基于redis的持久化存储的去重判断依据的实现'''
def _get_storage(self):
'''返回一个redis连接对象'''
pool = redis.ConnectionPool(host=self.redis_host, port=self.redis_port, db=self.redis_db)
client = redis.StrictRedis(connection_pool=pool)
return client
def _save(self, hash_value):
'''
利用redis的无序集合进行存储
:param hash_value:
:return:
'''
return self.storage.sadd(self.redis_key, hash_value)
def _is_exists(self, hash_value):
'''判断redis对应的无序集合中是否有对应的判断依据'''
return self.storage.sismember(self.redis_key, hash_value)
MySQL 持久化版本:
# 基于mysql的去重判断依据的存储
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from . import BaseFilter
Base = declarative_base()
class MySQLFilter(BaseFilter):
'''基于mysql的去重判断依据的存储'''
def __init__(self, *args, **kwargs):
self.table = type(
kwargs["mysql_table_name"],
(Base,),
dict(
__tablename__=kwargs["mysql_table_name"],
id=Column(Integer, primary_key=True),
hash_value=Column(String(40), index=True, unique=True)
)
)
BaseFilter.__init__(self, *args, **kwargs)
def _get_storage(self):
'''返回一个mysql连接对象(sqlalchemy的数据库连接对象)'''
engine = create_engine(self.mysql_url)
Base.metadata.create_all(engine) # 创建表、如果有就忽略
Session = sessionmaker(engine)
return Session
def _save(self, hash_value):
'''
利用redis的无序集合进行存储
:param hash_value:
:return:
'''
session = self.storage()
filter = self.table(hash_value=hash_value)
session.add(filter)
session.commit()
session.close()
def _is_exists(self, hash_value):
'''判断redis对应的无序集合中是否有对应的判断依据'''
session = self.storage()
ret = session.query(self.table).filter_by(hash_value=hash_value).first()
session.close()
if ret is None:
return False
return True
测试:
# python3
from information_summary_filter import MemoryFilter
from information_summary_filter import RedisFilter
from information_summary_filter import MySQLFilter
# filter = MemoryFilter()
# filter = RedisFilter(redis_key="test1")
mysql_url = "mysql+pymysql://root:[email protected]:3306/db_admin?charset=utf8"
filter = MySQLFilter(mysql_url=mysql_url, mysql_table_name="test20230319")
data = ["111", "qwe", '222', "333", "111", "qwe", "中文", "qwer"]
for d in data:
if filter.is_exists(d):
print("发现重复的数据: ", d)
else:
filter.save(d)
print("保存去重的数据:", d)
Simhash 算法是一种局部敏感哈希算法,能实现 相似 文本内容的去重。比如下列两篇新闻数据:
与信息摘要算法的区别:
Simhash 值比对:通过两者的 simhash 值的二进制位的差异来表示原始文本内容的差异。差异个数又被称为 海明距离。
注意:Simhash 对长文本
500字+
比较适用,短文本可能偏差较大。在google的论文给出的数据中,64位simhash值,在海明距离为3的情况下,可认为两篇文档是相似的或者是重复的。当然这个值只是参考值,针对自己的应用可能有不同的测试取值。
Python 实现的 simhash 算法。该模块得出的 simhash 值长度正是64位。https://github.com/leonsim/simhash
如对比前面列出的人民网和中国网的两篇相似新闻(以下值仅供参考)。128位MD5值:
64位simhash值:
import re
from simhash import Simhash, SimhashIndex
def get_features(s):
width = 3
s = s.lower()
s = re.sub(r'[^\w]+', '', s)
return [s[i:i + width] for i in range(max(len(s) - width + 1, 1))]
data = {
"key1": u'How are you? I Am fine. blar blar blar blar blar Thanks.',
"key2": u'How are you i am fine. blar blar blar blar blar than',
"key3": u'This is simhash test.',
"4": u'How are you i am fine. blar blar blar blar blar thank'
}
objs = [
(str(k), Simhash(get_features(v))) for k, v in data.items()
]
print(objs)
index = SimhashIndex(objs, k=4) # k相当于海明距离
print(index.bucket_size()) # 11
s1 = Simhash(get_features(u'How are you i am fine. blar blar blar blar blar thank'))
print(index.get_near_dups(s1)) # ['1']
index.add('4', s1)
print(index.get_near_dups(s1)) # ['4', '1']
# 二进制位的比对 只能在内存中进行
# 序列化工具: 将一个对象转换为二进制的一个数据
# 反序列化:二进制--> 对象
Python 实现的内存版布隆过滤器 pybloom,参考地址:https://github.com/jaybaird/python-bloomfilter
手动实现的 redis 版布隆过滤器,示例代码如下:
# -*- coding: utf-8 -*-
# @Time : 2023-03-23 23:28
# @Author : AmoXiang
# @File : test.py
# @Software: PyCharm
# @Blog : https://blog.csdn.net/xw1680
# 布隆过滤器 redis版本实现
import hashlib
import redis
import six
# 1. 多个hash函数的实现和求值
# 2. hash表实现和实现对应的映射和判断
class MultipleHash(object):
'''根据提供的原始数据,和预定义的多个salt,生成多个hash函数值'''
def __init__(self, salts, hash_func_name="md5"):
self.hash_func = getattr(hashlib, hash_func_name)
if len(salts) < 3:
raise Exception("please give three salt at least....")
self.salts = salts
def get_hash_values(self, data):
'''根据提供的原始数据, 返回多个hash函数值'''
hash_values = []
for i in self.salts:
hash_obj = self.hash_func()
hash_obj.update(self._safe_data(data))
hash_obj.update(self._safe_data(i))
ret = hash_obj.hexdigest()
hash_values.append(int(ret, 16))
return hash_values
def _safe_data(self, data):
'''
:param data: 给定的原始数据
:return: 二进制类型的字符串数据
'''
if isinstance(data, bytes):
return data
elif isinstance(data, str):
return data.encode()
else:
raise Exception("please give string....") # 建议使用英文来描述
class BloomFilter(object):
def __init__(self, salts, redis_host="localhost", redis_port=6379, redis_db=0, redis_key="bloomfilter"):
self.redis_host = redis_host
self.redis_port = redis_port
self.redis_db = redis_db
self.redis_key = redis_key
self.client = self._get_redis_client()
self.multiple_hash = MultipleHash(salts)
def _get_redis_client(self):
'''返回一个redis连接对象'''
pool = redis.ConnectionPool(host=self.redis_host, port=self.redis_port, db=self.redis_db)
client = redis.StrictRedis(connection_pool=pool)
return client
def save(self, data):
''''''
hash_values = self.multiple_hash.get_hash_values(data)
for hash_value in hash_values:
offset = self._get_offset(hash_value)
self.client.setbit(self.redis_key, offset, 1)
return True
def is_exists(self, data):
hash_values = self.multiple_hash.get_hash_values(data)
for hash_value in hash_values:
offset = self._get_offset(hash_value)
v = self.client.getbit(self.redis_key, offset)
if v == 0:
return False
return True
def _get_offset(self, hash_value):
# 2**8 = 256
# 2**20 = 1024 * 1024
# (2**8 * 2**20 * 2*3) 代表hash表的长度 如果同一项目中不能更改
return hash_value % (2 ** 8 * 2 ** 20 * 2 * 3)
if __name__ == '__main__':
data = ["asdfasdf", "123", "123", "456", "asf", "asf"]
bm = BloomFilter(salts=["1", "2", "3", "4"], redis_host="172.17.0.2")
for d in data:
if not bm.is_exists(d):
bm.save(d)
print("映射数据成功: ", d)
else:
print("发现重复数据:", d)