使用Scrapyd调度时,如何避免Scrapy Spider重复启动?
嘿,我之前也碰到过这个问题——给每个爬虫单独加锁太繁琐了,完全没必要。这里有几个更优雅的解决方案,帮你一次性搞定所有爬虫的单实例控制:
方案一:用Scrapy扩展实现全局锁(最灵活)
Scrapy的扩展(Extension)是专门用来做全局钩子逻辑的,能在爬虫启动/关闭时自动执行代码,完美适配这种全局锁需求。
步骤1:编写扩展类
在你的项目里新建一个extensions.py文件,写入以下代码:
from scrapy import signals from scrapy.exceptions import CloseSpider import os import fcntl class SingleInstanceExtension: def __init__(self, crawler): self.crawler = crawler self.lock_file = None self.lock_path = None @classmethod def from_crawler(cls, crawler): # 注册扩展,绑定启动/关闭信号 ext = cls(crawler) crawler.signals.connect(ext.on_spider_open, signal=signals.spider_opened) crawler.signals.connect(ext.on_spider_close, signal=signals.spider_closed) return ext def on_spider_open(self, spider): # 用爬虫名称生成唯一锁文件,确保每个爬虫独立控制 self.lock_path = f"{spider.name}.lock" self.lock_file = open(self.lock_path, 'w') try: # 尝试获取排他锁(非阻塞模式) fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) except BlockingIOError: # 拿不到锁说明已有实例在跑,直接关闭当前爬虫 self.crawler.engine.close_spider(spider, "另一个相同爬虫实例正在运行") raise CloseSpider("重复实例启动被拦截") def on_spider_close(self, spider): # 爬虫关闭时释放锁并清理文件 if self.lock_file: fcntl.flock(self.lock_file, fcntl.LOCK_UN) self.lock_file.close() os.remove(self.lock_path)
步骤2:注册扩展
在项目的settings.py里添加扩展配置:
EXTENSIONS = { '你的项目名.extensions.SingleInstanceExtension': 500, # 权重值只要不冲突就行 }
这样所有爬虫都会自动继承这个单实例控制逻辑,不用再逐个修改爬虫代码。
方案二:直接用Scrapyd配置(最简单)
其实Scrapyd本身就支持限制单个爬虫的并发进程数,完全不用写代码!
找到Scrapyd的配置文件scrapyd.conf(通常在/etc/scrapyd/或者项目根目录),添加或修改以下配置:
[scrapyd] max_proc_per_spider = 1
这个配置直接指定每个爬虫最多只能有1个进程在运行,Scrapyd会自动拦截重复的启动请求,省心又省力。
方案三:自定义爬虫基类(兼容旧项目)
如果你的项目已经有很多爬虫,不想动扩展配置,也可以写一个带锁逻辑的基类爬虫,让所有爬虫继承它:
from scrapy import Spider import os import fcntl from scrapy.exceptions import CloseSpider class SingleInstanceBaseSpider(Spider): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 初始化锁文件 self.lock_path = f"{self.name}.lock" self.lock_file = open(self.lock_path, 'w') try: fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) except BlockingIOError: raise CloseSpider("该爬虫已有实例在运行,启动失败") def closed(self, reason): # 爬虫结束时释放锁 fcntl.flock(self.lock_file, fcntl.LOCK_UN) self.lock_file.close() os.remove(self.lock_path) # 你的爬虫只要继承这个基类就行 class MySpider(SingleInstanceBaseSpider): name = "my-spider" # 原有的爬虫逻辑...
为什么你之前的方法没生效?
你之前是在爬虫类里直接创建锁文件,但每个爬虫实例都会生成自己的锁文件对象,没有利用文件排他锁的特性来阻止其他进程。上面的方案都是用fcntl.flock获取排他锁,确保同一时间只有一个进程能持有该锁,从根本上阻止重复实例启动。
内容的提问来源于stack exchange,提问作者MoreScratch




