因为set无法排序和动态更新权重,而zset天然支持唯一成员、可变分数、有序读取,精准满足爬虫去重、优先级打分、按分取任务三大需求;zadd中nx用于首次入队防覆盖,xx用于已存在url的权重提升。

为什么不用 set 而要用 zset 实现去重+优先级?
爬虫去重只靠 set 会丢掉调度逻辑——它没法按分数排序,也没法动态更新 URL 权重。而 zset 天然支持「唯一成员 + 可变分数 + 有序读取」,正好对应「URL 去重 + 优先级打分 + 按分取任务」三个需求。用两个结构(set + list)模拟,反而要加锁、易出错、无法原子更新分数。
zadd 的 xx 和 nx 参数怎么选?
往 zset 添加新 URL 时,必须控制是否覆盖已有分数:
-
nx:仅当 member 不存在时添加,适合“首次发现才入队”,避免低优先级覆盖高优先级 -
xx:仅当 member 已存在时更新分数,适合“已入队 URL 权重提升”,比如页面内链被多次引用后提权 - 实际常用组合是:
zadd('queue', {url: score}, nx=True)初次入队;后续用zadd('queue', {url: new_score}, xx=True)动态调权
用 zpopmin 还是 zrange + zrem?
Redis 5.0+ 提供了原子命令 zpopmin,但要注意它的行为边界:
Python 3.14.2是Python编程语言在2025年12月5日发布的稳定版本,属于3.14系列的第二个维护更新。该版本包含了18项修复,重点解决了多进程、数据类及正则表达式等模块的回归问题,并修复了CVE-2025-12084等安全漏洞。此版本标志着自由线程模式(移除GIL)正式获得官方支持,是Python发展的重要里程碑。
-
zpopmin('queue', count=1)返回一个元组(member, score),不是字符串,需解包 - 如果返回空列表,说明队列为空,别直接解包否则报
ValueError - 老版本 Redis(zpopmin,得用
zrange('queue', 0, 0, withscores=True)查最小项,再用zrem('queue', member)删除——这两步非原子,需用 Lua 脚本或客户端加锁保证一致性
Python 客户端连接与异常处理关键点
redis-py 默认不自动重连,网络抖动会导致 ConnectionError 直接崩掉爬虫主循环:
- 初始化 client 时务必设
retry_on_timeout=True和health_check_interval=30 - 对
zpopmin等关键操作,套一层 try/except,捕获redis.ConnectionError和redis.ResponseError(比如命令不存在于旧版 Redis) - 不要在每次请求都新建 connection,复用一个全局
redis.Redis实例,但注意多线程下它本身是线程安全的,无需额外加锁
真正麻烦的是分数设计本身——同样一个 URL,不同来源(首页入口 vs 深层跳转)该给多少分,这个逻辑不在 Redis 里,而在你自己的业务规则里。Redis 只负责稳稳地存和取,别指望它帮你做决策。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










