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><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6591" title="Li Python Sec Check"><img
src="https://img.php.cn/upload/skill/000/000/081/179102166033725.jpg" alt="Li Python Sec Check" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill6591" title="Li Python Sec Check" class="overflowclass">Li Python Sec Check</a>
<p class="overflowclass">Python 安全规范检查工具:基于 CloudBase 规范、腾讯安全指南,LLM 智能分析(默认禁用,优先本地执行)</p>
</div>
<a rel="nofollow" href="/xiazai/skill6591" title="Li Python Sec Check" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><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字段,否则@#@#@#@#@#@#@#@#@#@0和@#@#@#@#@#@#@#@#@#@1会被当成两个URL
为什么有时候还是重复了?常见漏点
去重失效,十有八九不是Redis的问题,而是代码没兜住边界:
- 爬虫用了多进程(
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更“安全”——其实原子性才是关键。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










