在分布式爬虫开发中,去重 与任务调度是两大核心痛点。单机内存去重容量有限、无法跨节点共享;任务队列不统一则会导致重复抓取、效率低下。Redis 凭借高性能、丰富数据结构与分布式友好特性,成为爬虫去重与任务队列的首选中间件。本文从原理到代码,带你实现工业级 Redis 爬虫去重 + 任务队列实战。
一、为什么选 Redis 做爬虫中间件
- 高性能:内存读写,QPS 可达 10 万 +,秒级响应 URL 查重与入队出队
- 分布式:多爬虫节点共享同一套队列与去重库,天然支持横向扩展
- 数据结构适配:List 做队列、Set 做精确去重、Bitmap/BloomFilter 做海量去重
- 持久化:RDB+AOF 保证断电不丢任务、不丢去重记录
- 轻量易用:无复杂依赖,Python 通过 redis-py 即可快速接入
二、核心数据结构选型
1. 任务队列:List(双向链表)
- 命令:LPUSH 入队、RPOP 出队、BLPOP 阻塞取任务
- 优势:FIFO 严格有序、支持阻塞等待、原子操作防并发争抢
- 适用:待爬 URL 队列、失败重试队列
2. 精确去重:Set(集合)
- 命令:SADD 添加指纹、SISMEMBER 判断是否存在
- 优势:100% 精确、自动去重、O (1) 查询
- 适用:中小规模 URL 去重、重要业务必选
3. 海量去重:布隆过滤器(RedisBloom)
- 优势:1 亿 URL 仅占~120MB 内存,查询极快
- 适用:超大规模爬取、允许极低误判率
4. 统计去重量:HyperLogLog
- 优势:极小内存统计唯一值数量
- 适用:只统计不查重、监控总去重规模
三、环境准备
- 安装 Redis
- 安装 Python 依赖
bash
运行
pip install redis requests hashlib
- 连接测试
python
运行
import redis
r = redis.Redis(
host='localhost',
port=6379,
db=0,
decode_responses=True
)
print(r.ping())
四、实战一:Redis URL 精确去重(生产级)
去重流程
- 对 URL 做 SHA1 哈希生成指纹(缩短长度、统一格式)
- SISMEMBER 判断指纹是否在去重集合
- 不存在则 SADD 加入集合,允许爬取
- 存在则直接跳过
完整代码
python
运行
import hashlib
import redis
class RedisDupeFilter:
def __init__(self, redis_host='localhost', redis_port=6379, redis_db=0):
self.redis = redis.Redis(host=redis_host, port=redis_port, db=redis_db, decode_responses=True)
self.dupe_key = 'crawler:dupe:set'
def url_to_fingerprint(self, url):
# URL生成固定长度指纹
sha1 = hashlib.sha1()
sha1.update(url.encode('utf-8'))
return sha1.hexdigest()
def is_duplicated(self, url):
# 检查是否重复
fp = self.url_to_fingerprint(url)
return self.redis.sismember(self.dupe_key, fp)
def add(self, url):
# 添加到去重库
fp = self.url_to_fingerprint(url)
self.redis.sadd(self.dupe_key, fp)
# 测试
if __name__ == '__main__':
df = RedisDupeFilter()
url = 'https://www.baidu.com'
if df.is_duplicated(url):
print('已爬取,跳过:', url)
else:
df.add(url)
print('开始爬取:', url)
五、实战二:Redis 任务队列(生产者 - 消费者模型)
队列流程
- 生产者:解析页面→提取链接→去重校验→LPUSH 推入队列
- 消费者:BLPOP 阻塞取任务→执行爬取→新链接入队→循环
完整代码
python
运行
import time
import requests
from bs4 import BeautifulSoup
# 队列配置
QUEUE_KEY = 'crawler:task:queue'
# 生产者:加入任务
def producer(url):
if df.is_duplicated(url):
return
df.add(url)
r.lpush(QUEUE_KEY, url)
print(f'入队: {url}')
# 消费者:取任务并爬取
def consumer():
while True:
# 阻塞取任务,超时3秒
task = r.blpop(QUEUE_KEY, timeout=3)
if not task:
print('队列为空,等待任务...')
continue
url = task[1]
try:
print(f'开始爬取: {url}')
resp = requests.get(url, timeout=5)
soup = BeautifulSoup(resp.text, 'html.parser')
# 模拟提取链接
for a in soup.find_all('a', href=True):
link = a['href']
if link.startswith('http'):
producer(link)
print(f'爬取完成: {url}')
except Exception as e:
print(f'爬取失败: {url}, 错误: {e}')
# 失败重新入队
r.lpush(QUEUE_KEY, url)
time.sleep(1)
if __name__ == '__main__':
df = RedisDupeFilter()
# 启动入口
producer('https://www.baidu.com')
# 启动消费者
consumer()
六、分布式扩展:多节点协同
- 所有节点连接同一台 Redis
- 共用
crawler:dupe:set去重库 - 共用
crawler:task:queue任务队列 - 多节点同时运行 consumer,自动负载均衡
- 无重复、无遗漏、水平扩展
七、生产环境优化建议
- 指纹优化:对 URL 排序参数、去噪后再哈希
- 队列优化:使用 BLPOP 替代 RPOP,避免空轮询
- 去重优化:千万级 URL 改用布隆过滤器
- 持久化:开启 AOF,每秒刷盘
- 过期清理:给历史去重数据设置过期时间,避免内存膨胀
- 监控:
bash
运行
redis-cli scard crawler:dupe:set # 查看去重总量
redis-cli llen crawler:task:queue # 查看待爬任务数
八、总结
Redis 用List 做任务队列 、Set 做精确去重 、布隆过滤器做海量去重,是分布式爬虫的标准方案。本文代码可直接部署,支持单机 / 分布式切换,解决重复爬取、任务丢失、并发争抢三大问题。