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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 02:25:45