Как я могу заставить Selenium работать параллельно с Scrapy? - PullRequest
1 голос
/ 13 апреля 2020

Я пытаюсь очистить некоторые URL с помощью Scrapy и Selenium. Некоторые URL-адреса обрабатываются Scrapy напрямую, а другие обрабатываются сначала с помощью Selenium.

Проблема в том, что пока Selenium обрабатывает URL-адрес, Scrapy не обрабатывает другие параллельно. Он ждет, пока веб-драйвер завершит свою работу sh.

Я попытался запустить несколько пауков с разными параметрами инициализации в отдельных процессах (используя пул многопроцессорной обработки), но я получил twisted.internet.error.ReactorNotRestartable. Я также пытался порождать другой процесс в методе parse. Но, похоже, у меня недостаточно опыта, чтобы все сделать правильно.

В приведенном ниже примере все URL-адреса печатаются только тогда, когда веб-драйвер закрыт. Пожалуйста, посоветуйте, есть ли способ заставить его работать "параллельно" ?

import time

import scrapy
from selenium.webdriver import Firefox


def load_with_selenium(url):
    with Firefox() as driver:
        driver.get(url)
        time.sleep(10)  # Do something
        page = driver.page_source
    return page


class TestSpider(scrapy.Spider):
    name = 'test_spider'

    tasks = [{'start_url': 'https://www.theguardian.com/', 'selenium': False},
             {'start_url': 'https://www.nytimes.com/', 'selenium': True}]

    def start_requests(self):
        for task in self.tasks:
            yield scrapy.Request(url=task['start_url'], callback=self.parse, meta=task)

    def parse(self, response):
        if response.meta['selenium']:
            response = response.replace(body=load_with_selenium(response.meta['start_url']))

        for url in response.xpath('//a/@href').getall():
            print(url)

1 Ответ

0 голосов
/ 21 апреля 2020

Кажется, я нашел решение.

Я решил использовать многопроцессорность, запуская по одному пауку в каждом процессе и передавая задачу в качестве параметра init. В некоторых случаях этот подход может быть неуместным, но он работает для меня.

Я пробовал этот способ раньше, но получал исключение twisted.internet.error.ReactorNotRestartable. Это было вызвано многократным вызовом start() метода CrawlerProcess в каждом процессе, что неверно. Здесь Я нашел простой и понятный пример запуска паука в al oop с использованием обратных вызовов.

Поэтому я разделил свой список tasks между процессами. Затем внутри метода crawl(tasks) я создаю цепочку обратных вызовов для многократного запуска моего паука, каждый раз передавая различную задачу в качестве параметра init.

import multiprocessing

import numpy as np
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings

tasks = [{'start_url': 'https://www.theguardian.com/', 'selenium': False},
         {'start_url': 'https://www.nytimes.com/', 'selenium': True}]


def crawl(tasks):
    process = CrawlerProcess(get_project_settings())

    def run_spider(_, index=0):
        if index < len(tasks):
            deferred = process.crawl('test_spider', task=tasks[index])
            deferred.addCallback(run_spider, index + 1)
            return deferred

    run_spider(None)
    process.start()


def main():
    processes = 2
    with multiprocessing.Pool(processes) as pool:
        pool.map(crawl, np.array_split(tasks, processes))


if __name__ == '__main__':
    main()

Код TestSpider в моем вопроснике должен изменить соответствующим образом, чтобы принять задачу в качестве параметра init.

def __init__(self, task):
    scrapy.Spider.__init__(self)
    self.task = task

def start_requests(self):
    yield scrapy.Request(url=self.task['start_url'], callback=self.parse, meta=self.task)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...