直接用 redis set 存 url 会撑爆内存且查询慢,应改用 redisbloom 模块实现布隆过滤器:先加载模块并预设 bf.reserve,再用 redisbloom 客户端调用 bfadd 原子判重,同时对 url 标准化处理。

为什么直接用 Redis 的 SET 存 URL 会撑爆内存?
一个中等规模爬虫每天抓 500 万 URL,每个 URL 平均长度 80 字节,光键名就占 400MB;加上 Redis 自身的内存开销和哈希表冗余,实际可能吃掉 1.2GB+ 内存。更麻烦的是,SET 每次 SISMEMBER 都要遍历或哈希查找,数据量大了延迟明显上升,还无法做误判率控制。
布隆过滤器(Bloom Filter)用固定内存、允许可控误判(比如 0.1%),换来了 O(k) 查询时间和常数级空间占用——这才是海量 URL 判重该走的路。
用 redisbloom 加载模块还是用 pybloom_live 客户端?
别自己手写布隆过滤器逻辑,也别用纯 Python 实现去连 Redis —— 性能差、序列化开销大、不支持原子操作。正确姿势是:在 Redis 服务端启用 RedisBloom 模块(v2.4+),然后用官方推荐的 redisbloom Python 客户端。
必须确认你的 Redis 已加载模块:
redis-cli MODULE LIST | grep -i bloom
没输出?那就得先下载 redisbloom.so,在 redis.conf 加 loadmodule /path/to/redisbloom.so,再重启 Redis。跳过这步,后面所有 BF.ADD 命令都会报 ERR unknown command。
Python 端只装这个:
pip install redisbloom
然后初始化客户端时显式启用 Bloom 命令:
from redisbloom.client import Client<br>r = Client(host='localhost', port=6379)<br># 不是 redis.Redis(),否则没有 bfAdd() 方法
BF.RESERVE 的 error_rate 和 capacity 怎么设才不翻车?
这两个参数不是“越大越好”,设错会导致内存浪费或误判爆炸。RedisBloom 的布隆过滤器是静态容量的,一旦建好不能扩容,所以 capacity 必须预估准确。
-
capacity:按你整个爬取周期预计去重 URL 总数 × 1.2(留点余量),比如预估 1 亿,就设120000000;设小了会提前饱和,后续BF.ADD返回0(表示已存在或插入失败),但此时误判率会飙升 -
error_rate:默认是0.01(1%),对爬虫来说太高了。建议设0.001(0.1%)或0.0001(0.01%)。注意:error_rate 每降一个数量级,内存约增 1.4 倍
建过滤器命令示例(在 redis-cli 或客户端里执行一次即可):
BF.RESERVE urls_bf 0.001 120000000
之后所有 URL 都往 urls_bf 这个 key 里塞,不要为每个种子域名建独立 BF key——key 越多,Redis 元信息开销越大,且无法共享容量。
判重逻辑里漏掉 BF.EXISTS 的返回值判断就是埋雷
很多人写成这样:
if r.bfExists('urls_bf', url):<br> continue<br>r.bfAdd('urls_bf', url)
问题在于:bfExists 返回 1 表示“极大概率存在”,返回 0 才表示“确定不存在”。但 bfAdd 成功返回 1,失败(比如容量满)也返回 0。如果只靠 bfExists 跳过,而 bfAdd 实际失败了,下次遇到同样 URL 还会误判为新 URL——漏去重就开始了。
安全写法是始终以 bfAdd 的返回值为准(它隐含了查询 + 插入原子操作):
if r.bfAdd('urls_bf', url) == 0:<br> # 插入失败 → 极大概率已存在,跳过<br> continue<br># 否则为新 URL,正常处理
注意:bfAdd 在 key 不存在时会自动创建(用默认参数),但参数和你之前 BF.RESERVE 设的不一致,容易导致误判率失控。所以务必先手动 RESERVE,别依赖自动创建。
另外,URL 要标准化后再进 BF:统一 scheme 小写、去掉末尾 /、解码 %20 等,否则 http://a.com/ 和 http://a.com 会被当成两个 URL。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











