You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Scrapy爬虫爬完所有指定URL后手动停止?解决重启错误

解决Scrapy爬虫爬完URL后出现ReactorNotRestartable错误的方案

首先,咱们先拆解下你遇到的问题核心:twisted.internet.error.ReactorNotRestartable本质是Twisted的反应器(reactor)是单例实例,一旦停止就无法再次启动。而你的代码里可能存在重复启动reactor的情况,再加上Spider的继承逻辑有小问题,导致爬取流程异常,最终触发了这个错误。

第一步:修正Spider的继承逻辑

你当前的CoreSpider继承了scrapy.Spider,但却使用了rules属性——这是CrawlSpider专属的特性,普通Spider不会自动解析和应用这些规则,这会导致你的爬取逻辑不符合预期,甚至无法正常结束。

修正后的Spider代码:

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class CoreSpider(CrawlSpider):
    name = "final"
    custom_settings = {
        # 注意这里用布尔值而非字符串,Scrapy会自动解析
        'ROBOTSTXT_OBEY': False,
        'HTTPCACHE_ENABLED': True,
        'LOG_ENABLED': False,
        'DOWNLOADER_MIDDLEWARES': {
            'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110,
            'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
            'random_useragent.RandomUserAgentMiddleware': 320
        },
    }
    
    def __init__(self):
        # 必须调用父类构造函数,否则CrawlSpider的规则逻辑不会生效
        super(CoreSpider, self).__init__()
        self.all_ngrams = get_ngrams()
        self.search_term = ""
        self.start_urls = self.read_url()
        self.rules = (
            Rule(LinkExtractor(unique=True), callback='parse', follow=True, process_request='process_request'),
        )
    
    def read_url(self):
        # 示例:从文本文件读取起始URL
        with open("your_urls.txt", "r") as f:
            return [line.strip() for line in f if line.strip()]
    
    def parse(self, response):
        # 这里写你的页面解析逻辑
        pass
    
    def process_request(self, request, spider):
        # 自定义请求处理逻辑
        return request

第二步:正确启动爬虫,避免重复启动Reactor

不管用CrawlerProcess还是CrawlerRunner,核心原则是只启动一次reactor,且确保爬虫结束后不会触发重复启动的逻辑。

方案1:使用CrawlerRunner+inlineCallbacks(推荐)

这种方式能更优雅地控制爬虫生命周期,确保reactor只启动一次:

from scrapy.crawler import CrawlerRunner
from scrapy.utils.project import get_project_settings
from twisted.internet import reactor, defer
from your_spider_module import CoreSpider

@defer.inlineCallbacks
def run_crawl():
    runner = CrawlerRunner(get_project_settings())
    # 启动爬虫,等待爬取完成
    yield runner.crawl(CoreSpider)
    # 爬虫完成后停止reactor
    reactor.stop()

if __name__ == "__main__":
    run_crawl()
    # 启动reactor,直到被停止
    reactor.run()

方案2:使用CrawlerProcess

如果偏好这种方式,确保只调用一次process.start(),且脚本不会重复执行启动逻辑:

from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
from your_spider_module import CoreSpider

if __name__ == "__main__":
    process = CrawlerProcess(get_project_settings())
    process.crawl(CoreSpider)
    # 启动爬虫,阻塞直到完成
    process.start()

第三步:手动触发爬虫停止(按需)

如果需要在爬完所有URL后主动停止爬虫,可以通过监听spider_idle信号,检查请求队列是否为空来触发停止:

from scrapy.signals import spider_idle

class CoreSpider(CrawlSpider):
    # ... 其他代码 ...
    
    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        spider = super(CoreSpider, cls).from_crawler(crawler, *args, **kwargs)
        # 绑定爬虫空闲信号的处理函数
        crawler.signals.connect(spider.handle_spider_idle, signal=spider_idle)
        return spider
    
    def handle_spider_idle(self, spider):
        # 检查调度器是否还有待处理的请求
        if not self.crawler.engine.slot.scheduler.has_pending_requests():
            # 主动关闭爬虫
            self.crawler.engine.close_spider(spider, reason="All URLs have been crawled successfully")

关键注意事项

  1. 不要在同一个脚本中多次调用reactor.run()或process.start(),这是触发ReactorNotRestartable的最常见原因。
  2. 确保Spider继承正确的父类:用rules就必须继承CrawlSpider,否则规则不会生效。
  3. 避免在模块级代码中直接启动爬虫(比如process.start()写在模块顶层),否则当模块被重复导入时会重复启动reactor。

内容的提问来源于stack exchange,提问作者Om Prakash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:23:49