FastAPI调用Scrapy二次请求报ReactorNotRestartable错误如何解决
错误根因
CrawlerProcess内部依赖Twisted的Reactor事件循环,而Twisted的Reactor设计上仅允许被启动1次。原代码全局初始化了process实例,且每次请求都调用process.start(),第一次请求执行完成后Reactor已经处于停止状态,第二次请求尝试重启就会抛出twisted.internet.error.ReactorNotRestartable错误。
修复方案
提供两种生产环境常用的可行方案:
方案1:使用CrawlerRunner配合异步事件循环(推荐,适配FastAPI异步特性)
该方案将Twisted的Reactor与FastAPI使用的asyncio事件循环整合,全程复用同一个事件循环,无需重复启停Reactor,资源消耗更低:
import asyncio from fastapi import FastAPI from pydantic import BaseModel from typing import List from scrapy.crawler import CrawlerRunner from scrapy.utils.project import get_project_settings from twisted.internet import asyncioreactor # 必须在导入其他Twisted相关模块前完成反应器注册 asyncioreactor.install(asyncio.get_event_loop()) class Request(BaseModel): someIDs: List[str] # 全局初始化CrawlerRunner即可,无需使用CrawlerProcess runner = CrawlerRunner(get_project_settings()) app = FastAPI() @app.post("/") async def home(request: Request): # 将Scrapy爬虫任务包装为异步任务等待执行完成 deferred = runner.crawl('rt_criteria', ids=request.someIDs) await asyncio.wrap_future(deferred.asFuture(asyncio.get_event_loop())) return {"crawled": True} # 启动命令不变:uvicorn main:app --reload
方案2:每次请求启动独立子进程运行爬虫
如果不想调整原有同步逻辑,可以把爬虫逻辑放到独立子进程中执行,子进程结束后自动销毁Reactor实例,不会影响后续请求:
from fastapi import FastAPI from pydantic import BaseModel from typing import List import multiprocessing from scrapy.crawler import CrawlerProcess from scrapy.utils.project import get_project_settings class Request(BaseModel): someIDs: List[str] app = FastAPI() # 爬虫逻辑封装为独立函数,仅在子进程内部执行 def run_crawler(ids: List[str]): process = CrawlerProcess(get_project_settings()) process.crawl('rt_criteria', ids=ids) process.start() @app.post("/") def home(request: Request): # 每次请求启动独立子进程执行爬虫任务 p = multiprocessing.Process(target=run_crawler, args=(request.someIDs,)) p.start() p.join() # 等待爬虫任务执行完成后返回 return {"crawled": True} # 启动命令不变:uvicorn main:app --reload
该方案注意事项:不要全局初始化
CrawlerProcess,必须将实例化逻辑放到子进程执行的函数内部,保证每个子进程都有独立的Reactor实例;并发请求量大的场景下子进程资源消耗会高于方案1。
内容的提问来源于stack exchange,提问作者Mohammad Reza Karimi
相关产品推荐
相关产品推荐

