Scrapy `ReactorNotRestartable`: один класс для запуска двух (или более) пауков

Я собираю ежедневные данные с помощью Scrapy, используя двухэтапное сканирование. На первом этапе создается список URL-адресов со страницы индекса, а на втором этапе HTML-код для каждого URL-адреса в списке записывается в тему Kafka.

кластер kafka для сканера Scrapy

Хотя два компонента обхода связаны, я бы хотел, чтобы они были независимыми: url_generator запускался как запланированная задача один раз в день, а page_requester запускался постоянно, обрабатывая URL-адреса, когда они доступны. Ради «вежливости» я настрою DOWNLOAD_DELAY так, чтобы краулер завершал работу в течение 24 часов, но при этом загружал сайт минимально.

Я создал класс CrawlerRunner, в котором есть функции для генерации URL и извлечения HTML:

from twisted.internet import reactor
from scrapy.crawler import Crawler
from scrapy import log, signals
from scrapy_somesite.spiders.create_urls_spider import CreateSomeSiteUrlList
from scrapy_somesite.spiders.crawl_urls_spider import SomeSiteRetrievePages
from scrapy.utils.project import get_project_settings
import os
import sys

class CrawlerRunner:

    def __init__(self):
        sys.path.append(os.path.join(os.path.curdir, "crawl/somesite"))
        os.environ['SCRAPY_SETTINGS_MODULE'] = 'scrapy_somesite.settings'
        self.settings = get_project_settings()
        log.start()

    def create_urls(self):
        spider = CreateSomeSiteUrlList()
        crawler_create_urls = Crawler(self.settings)
        crawler_create_urls.signals.connect(reactor.stop, signal=signals.spider_closed)
        crawler_create_urls.configure()
        crawler_create_urls.crawl(spider)
        crawler_create_urls.start()
        reactor.run()

    def crawl_urls(self):
        spider = SomeSiteRetrievePages()
        crawler_crawl_urls = Crawler(self.settings)
        crawler_crawl_urls.signals.connect(reactor.stop, signal=signals.spider_closed)
        crawler_crawl_urls.configure()
        crawler_crawl_urls.crawl(spider)
        crawler_crawl_urls.start()
        reactor.run()

Когда я создаю экземпляр класса, я могу успешно выполнить любую функцию по отдельности, но, к сожалению, я не могу выполнить их вместе:

from crawl.somesite import crawler_runner

cr = crawler_runner.CrawlerRunner()

cr.create_urls()
cr.crawl_urls()

Второй вызов функции генерирует twisted.internet.error.ReactorNotRestartable при попытке выполнить reactor.run() в функции crawl_urls.

Мне интересно, есть ли простое решение для этого кода (например, какой-то способ запуска двух отдельных реакторов Twisted) или есть ли лучший способ структурировать этот проект.


person Alex Woolford    schedule 21.06.2015    source источник


Ответы (1)


Можно было запустить несколько пауков в одном реакторе, оставив реактор открытым, пока все пауки не перестанут работать. Это было достигнуто за счет сохранения списка всех запущенных пауков и не выполнения reactor.stop() до тех пор, пока этот список не станет пустым:

import sys
import os
from scrapy.utils.project import get_project_settings
from scrapy_somesite.spiders.create_urls_spider import Spider1
from scrapy_somesite.spiders.crawl_urls_spider import Spider2

from scrapy import signals, log
from twisted.internet import reactor
from scrapy.crawler import Crawler

class CrawlRunner:

    def __init__(self):
        self.running_crawlers = []

    def spider_closing(self, spider):
        log.msg("Spider closed: %s" % spider, level=log.INFO)
        self.running_crawlers.remove(spider)
        if not self.running_crawlers:
            reactor.stop()

    def run(self):

        sys.path.append(os.path.join(os.path.curdir, "crawl/somesite"))
        os.environ['SCRAPY_SETTINGS_MODULE'] = 'scrapy_somesite.settings'
        settings = get_project_settings()
        log.start(loglevel=log.DEBUG)

        to_crawl = [Spider1, Spider2]

        for spider in to_crawl:

            crawler = Crawler(settings)
            crawler_obj = spider()
            self.running_crawlers.append(crawler_obj)

            crawler.signals.connect(self.spider_closing, signal=signals.spider_closed)
            crawler.configure()
            crawler.crawl(crawler_obj)
            crawler.start()

        reactor.run()

Класс выполняется:

from crawl.somesite.crawl import CrawlRunner

cr = CrawlRunner()
cr.run()

Это решение основано на сообщении в блоге Киран Кодуру.

person Alex Woolford    schedule 05.07.2015
comment
Есть ли способ добавить сканеры в реактор во время его работы? Как это можно сделать, блокирует реактор.run()? - person Tim Givois; 28.09.2016