一、url去重的几种方式

1.使用 Java 的 Set 集合判重

Set 集合天生具备不可重复性,使用它只能存储值不相同的元素,如果值相同添加就会失败,因此我们可以通过添加 Set 集合时的结果来判定 URL 是否重复

redis的set去重是去key吗 redis set去重_java

通过研究java的set集合底层源码可以知道,Set底层是Hash来实现的,Hash类型是散列,所以是无序的。以传入的值作为key值,PERSENT作为value来存储到map中,如果key值相同,将会覆盖,这就是set为什么能去重的原因(key相同会覆盖)。

2.Redis Set 集合去重

Redis Set 集合是一个无序的、自动去重的集合数据类型,Set底层用两种数据结构存储,一个是hashtable,一个是inset,

通俗地讲使用 Redis 的 Set 集合的实现思路和 Java 中的 Set 集合思想思路是一致的,都是利用 Set 的不可重复性实现的;

redis的set去重是去key吗 redis set去重_布隆过滤器_02

从上述结果可以看出,当添加成功时表示 URL 没有重复,但添加失败时(结果为 0)表示此 URL 已经存在了。

3.数据库去重

我们也可以借助数据库实现 URL 的重复判断,首先我们先来设计一张 URL 的存储表,如下图所示:

redis的set去重是去key吗 redis set去重_java_03

4.唯一索引去重

我们也可以使用数据库的唯一索引来防止 URL 重复,它的实现思路和前面 Set 集合的思想思路非常像。

5.Guava 布隆过滤器去重

我们以网络爬虫为例。网络间的链接错综复杂,爬虫程序在网络间“爬行”很可能会形成“环”。为了避免形成“环”,程序需要知道已经访问过网站的URL。当程序又遇到一个网站,根据它的URL,怎么判断是否已经访问过呢?

第一个想法就是将已有URL放置在 HashSet中,然后利用 HashSet的特性进行判断。它只花费O(1)的时间。但是,该方法消耗的内存空间很大,就算只有1亿个URL,每个URL只算50个字符,就需要大约5GB内存。

如何减少内存占用呢?URL可能太长,我们使用MD5等单向哈希处理后再存到HashSet中吧,处理后的字段只有128Bit,这样可以节省大量的空间。我们的网络爬虫程序又可以继续执行了。

但是好景不长,网络世界浩瀚如海,URL的数量急速增加,以128bit的大小进行存储也要占据大量的内存。

这种情况下,我们还可以使用 BitSet,使用哈希函数将URL处理为1bit,存储在BitSet中。但是,哈希函数发生冲突的概率比较高,若要降低冲突概率到1%,就要将 BitSet的长度设置为URL个数的100倍。

但是冲突无法避免,这就带来了误判。理想中的算法总是又准确又快捷,但是现实中往往是“一地鸡毛”。我们真的需要100%的正确率吗?如果需要,时间和空间的开销无法避免;如果能够忍受低概率的错误,就有极大地降低时间和空间的开销的方法。

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

redis的set去重是去key吗 redis set去重_java_04

6.Redis 布隆过滤器去重

除了 Guava 的布隆过滤器,我们还可以使用 Redis 的布隆过滤器来实现 URL 判重。在使用之前,我们先要确保 Redis 服务器版本大于 4.0(此版本以上才支持布隆过滤器),并且开启了 Redis 布隆过滤器功能才能正常使用。

redis的set去重是去key吗 redis set去重_python_05

误差率的体现?

7.scrapy里面的指纹去重

Scrapy 有自动去重功能,它的去重使用了 Python 中的集合。这个集合记录了 Scrapy 中每个 Request 的指纹,这个指纹实际上就是 Request 的散列值。利用集合元素的不重复特性来实现 Request 的去重。

8.scrapy_redis共享的集合去重

Scrapy 的去重是利用集合来实现的,而scrapy-redis的去重就需要利用共享的集合,只不过是将指纹池储存到了redis中。

9.scrapy+布隆过滤器对接的使用

①安装scrapy-redis-bloomfilter 模块:pip install scrapy-redis-bloomfilter
②配置setting文件:
scrapy+布隆过滤器的使用

Ensure use this Scheduler

SCHEDULER = “scrapy_redis_bloomfilter.scheduler.Scheduler”

把去重模块更改为scrapy-redis-bloomfilter写好的模块

DUPEFILTER_CLASS = “scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter”

Redis URL

REDIS_URL = ‘redis://localhost:6379/0’

散列函数的个数,个人偏向设置为10,不设置则默认为6,

BLOOMFILTER_HASH_NUMBER = 6

Bloom Filter的bit参数,默认30(一亿级指纹池)

BLOOMFILTER_BIT = 30

Persist

SCHEDULER_PERSIST = True
DUPEFILTER_CLASS 是去重类,如果要使用 BloomFilter 需要将 DUPEFILTER_CLASS 修改为该包的去重类。
BLOOMFILTER_HASH_NUMBER 是 BloomFilter 使用的哈希函数的个数,默认为 6,可以根据去重量级自行修改。
BLOOMFILTER_BIT 即前文所介绍的 BloomFilter 类的 bit 参数,它决定了位数组的位数,如果 BLOOMFILTER_BIT 为 30,那么位数组位数为 2 的 30 次方,将占用 Redis 128MB 的存储空间,去重量级在 1 亿左右,即对应爬取量级 1 亿左右。如果爬取量级在 10 亿、20 亿甚至 100 亿,请务必将此参数对应调高。

二、优缺点

redis的set去重是去key吗 redis set去重_java_06

三、总结

其中 Redis Set、Redis 布隆过滤器、数据库和唯一索引这 4 种解决方案适用于分布式系统,如果是海量的分布式系统,建议使用 Redis 布隆过滤器来实现 URL 去重,如果是单机海量数据推荐使用 Guava 的布隆器来实现 URL 去重。

参考地址:①.scrapy去重原理,scrapy_redis去重原理和布隆过滤器的使用
②.url去重的六种方案
③java set去重底层源码解析