web-dev-qa-db-fra.com

Scrapy - Réacteur non redémarrable

avec:

from twisted.internet import reactor
from scrapy.crawler import CrawlerProcess

J'ai toujours couru ce processus avec succès:

process = CrawlerProcess(get_project_settings())
process.crawl(*args)
# the script will block here until the crawling is finished
process.start() 

mais depuis que j'ai déplacé ce code dans une fonction web_crawler(self), comme ceci:

def web_crawler(self):
    # set up a crawler
    process = CrawlerProcess(get_project_settings())
    process.crawl(*args)
    # the script will block here until the crawling is finished
    process.start() 

    # (...)

    return (result1, result2) 

et a commencé à appeler la méthode en utilisant l'instanciation de classe, comme:

def __call__(self):
    results1 = test.web_crawler()[1]
    results2 = test.web_crawler()[0]

et en cours d'exécution:

test()

Je reçois l'erreur suivante:

Traceback (most recent call last):
  File "test.py", line 573, in <module>
    print (test())
  File "test.py", line 530, in __call__
    artists = test.web_crawler()
  File "test.py", line 438, in web_crawler
    process.start() 
  File "/Library/Python/2.7/site-packages/scrapy/crawler.py", line 280, in start
    reactor.run(installSignalHandlers=False)  # blocking call
  File "/Library/Python/2.7/site-packages/twisted/internet/base.py", line 1194, in run
    self.startRunning(installSignalHandlers=installSignalHandlers)
  File "/Library/Python/2.7/site-packages/twisted/internet/base.py", line 1174, in startRunning
    ReactorBase.startRunning(self)
  File "/Library/Python/2.7/site-packages/twisted/internet/base.py", line 684, in startRunning
    raise error.ReactorNotRestartable()
twisted.internet.error.ReactorNotRestartable

qu'est-ce qui ne va pas?

8
data_garden

Vous ne pouvez pas redémarrer le réacteur, mais vous devriez pouvoir l'exécuter plusieurs fois en exécutant un processus séparé:

import scrapy
import scrapy.crawler as crawler
from multiprocessing import Process, Queue
from twisted.internet import reactor

# your spider
class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = ['http://quotes.toscrape.com/tag/humor/']

    def parse(self, response):
        for quote in response.css('div.quote'):
            print(quote.css('span.text::text').extract_first())


# the wrapper to make it run more times
def run_spider(spider):
    def f(q):
        try:
            runner = crawler.CrawlerRunner()
            deferred = runner.crawl(spider)
            deferred.addBoth(lambda _: reactor.stop())
            reactor.run()
            q.put(None)
        except Exception as e:
            q.put(e)

    q = Queue()
    p = Process(target=f, args=(q,))
    p.start()
    result = q.get()
    p.join()

    if result is not None:
        raise result

Exécutez-le deux fois:

print('first run:')
run_spider(QuotesSpider)

print('\nsecond run:')
run_spider(QuotesSpider)

Résultat:

first run:
“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”
“A day without sunshine is like, you know, night.”
...

second run:
“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”
“A day without sunshine is like, you know, night.”
...
12
Ferrard

C'est ce qui m'a aidé à gagner la bataille contre ReactorNotRestartable error: dernière réponse de l'auteur de la question
0) pip install crochet
1) import from crochet import setup
2) setup() - en haut du fichier
3) supprimer 2 lignes:
a) d.addBoth(lambda _: reactor.stop())
b) reactor.run()

J'ai eu le même problème avec cette erreur, et passer plus de 4 heures à résoudre ce problème, lisez toutes les questions à ce sujet. Enfin trouvé celui-là - et le partager. C'est comme ça que j'ai résolu ça. Les seules lignes significatives de Scrapy docs left sont les 2 dernières lignes de ce code:

#some more imports
from crochet import setup
setup()

def run_spider(spiderName):
    module_name="first_scrapy.spiders.{}".format(spiderName)
    scrapy_var = import_module(module_name)   #do some dynamic import of selected spider   
    spiderObj=scrapy_var.mySpider()           #get mySpider-object from spider module
    crawler = CrawlerRunner(get_project_settings())   #from Scrapy docs
    crawler.crawl(spiderObj)                          #from Scrapy docs

Ce code me permet de sélectionner l'araignée à exécuter simplement avec son nom passé à la fonction run_spider et, une fois la mise au rebut terminée, sélectionnez une autre araignée et relancez-la.
J'espère que cela aidera quelqu'un, comme cela m'a été utile :)

8
Chiefir

Selon la documentation Scrapy , la méthode start() de la classe CrawlerProcess effectue les opérations suivantes:

"[...] démarre un réacteur Twisted, ajuste la taille de son pool à REACTOR_THREADPOOL_MAXSIZE et installe un cache DNS basé sur DNSCACHE_ENABLED et DNSCACHE_SIZE."

L'erreur que vous recevez est générée par Twisted, car un réacteur Twisted ne peut pas être redémarré. Il utilise une tonne de globals, et même si vous utilisez une sorte de code pour le relancer (je l'ai déjà vu), rien ne garantit que cela fonctionnera. 

Honnêtement, si vous pensez devoir redémarrer le réacteur, vous faites probablement quelque chose de mal.

En fonction de ce que vous souhaitez faire, j'examinerais également la partie Running Scrapy depuis un script de la documentation.

1
Rejected

L'erreur est dans ce code:

def __call__(self):
    result1 = test.web_crawler()[1]
    result2 = test.web_crawler()[0] # here

web_crawler() renvoie deux résultats et, dans ce but, il tente de démarrer le processus deux fois, en redémarrant le Reactor, comme indiqué par @Rejected.

obtenir les résultats en exécutant un seul processus et stocker les deux résultats dans un tuple est le chemin à parcourir ici:

def __call__(self):
    result1, result2 = test.web_crawler()
1
data_garden

Ceci a résolu mon problème, mettez le code ci-dessous après reactor.run() ou process.start():

time.sleep(0.5)

os.execl(sys.executable, sys.executable, *sys.argv)
0
Neeraj Yadav

Comme certaines personnes l'ont déjà fait remarquer: il ne devrait pas être nécessaire de redémarrer le réacteur.

Idéalement, si vous souhaitez chaîner vos processus (crawl1 puis crawl2 puis crawl3), vous ajoutez simplement des rappels.

Par exemple, j'ai utilisé cette araignée en boucle qui suit ce modèle:

1. Crawl A
2. Sleep N
3. goto 1

Et voici à quoi ça ressemble dans scrapy:

import time

from scrapy.crawler import CrawlerRunner
from scrapy.utils.project import get_project_settings
from twisted.internet import reactor

class HttpbinSpider(scrapy.Spider):
    name = 'httpbin'
    allowed_domains = ['httpbin.org']
    start_urls = ['http://httpbin.org/ip']

    def parse(self, response):
        print(response.body)

def sleep(_, duration=5):
    print(f'sleeping for: {duration}')
    time.sleep(duration)  # block here


def crawl(runner):
    d = runner.crawl(HttpbinSpider)
    d.addBoth(sleep)
    d.addBoth(lambda _: crawl(runner))
    return d


def loop_crawl():
    runner = CrawlerRunner(get_project_settings())
    crawl(runner)
    reactor.run()


if __== '__main__':
    loop_crawl()

Pour expliquer le processus davantage, la fonction crawl planifie une analyse et ajoute deux rappels supplémentaires qui sont appelés à la fin de l'analyse: le blocage de la mise en veille et des appels récursifs (planification d'une autre analyse).

$ python endless_crawl.py 
b'{\n  "Origin": "000.000.000.000"\n}\n'
sleeping for: 5
b'{\n  "Origin": "000.000.000.000"\n}\n'
sleeping for: 5
b'{\n  "Origin": "000.000.000.000"\n}\n'
sleeping for: 5
b'{\n  "Origin": "000.000.000.000"\n}\n'
sleeping for: 5
0
Granitosaurus