web-dev-qa-db-fra.com

Exécutez une araignée Scrapy dans une tâche de céleri

Cela ne fonctionne plus , l'API de scrapy a changé.

Maintenant, la documentation propose un moyen de " Exécuter Scrapy à partir d'un script " mais j'obtiens l'erreur ReactorNotRestartable.

Ma tâche:

from celery import Task

from twisted.internet import reactor

from scrapy.crawler import Crawler
from scrapy import log, signals
from scrapy.utils.project import get_project_settings

from .spiders import MySpider



class MyTask(Task):
    def run(self, *args, **kwargs):
        spider = MySpider
        settings = get_project_settings()
        crawler = Crawler(settings)
        crawler.signals.connect(reactor.stop, signal=signals.spider_closed)
        crawler.configure()
        crawler.crawl(spider)
        crawler.start()

        log.start()
        reactor.run()
31
Juan Riaza

Le réacteur torsadé ne peut pas être redémarré. Une solution consiste à laisser la tâche céleri bifurquer un nouveau processus enfant pour chaque analyse que vous souhaitez exécuter, comme proposé dans l'article suivant:

Cela permet de contourner le problème "Impossible de redémarrer le réacteur" en utilisant le package multiprocessing. Mais le problème est que la solution de contournement est désormais obsolète avec la dernière version de céleri, car vous rencontrerez plutôt un autre problème où un processus démon ne peut pas générer de sous-processus. Donc, pour que la solution de contournement fonctionne, vous devez descendre en version céleri.

Oui, et l'API scrapy a changé. Mais avec des modifications mineures (import Crawler au lieu de CrawlerProcess). Vous pouvez obtenir la solution de contournement en descendant dans la version céleri.

Le problème du céleri peut être trouvé ici: Celery Issue # 1709

Voici mon script d'exploration mis à jour qui fonctionne avec les nouvelles versions de céleri en utilisant billiard au lieu de multiprocessing:

from scrapy.crawler import Crawler
from scrapy.conf import settings
from myspider import MySpider
from scrapy import log, project
from twisted.internet import reactor
from billiard import Process
from scrapy.utils.project import get_project_settings

class UrlCrawlerScript(Process):
    def __init__(self, spider):
        Process.__init__(self)
        settings = get_project_settings()
        self.crawler = Crawler(settings)
        self.crawler.configure()
        self.crawler.signals.connect(reactor.stop, signal=signals.spider_closed)
        self.spider = spider

    def run(self):
        self.crawler.crawl(self.spider)
        self.crawler.start()
        reactor.run()

def run_spider(url):
    spider = MySpider(url)
    crawler = UrlCrawlerScript(spider)
    crawler.start()
    crawler.join()

Edit: En lisant le numéro de céleri # 1709 ils suggèrent d'utiliser le billard au lieu du multitraitement pour que la limitation du sous-processus soit levé. En d'autres termes, nous devrions essayer billard et voir si cela fonctionne!

Édition 2: Oui, en utilisant billard , mon script fonctionne avec la dernière version de céleri! Voir mon script mis à jour.

36
Bj Blazkowicz

Le réacteur Twisted ne peut pas être redémarré, donc une fois qu'une araignée a fini de fonctionner et que crawler arrête le réacteur implicitement, ce travailleur est inutile.

Comme indiqué dans les réponses à cette autre question, tout ce que vous avez à faire est de tuer le travailleur qui a dirigé votre araignée et de la remplacer par une nouvelle, ce qui empêche le démarrage et l'arrêt du réacteur plus d'une fois. Pour ce faire, définissez simplement:

CELERYD_MAX_TASKS_PER_CHILD = 1

L'inconvénient est que vous n'êtes pas vraiment en utilisant le réacteur Twisted à son plein potentiel et gaspillez des ressources en exécutant plusieurs réacteurs, car un réacteur peut exécuter plusieurs araignées à la fois dans un seul processus. Une meilleure approche consiste à faire fonctionner un réacteur par travailleur (ou même un réacteur dans le monde) et à ne pas laisser crawler y toucher.

Je travaille là-dessus pour un projet très similaire, donc je mettrai à jour cet article si je fais des progrès.

11
Blender

Pour éviter l'erreur ReactorNotRestartable lors de l'exécution de Scrapy dans Celery Tasks Queue, j'ai utilisé des threads. La même approche utilisée pour exécuter le réacteur Twisted plusieurs fois dans une application. Scrapy a également utilisé Twisted, nous pouvons donc faire de même.

Voici le code:

from threading import Thread
from scrapy.crawler import CrawlerProcess
import scrapy

class MySpider(scrapy.Spider):
    name = 'my_spider'


class MyCrawler:

    spider_settings = {}

    def run_crawler(self):

        process = CrawlerProcess(self.spider_settings)
        process.crawl(MySpider)
        Thread(target=process.start).start()

N'oubliez pas d'augmenter CELERYD_CONCURRENCY pour le céleri.

CELERYD_CONCURRENCY = 10

fonctionne bien pour moi.

Ce n'est pas bloquer le processus en cours d'exécution, mais de toute façon la meilleure pratique est de traiter les données dans les rappels. Faites comme ça:

for crawler in process.crawlers:
    crawler.spider.save_result_callback = some_callback
    crawler.spider.save_result_callback_params = some_callback_params

Thread(target=process.start).start()
2
Denis Cherniatev