Redis集合去重的核心逻辑是利用Set结构的唯一性和SADD命令的原子性:SADD返回1表示新增成功(URL未见过),返回0说明已存在,应跳过抓取;必须标准化URL、妥善处理异常、避免竞态与编码错误。

Redis集合去重的核心逻辑是什么
Python爬虫里用Redis去重,本质是把每个待抓取的url当成一个唯一元素,塞进Redis的Set结构里。Set天然不重复、插入快、查询快,比用本地set()或数据库查表靠谱得多——尤其当爬虫分布运行、或者重启后要延续状态时。
-
Set操作是原子的:SADD返回1表示新增成功(即该url没存过),返回0说明已存在 - 不要用
GET/SET模拟,会引发竞态:两个协程同时查“不存在”,然后都写入 - 别把整个
url原样当key,更别用Hash或String做去重主逻辑
怎么用redis-py安全判断并插入URL
用redis-py的spop或sismember都不如直接用sadd一步到位。关键不是“先查再插”,而是靠sadd的返回值做决策。
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
<p>def is_url_seen(url: str) -> bool:</p><h1>注意:这里用 sadd,不是 sismember + sadd 组合</h1><pre class='brush:python;toolbar:false;'>return r.sadd('seen_urls', url) == 0-
is_url_seen(url)返回True,代表这个url已经存在,跳过抓取 - 必须用
db参数明确指定数据库号,别依赖默认db=0——多个爬虫项目容易串库 - 如果
url带动态参数(比如utm_source、时间戳),得先标准化:用urllib.parse.urlparse和urlunparse剔除无关query字段,否则<a href="https://www.php.cn/link/79219948612cd052510d132c3071d1cc">https://www.php.cn/link/79219948612cd052510d132c3071d1cc</a>和<a href="https://www.php.cn/link/29ecd48788baa6e9046fde7219548b65">https://www.php.cn/link/29ecd48788baa6e9046fde7219548b65</a>会被当成两个URL
为什么有时候还是重复了?常见漏点
去重失效,十有八九不是Redis的问题,而是代码没兜住边界:
立即学习“Python免费学习笔记(深入)”;
- 爬虫用了多进程(
multiprocessing)但没共享Redis连接实例,每个子进程自己建连接,Set操作看似独立,实则都连到同一个db——这本身没问题;但如果用了ConnectionPool却没设max_connections,高并发下连接耗尽,部分请求失败,sadd抛异常没捕获,就跳过了去重逻辑 - 没处理
redis.ConnectionError或redis.TimeoutError,异常时直接跳过判断,当成“没看过”,导致重复抓 - 把
url当成bytes传给sadd,而Python 3里字符串默认是str,redis-py会自动编码,但若中间经过json.dumps或手动.encode()又解码错位,可能存了两份编码不同的同一url
要不要加过期时间?什么时候加
Set本身不支持单个元素过期,所以不能给某个url设TTL。但你可以:
- 用
EXPIRE给整个seen_urlskey设过期时间,比如r.expire('seen_urls', 3600<em>24</em>7),适合短期任务(如爬一周新闻),避免Set无限膨胀 - 更稳妥的做法是定期清理:用
SSCAN分批遍历+SREM,或直接DEL seen_urls后重建,前提是业务能接受少量重复(比如每天凌晨清空) - 别在每次
sadd后都调一次expire,那会频繁刷写,浪费性能
Redis去重看着简单,真正卡住人的,往往是URL标准化不彻底、异常没兜住、或者误以为sismember比sadd更“安全”——其实原子性才是关键。


















