Scrapy重复爬取报错ReactorNotRestartable,FastAPI集成求助
解决FastAPI集成Scrapy时
ReactorNotRestartable错误,支持多次爬取 问题本质
twisted.internet.error.ReactorNotRestartable错误的核心原因是:Twisted的Reactor是单实例且不可重启的。你的代码中每次调用/Produtos接口都会创建CrawlerProcess并执行process.start(),该方法默认会启动Reactor,爬取完成后自动停止Reactor。第二次请求时,Reactor已处于停止状态,无法再次启动,因此触发错误。
解决方案
针对这个问题,我们需要调整Scrapy的集成方式,适配FastAPI的异步环境,同时避免Reactor重启问题:
- 用
CrawlerRunner替代CrawlerProcess:CrawlerRunner不会自动启动/停止Reactor,更适合在已有异步框架(如FastAPI)中使用。 - 移除全局状态传递:放弃
recebeTermo这类全局函数,改为通过Spider构造函数传入参数,避免并发请求的状态污染。 - 整合Twisted与asyncio事件循环:使用
AsyncioSelectorReactor让Twisted复用FastAPI的asyncio事件循环,避免循环冲突。 - 直接收集爬取数据:跳过本地文件存储,在内存中收集爬取结果,减少IO开销和并发冲突。
修改后的代码实现
第一步:调整Scrapy Spider(produtosWeg.py)
修改Spider,使其通过构造函数接收搜索关键词,替代原有的全局参数传递逻辑:
# server/lib/ProdsWeg/ProdsWeg/spiders/produtosWeg.py import scrapy class CatalogoSpider(scrapy.Spider): name = "catalogo" def __init__(self, produto=None, *args, **kwargs): super().__init__(*args, **kwargs) self.produto = produto # 根据关键词构造起始URL(替换为你的实际URL规则) self.start_urls = [f"https://example.com/search?q={self.produto}"] def parse(self, response): # 你的爬取逻辑,返回item对象 yield { "product_name": response.css(".product-title::text").get(), "product_price": response.css(".product-price::text").get(), # 其他需要爬取的字段... }
第二步:修改FastAPI主文件
from fastapi import FastAPI, Response from scrapy.crawler import CrawlerRunner from scrapy.utils.log import configure_logging from server.lib.ProdsWeg.ProdsWeg.spiders.produtosWeg import CatalogoSpider from server.lib.ProdsWeg.ProdsWeg.spiders.ServerMongo import ConectMongo import json import random from twisted.internet import reactor from twisted.internet.asyncioreactor import AsyncioSelectorReactor from scrapy.signalmanager import dispatcher from scrapy import signals # 整合Twisted Reactor与asyncio事件循环 AsyncioSelectorReactor.install() configure_logging(install_root_handler=False) # 避免Scrapy日志干扰FastAPI app = FastAPI() def is_not_blank(termoss): return bool(termoss and not termoss.isspace()) @app.get("/", tags=['Início']) def inicio(): return {"Bem Vindo ao FastAPI+Scrapy v-1.0"} @app.get("/Produtos", tags=["Raspagem de Dados"]) async def raspagem_produtos(produto: str): scraped_items = [] # 定义信号回调,收集爬取到的item def collect_item(item, response, spider): scraped_items.append(item) # 绑定item_scraped信号 dispatcher.connect(collect_item, signal=signals.item_scraped) runner = CrawlerRunner() # 启动爬虫,传入produto参数 deferred = runner.crawl(CatalogoSpider, produto=produto) # 爬取完成后停止Reactor deferred.addCallback(lambda _: reactor.stop()) # 启动Reactor,禁用信号处理器避免与FastAPI冲突 reactor.run(installSignalHandlers=False) # 处理MongoDB存储(需修改DbMongoJsonProds方法,接受爬取数据参数) if is_not_blank(produto): ConectMongo.DbMongoJsonProds(produto, scraped_items) else: produto = str(random.randint(10, 1000)) ConectMongo.DbMongoJsonProds(produto, scraped_items) # 返回JSON响应 json_str = json.dumps(scraped_items, indent=4, sort_keys=True, default=str) return Response(json_str, media_type='application/json')
关键说明
- Reactor整合:
AsyncioSelectorReactor.install()让Twisted使用FastAPI的asyncio事件循环,确保异步环境兼容,避免循环冲突。 - 无全局状态:每个请求的爬虫实例独立接收
produto参数,并发请求时不会互相干扰。 - 内存收集数据:通过Scrapy的
item_scraped信号在内存中收集结果,无需本地文件,解决了并发下的文件读写冲突。 - Reactor控制:手动启动/停止Reactor,确保每次爬取完成后正确清理,且下次请求可正常启动Reactor(基于asyncio循环支持重复启动)。
内容的提问来源于stack exchange,提问作者CH97
相关产品推荐
相关产品推荐

