DownloaderAwarePriorityQueue 学习笔记
1. 简介
DownloaderAwarePriorityQueue
是 Scrapy 中一个高级的优先级队列实现,它不仅考虑请求的优先级,还会考虑下载器的负载情况。这个队列为每个域名(slot)维护独立的优先级队列,通过平衡不同域名的请求来优化爬虫性能。
1.1 主要特点
- 为每个域名维护独立的优先级队列
- 考虑下载器当前负载进行调度
- 支持请求优先级
- 支持持久化存储
1.2 使用限制
- 不支持
CONCURRENT_REQUESTS_PER_IP
设置 - 需要显式配置才能使用
- 需要更多的内存来维护多个队列
2. 核心组件
2.1 DownloaderInterface
class DownloaderInterface:
def __init__(self, crawler):
self.downloader = crawler.engine.downloader
def stats(self, possible_slots):
# 返回每个 slot 的活跃下载数
return [(self._active_downloads(slot), slot)
for slot in possible_slots]
def get_slot_key(self, request):
# 获取请求对应的 slot key
return self.downloader.get_slot_key(request)
def _active_downloads(self, slot):
# 获取指定 slot 的活跃下载数
if slot not in self.downloader.slots:
return 0
return len(self.downloader.slots[slot].active)
2.2 队列管理
class DownloaderAwarePriorityQueue:
def __init__(self, crawler, downstream_queue_cls, key, slot_startprios=None):
self._downloader_interface = DownloaderInterface(crawler)
self.pqueues = {} # slot -> priority queue 映射
3. 工作原理
3.1 请求入队流程
- 获取请求的 slot key(通常是域名)
- 检查该 slot 是否有对应的优先级队列
- 如果没有,创建新的优先级队列
- 将请求添加到对应的队列中
3.2 请求出队流程
- 获取所有 slot 的活跃下载数
- 选择负载最小的 slot
- 从该 slot 的队列中获取请求
- 如果队列为空,删除该 slot 的队列
4. 核心方法实现
4.1 push 方法
def push(self, request):
"""将请求添加到对应 slot 的优先级队列中"""
slot = self._downloader_interface.get_slot_key(request)
if slot not in self.pqueues:
self.pqueues[slot] = self.pqfactory(slot)
queue = self.pqueues[slot]
queue.push(request)
4.2 pop 方法
def pop(self):
"""从负载最小的 slot 中获取下一个请求"""
stats = self._downloader_interface.stats(self.pqueues)
if not stats:
return None
slot = min(stats)[1] # 获取负载最小的 slot
queue = self.pqueues[slot]
request = queue.pop()
if len(queue) == 0:
del self.pqueues[slot]
return request
5. 负载均衡策略
5.1 slot 选择
- 基于活跃下载数选择最空闲的域名
- 避免单个域名被过度请求
- 自动平衡不同域名的请求量
5.2 优化效果
- 防止对单个域名的并发请求过多
- 提高爬虫的整体效率
- 降低被反爬的风险
6. 使用场景
6.1 适用场景
- 需要抓取多个域名的爬虫
- 对抓取速度和效率有较高要求
- 需要控制对每个域名的请求频率
- 大规模分布式爬虫系统
6.2 不适用场景
- 只抓取单个域名的爬虫
- 对内存使用有严格限制的场景
- 需要使用
CONCURRENT_REQUESTS_PER_IP
的场景
7. 配置和使用
7.1 启用配置
# settings.py
SCHEDULER_PRIORITY_QUEUE = "scrapy.pqueues.DownloaderAwarePriorityQueue"
7.2 注意事项
- 确保
CONCURRENT_REQUESTS_PER_IP = 0
- 合理设置并发数
- 注意内存使用
- 监控队列状态
8. 性能考虑
8.1 优势
- 智能的负载均衡
- 自动的请求分配
- 防止域名过载
8.2 劣势
- 额外的内存开销
- 调度开销略大
- 配置要求较高
9. 最佳实践
9.1 使用建议
- 根据实际需求选择是否使用
- 合理配置并发参数
- 监控内存使用情况
- 定期检查队列状态
9.2 优化建议
- 合理设置请求优先级
- 适当调整并发数
- 实现自定义的负载均衡策略
- 定期清理空闲队列