Search code examples
pythonscrapyfastapitwisted.internet

Error twisted.internet.error.ReactorNotRestartable


I'm having problems trying to run Scrapy again, after a scrape.

For example, when I run my FastAPI and have Scrapy do a scrape, it will work just fine. Bringing me the correct data. However, if I try to do another scrape without restarting the application, I get the error 'twisted.internet.error.ReactorNotRestartable'.

I've tried several solutions, but without success. Or I've applied them wrong.

I would like the user to have the freedom to do several sweeps, without having to kill the application every time and raise it again.

If you can help me out, I'd appreciate it.

from fastapi import FastAPI
from scrapy.crawler import CrawlerProcess
from server.lib.ProdsWeg.ProdsWeg.spiders.produtosWeg import CatalogoSpider
from server.lib.ProdsWeg.ProdsWeg.spiders.ServerMongo import ConectMongo
from fastapi import Response
import os
import json
import random
from server.lib.ProdsWeg.ProdsWeg.spiders.produtosWeg import recebeTermo

app = FastAPI()

## função para verificar se determinada variavel string é vazia ou não
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):

    ##passando o termo para dentro da função localizada dentro do arquivo da spider, para ativar o browser da URL
    recebeTermo(produto)

    if os.path.exists("DadosScrapingProdutos.js"):
        os.remove("DadosScrapingProdutos.js")
        print('Arquivo já existe, removendo ele para a criação do novo JSON!')
    else:
        print("O arquivo não existe, criando um novo JSON!")

    custom_settings = {
    'FEED_URI': 'DadosScrapingProdutos.js',
    'FEED_FORMAT': 'json',
    'FEED_EXPORT_ENCODING': 'utf-8'
    }
    process = CrawlerProcess(settings=custom_settings)
    process.crawl(CatalogoSpider)
    process.start()

    if is_not_blank(produto)==True:
        ConectMongo.DbMongoJsonProds(produto)
    else:
        produto = str(random.randint(10, 1000))
        ConectMongo.DbMongoJsonProds(produto)

    f = open("DadosScrapingProdutos.js")
    d = json.load(f)
    json_str = json.dumps(d, indent=4, sort_keys=True, default=str)    
    return Response(json_str, media_type='application/json')

Solution

  • Guys I managed to solve my problem. Following @Alexandre's suggestion in the above comment, I implemented the subprocess in the code.

    Instead of using CrawlerProcess, I used subprocess.call() which waits until the end of the operation.Passing to call() the command that executes Scrapy together with an argument '-a) to pass into the Spider class the term that will complement the search URL. And I also inform 'cwd' which is the current working directory where the command will be executed.

    Below File FastAPI --> app.py

    from fastapi import FastAPI
    from server.lib.Prods.Prods.spiders.ServerMongo import ConectMongo
    from fastapi import Response
    import os
    import json
    import random
    import subprocess
    
    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): 
    
        ## aqui fiz um procedimento de verificar se o JSON já existe, e se existir eu excluo ele, para evitar que o arquivo contenha as informações da ultima raspagem
        if os.path.exists("./app/server/lib/Prods/Prods/DadosScrapingProdutos.js"):
            os.remove("./app/server/lib/Prods/Prods/DadosScrapingProdutos.js")
            print('Arquivo já existe, removendo ele para a criação do novo JSON!')
        else:
            print("O arquivo não existe, criando um novo JSON!")
    
        subprocess.call([f'scrapy crawl produtos -a produto='+produto+''], shell = True, cwd='./app/server/lib/Prods/Prods')
    
        if is_not_blank(produto)==True:
            ConectMongo.DbMongoJsonProds(produto)
        else:
            produto = str(random.randint(10, 1000))
            ConectMongo.DbMongoJsonProds(produto)
    
        ## Nesta parte, é feito a leitura do JSON e adequação para que retorne os dados para a API de forma organizada e estruturada
        f = open("./app/server/lib/Prods/Prods/DadosScrapingProdutos.js")
        d = json.load(f)
        json_str = json.dumps(d, indent=4, sort_keys=True, default=str)    
        return Response(json_str, media_type='application/json')
    

    In the spider file I had to instantiate the init function to make it possible to bring the term typed by the user in FastAPI here. The Scrapy documentation explains this in more depth: [In the spider file I had to instantiate the init function to make it possible to bring the term typed by the user in FastAPI here. The Scrapy documentation explains this in more depth: [https://doc.scrapy.org/en/latest/topics/spiders.html#spider-arguments][1]

    Below File Scrapy --> produtos.py --> Spider name = 'produtos'

    class CatalogoSpider(scrapy.Spider):
        name = 'produtos'
        custom_settings = {
        'FEED_URI': 'DadosScrapingProdutos.js',
        'FEED_FORMAT': 'json',
        'FEED_EXPORT_ENCODING': 'utf-8'
        }
    
        def __init__(self, produto='', *args, **kwargs):
            super(CatalogoSpider, self).__init__(*args, **kwargs)
            self.produto = produto
    
    
      [1]: https://doc.scrapy.org/en/latest/topics/spiders.html#spider-arguments
    
    

    In this way, I can upload the FastAPI application, and perform as many scrapes as the user needs.