用Redis存URL的MD5而非原始URL,因MD5长度固定、可标准化去重;但需先归一化URL(去斜杠、排序并解码query),再用SET的sismember+sadd原子操作判断插入,配合连接池防并发问题。
为什么用 Redis 存 URL 的 MD5 而不是原始 URL?
因为原始 URL 长度不固定,有的带动态参数、乱序 query、session_id,直接比对极易误判重复;MD5 固定 32 字符,标准化后去重更稳。但注意:
前必须统一处理 URL:去掉末尾斜杠、排序 query 参数、解码 URL 编码(
),否则相同语义的 URL 会算出不同哈希。
常见错误现象:
或去重失效——多半是没做 URL 归一化,或 Redis 连接未复用(每次新建连接导致性能崩)。
归一化示例:
和
必须转成同一串 MD5
别用
这种手写拼接,改用
+
安全重组
Redis 连接建议用单例或连接池(
),避免
爬虫
并发高时连接耗尽
如何用 Redis 的 SET 实现高效去重判断?
用
类型存 MD5,靠
判断是否已存在,再用
尝试插入——原子操作,天然防竞态。别用 GET/SET 组合,那是自己造 race condition。
性能影响明显:单次判断 + 插入平均 redis_client.expire('urls_seen', 604800),否则集合无限增长。
立即学习
“
Python免费学习笔记(深入)
”;
Python 3.14.3
微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。
下载
别把整个 URL 当 key(如
),key 长度超限且无法批量查
避免用
存 URL→状态映射,纯属浪费内存和 CPU
如果要支持清理旧数据,改用
+ 时间戳分值,但增量爬取通常不需要这么复杂
增量爬取时怎么保证“新 URL 不漏、旧 URL 不重”?
核心逻辑就一句:
先查 MD5 是否存在,不存在才请求 + 解析 + 入库 + 写入 Redis
。关键在“解析”环节——新页面里提取的子链接,必须同样走一遍归一化 + MD5 +
流程,不能跳过。
容易踩的坑是“只对种子 URL 做去重,子链接放行”。结果就是首页抓 10 次,每次都把全站链接又扫一遍。还有人把去重逻辑写在入库后,导致重复请求已失效的 URL(比如 404 页面反复抓)。
所有待爬 URL(无论来源)必须在
前完成去重校验
如果用 Scrapy,把去重逻辑塞进
和
里的
之前
Redis 写失败(如网络抖动)不能静默吞掉,得 fallback 到内存 set() 或抛异常中断,否则漏去重
Redis 挂了或网络超时怎么办?
别让整个爬虫停摆。加一层简单容错:
,捕获后降级到本地
缓存本次运行的 MD5,同时打日志告警。但注意:本地 set 只能保本次进程内去重,重启即丢,所以必须标清楚“降级模式”,避免误以为长期可靠。
另一个现实问题是 Redis 内存爆了——如果爬的站点 URL 量极大(千万级),
内存占用线性增长,这时得考虑布隆过滤器(
或 RedisBloom 模块),但会引入误判率(
)。普通中小规模爬取,坚持用
最省心。
别在 except 里 sleep(1) 后重试,可能拖慢整个爬虫节奏
本地降级的
必须是模块级变量,不能每次函数调用都新建
如果用 Docker 部署,检查 Redis 容器内存 limit,
看实际 RSS 占用
事情说清了就结束。真正难的不是写那几行
,而是 URL 归一化的边界 case——比如带 fragment 的 URL 是否该去掉
,比如大小写敏感的域名要不要强制小写。这些细节不抠准,Redis 再快也白搭。
hashlib.md5(url.encode()).hexdigest()urllib.parse.unquoteredis.exceptions.ConnectionErrorhttps://a.com/?b=1&a=2https://a.com/?a=2&b=1url.strip('/') + '?' + '&'.join(sorted(query_pairs))urllib.parse.urlunparseurllib.parse.parse_qsredis.ConnectionPoolSETredis_client.sismember('urls_seen', md5)redis_client.sadd('urls_seen', md5)redis_client.get(url)HSETzsetsismemberrequests.get()start_requestsparseresponse.follow()try/except redis.ConnectionErrorset()SETpyreBloomfalse positiveSETseen_urls = set()docker statssismember#xxx