python set仅限单机内存,无法共享、不持久、易爆内存;redis set支持分布式共享、持久化、原子去重(sadd返回1/0直接判断)、高性能(万级qps),且内置哈希表无需额外哈希,配合url规范化与连接池更可靠。

为什么不用 Python set 而要用 Redis Set?
Python 内置的 set 在单机小规模去重时够用,但爬虫一旦分布式部署或 URL 量级上千万,内存会爆、进程间无法共享、重启即丢数据。Redis SET 是原子性、持久化(可配)、跨进程/跨机器共享的,天然适合爬虫去重场景。
- 单条 URL 插入平均耗时约 0.1–0.3ms(局域网 Redis),吞吐轻松过万 QPS
-
SADD返回值为 1 表示新增,0 表示已存在,无需先SISMEMBER再插入,避免两次网络往返 - 不需要手动管理“是否已存在”的逻辑分支,靠返回值直接判断
如何用 redis-py 正确调用 SADD 做去重?
核心是用 redis.Redis().sadd(),不是 smembers() 或 sismember() —— 后者只查不写,性能差且非原子。
import redis
<p>r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
url = "<a href="https://www.php.cn/link/32b87d267730795171ae25b605e7cee3">https://www.php.cn/link/32b87d267730795171ae25b605e7cee3</a>"</p><h1>关键:只调一次 sadd,靠返回值判断</h1><p>if r.sadd("crawler:urls", url) == 1:
print("新 URL,可以抓取")</p><h1>这里放你的下载/解析逻辑</h1><p>else:
print("重复 URL,跳过")</p>
-
decode_responses=True必须加,否则返回 bytes,后续字符串操作易出错 - key 名建议带业务前缀(如
crawler:urls),避免多项目混用冲突 - 不要对 URL 做额外哈希(如
hashlib.md5(url.encode()).hexdigest())再存——Redis Set 本身已用哈希表实现,再哈希纯属浪费 CPU,还可能因哈希碰撞引入误判
URL 规范化处理必须在 sadd 前完成
原始 URL 可能含参数顺序不同、大小写差异、末尾斜杠、tracking 参数等,直接存会导致同一页面被当成多个 URL。
常见需统一的操作:
- 强制转小写(
url.lower()) - 去掉
utm_*、ref=等无意义参数(用urllib.parse解析后重建) - 标准化路径,如
/a/b/→/a/b(去掉末尾斜杠) - 对于有分页的列表页,可截断页码参数(如把
?page=2替换为?page=1)
没做这步,Redis Set 存了 10 万个看似不同实则重复的 URL,去重就失效了。
大流量下要注意连接和超时配置
默认 redis.Redis() 是阻塞式连接,高并发时容易卡住或超时。
- 加
socket_timeout=1和socket_connect_timeout=1(单位秒),防止某次慢请求拖垮整个爬虫线程 - 使用连接池:
redis.ConnectionPool(max_connections=20),避免频繁建连开销 - 如果用 Scrapy,建议封装成中间件;如果用 requests + 多线程,pool 必须复用,不能每次 new 一个 client
Redis 内存不是无限的,INFO memory 要定期看。URL 平均长度 80 字节 × 1 亿条 ≈ 8GB,别等 OOM 才想起配 maxmemory 和淘汰策略。
真正难的不是写那行 sadd,而是 URL 规范化是否覆盖全业务场景,以及 Redis 连接是否扛得住峰值流量。这两处出问题,日志里看不到明显报错,但去重率会悄悄掉到 70% 以下。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











