You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用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

火山引擎 最新活动