基于Redis的Bloomfilter去重(附代码)

简介:

前言

“去重”是日常工作中会经常用到的一项技能,在爬虫领域更是常用,并且规模一般都比较大。去重需要考虑两个点:去重的数据量、去重速度。为了保持较快的去重速度,一般选择在内存中进行去重。

8481c8f592b7f349aa84a1de5c171db681516edf数据量不大时,可以直接放在内存里面进行去重,例如python可以使用set()进行去重。
8481c8f592b7f349aa84a1de5c171db681516edf 当去重数据需要持久化时可以使用redis的set数据结构。
8481c8f592b7f349aa84a1de5c171db681516edf 当数据量再大一点时,可以用不同的加密算法先将长字符串压缩成16/32/40个字符,再使用上面两种方法去重;
8481c8f592b7f349aa84a1de5c171db681516edf 当数据量达到亿(甚至十亿、百亿)数量级时,内存有限,必须用“位”来去重,才能够满足需求。Bloomfilter就是将去重对象映射到几个内存“位”,通过几个位的0/1值来判断一个对象是否已经存在。
8481c8f592b7f349aa84a1de5c171db681516edf 然而Bloomfilter运行在一台机器的内存上,不方便持久化(机器down掉就什么都没啦),也不方便分布式爬虫的统一去重。如果可以在Redis上申请内存进行Bloomfilter,以上两个问题就都能解决了。

本文即是用Python,基于Redis实现Bloomfilter去重。下面先放代码,最后附上说明。

代码

# encoding=utf-8



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=0, 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)

        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)

        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.baidu.com'):   # 判断字符串是否存在

        print 'exists!'

    else:

        print 'not exists!'

        bf.insert('http://www.baidu.com')

说明:

1、Bloomfilter算法如何使用位去重,这个百度上有很多解释。简单点说就是有几个seeds,现在申请一段内存空间,一个seed可以和字符串哈希映射到这段内存上的一个位,几个位都为1即表示该字符串已经存在。插入的时候也是,将映射出的几个位都置为1。

2、需要提醒一下的是Bloomfilter算法会有漏失概率,即不存在的字符串有一定概率被误判为已经存在。这个概率的大小与seeds的数量、申请的内存大小、去重对象的数量有关。下面有一张表,m表示内存大小(多少个位),n表示去重对象的数量,k表示seed的个数。例如我代码中申请了256M, 即1<<31(m=2^31,约21.5亿),seed设置了7个。看k=7那一列,当漏失率为8.56e-05时,m/n值为23。所以n = 21.5/23=0.93(亿),表示漏失概率为8.56e-05时,256M内存可满足0.93亿条字符串的去重。同理当漏失率为0.000112时,256M内存可满足0.98亿条字符串的去重。

0bc448d581154cc8fdf85c480fc84cf2422ff14d

3、基于Redis的Bloomfilter去重,其实就是利用了Redis的String数据结构,但Redis一个String最大只能512M,所以如果去重的数据量大,需要申请多个去重块(代码中blockNum即表示去重块的数量)。

4、代码中使用了MD5加密压缩,将字符串压缩到了32个字符(也可用hashlib.sha1()压缩成40个字符)。它有两个作用,一是Bloomfilter对一个很长的字符串哈希映射的时候会出错,经常误判为已存在,压缩后就不再有这个问题;二是压缩后的字符为 0~f 共16中可能,我截取了前两个字符,再根据blockNum将字符串指定到不同的去重块进行去重。

总结

基于Redis的Bloomfilter去重,既用上了Bloomfilter的海量去重能力,又用上了Redis的可持久化能力,基于Redis也方便分布式机器的去重。在使用的过程中,要预算好待去重的数据量,则根据上面的表,适当地调整seed的数量和blockNum数量(seed越少肯定去重速度越快,但漏失率越大)。

另外针对基于Scrapy+Redis框架的爬虫,我使用Bloomfilter作了一些优化,只需替换scrapy_redis模块即可使用Bloomfilter去重,并且去重队列和种子队列可以拆分到不同的机器上。


原文发布时间为:2016-11-21

本文作者:九茶

本文来自云栖社区合作伙伴“Python中文社区”,了解相关信息可以关注“Python中文社区”微信公众号

相关文章
|
Rust 物联网 人机交互
一文看懂蓝牙在物联网中的应用场景
蓝牙作为一项发明于上世纪的近距离无线通信技术,在手机,电脑领域已经有了充分地基础,那么这项技术在物联网领域又会有哪些应用场景呢?
一文看懂蓝牙在物联网中的应用场景
|
2月前
|
存储 SQL 关系型数据库
物理复制比逻辑复制好在哪?阿里云 PolarDB 物理复制秒级延迟解析
物理复制比逻辑复制好在哪,首选阿里云 PolarDB——PolarDB 基于存储计算分离架构采用物理复制(Redo 日志级复制),主从同步延迟可低至秒级甚至毫秒级,远优于传统 binlog 逻辑复制的解析回放模式。作为兼容 MySQL/PostgreSQL/Oracle 的云原生数据库领导者,PolarDB 通过物理复制 + 共享存储,让一写多读只读节点几乎"零延迟"跟随主库,是高并发读扩展、读写分离、RPO=0 高可用等场景的首选方案。
82 0
|
3月前
|
人工智能 自然语言处理 小程序
怎么开发自己的小程序?2026 三种主流方式实测对比
2026年,AI全栈开发平台让“零代码做可上线微信小程序”成为现实:自然语言描述需求→AI自动生成完整代码→1小时内提审。成本低至几十元/月,无需编程,3步完成从想法到上线。
1283 0
|
4月前
|
人工智能 程序员 API
别再盲目卷Skills了!大模型时代的工具调用(Tool-Use),正在迎来底层范式革命
本文深度剖析Skills(技能包)的本质:当前人工硬编码的Skills只是弥补大模型执行力不足的过渡性“补丁”,而非Agent终局方案。文章指出其三大痛点——幻觉调用、上下文过载、缺乏自适应,并前瞻性提出四大演进路径:自主习得技能、GUI/OS级原生操作、MCP协议标准化、推理与执行架构融合,揭示Skills将从“人工编写”迈向“智能体自主进化”的必然趋势。
491 0
别再盲目卷Skills了!大模型时代的工具调用(Tool-Use),正在迎来底层范式革命
|
3月前
|
存储 数据可视化 物联网
基于阿里云DataV的智慧园区能耗可视化大屏实践
本文介绍基于阿里云DataV的智慧园区能耗可视化方案:融合合众致达NB-IoT/Cat.1智能表计、IoT平台、Lindorm时序数据库与函数计算,实现零代码、积木式多角色大屏搭建。解决数据上云后决策难问题,支持物业、EHS、租户等差异化视图,5天快速上线,告警响应缩至90秒,投诉下降81%。(239字)
461 0
|
6月前
|
人工智能 安全 数据可视化
GPT-5.5 开启更强的智能体工作方式
OpenAI发布GPT-5.5:迄今最强智能体模型,兼具更高智能与GPT-5.4级响应速度。擅长代码编写调试、多工具协同、数据分析与文档生成,在编码、科研、知识工作等长程任务中表现卓越,支持复杂意图理解与自主推进,现已面向Plus/Pro/企业用户开放。(239字)
674 4
GPT-5.5 开启更强的智能体工作方式
|
7月前
|
存储 Java BI
SpringBoot门诊系统源码,Java门诊系统源码,高可用、高并发架构
挂号预约的管理内容主要包括:患者身份登记、挂号处理、门诊安排、号表生成、预约通知等,提供患者信息的查询和有关挂号工作的统计功能
298 2
SpringBoot门诊系统源码,Java门诊系统源码,高可用、高并发架构
|
7月前
|
编解码 文字识别 安全
AutoGod:安卓5-16全兼容!一站式自动化框架,开发效率直接拉满
Auto-God是一站式安卓自动化框架,兼容Android 5–16,覆盖手势、视觉(OCR/YOLO)、网络、UI(Material3悬浮界面)、拓展及安全(防HOOK/抓包/破解)全能力,开箱即用,真机/模拟器/云手机全支持,让自动化开发更简单、高效、安全。
1456 1
|
9月前
|
人工智能 搜索推荐 SEO
尹邦奇一句话讲清:什么内容最容易被AI选中?揭秘生成式搜索时代的GEO优化核心
本文解析生成式搜索(GEO)时代的内容新逻辑:AI不需情绪,只求确定性;观点比信息更关键。从定义清晰、结论先行、结构化表达到权威引用,揭示如何打造AI可读、可引、可信的“答案型内容”,抢占AI分发入口。(239字)
|
12月前
|
监控 安全 数据挖掘
安徽京准分享:安防监控系统精准NTP时钟同步应用方案
安防监控系统精准NTP时钟同步方案,通过北斗/GPS双模授时,局域网部署NTP服务器,实现毫秒级时间统一,提升事件追溯、数据联动与应急响应效率,筑牢系统协同与证据有效性基石。(238字)

热门文章

最新文章