scrapy使用布隆过滤器

前言

搜一下scrapy布隆过滤器其实已经有现成的了,不过我发现代码还有能优化的地方,这篇文章说一下可以优化的点

现成的布隆过滤器: https://github.com/Python3WebSpider/ScrapyRedisBloomFilter

优化细节

造成的问题

当数据量很大时,使用这个过滤器会阻塞掉数据采集,在与redis 交互的时候甚至大于采集和解析的时间

原因和优化点

代码里的去重逻辑主要是scrapy_redis_bloomfilter.dupefilter 这个文件,去重关键代码如下:

self.bf是一个布隆过滤器的实例。每执行一次bf.exists就会使用6个不同的hash函数计算出值,然后执行6次redis的getbit操作,判断相应的bit位。也就是说,bf.exists会和redis服务器进行6次交互操作,bf.insert也是6次,只是变成了setbit。(这里的6个指的是hash函数的个数,也就是设置的BLOOMFILTER_HASH_NUMBER 的默认值)

这就导致了去重一个url最少要和redis服务器交互6次(存在),最多是12次(不存在的情况)。如果网络差点或者数据量很大,那么去重所带来的网络开销会很大,同时也会卡住scrapy的异步逻辑,让scrapy变的很慢。

优化方式

redis其实已经实现了布隆过滤器,直接拿到用就行了,官方的优化肯定比咱们做的好。更重要的是,使用redis的布隆过滤器,只需要传一个url给redis服务端,服务端告诉你这个url存不存在,这样去重只需要和服务端进行一次交互就能完成。

python使用redis布隆过滤器

import redis

redis_url = "redis://127.0.0.1:6379"
server = redis.StrictRedis.from_url(redis_url, decode_responses=True)

bf = server.bf()
# redis key
key = "test"
# 错误率
errorRate = 0.001
# 去重数据量
capacity = 10000
if not server.exists(key):
    print(bf.create(key, errorRate, capacity))

for i in range(10):
    print(i, bf.add(key, f"http://www.httpbin.org/get?a={i}"))
for i in range(20):
    print(i, bf.add(key, f"http://www.httpbin.org/get?a={i}"))
for i in range(10):
    print(i, bf.exists(key, f"http://www.httpbin.org/get?a={i}"))

基本操作大概就是add和exists了,更多的命令看官方文档:https://redis.io/commands/?group=module。另外,python的接口和官方文档可能有点不一样,比如创建布隆过滤器,redis命令是BF.RESERVE,在python里的方法则是bf.create

要在redis中使用布隆过滤器,需要先加载一个插件,或者直接运行带布隆过滤器插件的docker,这个可以参考:docker启动带布隆过滤器的redis

我是直接使用docker,如果要在docker设置redis的密码,启动命令如下:

docker run -d -p 6379:6379 --name redisbloom redislabs/rebloom:latest \
/usr/local/bin/redis-server \
--appendonly yes \
--requirepass "123456" \
--loadmodule "/usr/lib/redis/modules/redisbloom.so"

--requirepass 后面跟的就是密码,/usr/lib/redis/modules/redisbloom.so 这个不用管,镜像里自带这个文件

代码

改动的地方比较少,用上面的bf替换掉代码的bf即可

完整代码请看:https://github.com/kanadeblisst/scrapy_redis_bf

使用

安装

pip install scrapy-redis-bf

使用

将如下设置加入到scrapy项目的settings.py文件中

SCHEDULER = "scrapy_redis_bf.scheduler.Scheduler"

DUPEFILTER_CLASS = "scrapy_redis_bf.dupefilter.RFPDupeFilter"

# 默认是通过spider的name来创建redis key
SCHEDULER_DUPEFILTER_ATTR = "name"

REDIS_URL = 'redis://localhost:6379'

# 错误率
BLOOMFILTER_ERRORRATE = 0.001
# 去重量
BLOOMFILTER_CAPACITY = 10000

SCHEDULER_DUPEFILTER_ATTR是设置redis键的字段,使用场景:一个网站可能需要写几个爬虫,spider name不一样,但是又想共用去重的redis键,这个时候可以给spider增加一个属性,比如叫site_name

然后设置SCHEDULER_DUPEFILTER_ATTR = "site_name",那么只要site_name相同的spider都会用一个redis键来去重

测试

GitHub项目的tests文件夹有个测试的scrapy项目,修改里面的REDIS_URL运行scrapy crawl test即可


请使用浏览器的分享功能分享到微信等