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

Мне нужно решить две проблемы:

Первая: правильно определить местонахождение элемента на веб-сайте с помощью драйвера. Второе: передайте ссылку, созданную в результате этого действия, в метод синтаксического анализа или LinkExtractor.

ad. 1.

Мне нужно найти кнопку «Загрузить еще» и щелкнуть по ней, чтобы просканировать получившуюся страницу.

<div class="col-sm-4 col-sm-offset-4 col-md-2 col-md-offset-5 col-xs-12 col-xs-offset-0">
            <button class="btn btn-secondary">Load more</button>
        </div>

ad.2.

Я определил LinkExtractor правила, которые корректно работают на сайтах stati c, и метод синтаксического анализа. Я видел много других примеров в подобных вопросах, но не могу понять, как мне их склеить?

Это моя последняя попытка:

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from os.path import join as path_join
import json
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
import time


class MySpider(CrawlSpider):
    input_dir = '../input'
    encoding = 'utf8'
    passing_file = path_join(input_dir, 'site_to_scrap.txt')
    with open(passing_file, 'r', encoding = encoding) as f:
        input_file = str(f.readlines()[0]).replace('\n', '')
        f.close()

    with open(path_join(input_dir, input_file + '.json'), 'r') as json_file:
        data = json.load(json_file)
        json_file.close()
    name = data['name']
    allowed_domains = data['allowed_domains']
    start_urls = data['start_urls']

    rules = (

        Rule(LinkExtractor(allow=data['allow'], deny=data['deny'],
                           deny_domains=data['deny_domains'],
                           restrict_text=data['restrict_text']),
             callback='parse_page', follow=True),

    )

    driver = webdriver.Chrome(ChromeDriverManager().install())

    def parse_page(self, response):
        self.driver.get(response.url)
        self.driver.implicitly_wait(2)
        self.driver.find_element_by_class_name('btn btn-secondary').click()
        time.sleep(2)

        for p in response.css('p'):
            yield {
                'p': p.css('p::text').get(),
            }
        for div in response.css('div'):
            yield {
                'div': div.css('div::text').get(),
            }
        for title in response.css('div.title'):
            yield {
                'header': title.css('div.title::text').get(),
            }
        for head in response.css('head'):
            yield {
                'header': head.css('head::text').get(),
            }

        # write visited urls into a file
        output_dir = './output'
        file = 'visited_urls.txt'
        with open(path_join(output_dir, file), 'a') as outfile:
            print(response.url, file=outfile)
            outfile.close()

Я удалил значительные часть метода parse_page для удобства чтения (это не влияет на выполнение).

Другой пример, который я безуспешно опробовал:

    driver = webdriver.Chrome(ChromeDriverManager().install())

    def parse_url(self, response):
        self.driver.get(response.url)

        while True:
            self.driver.implicitly_wait(2)
            next = self.driver.find_element_by_class_name('btn btn-secondary')

            try:
                next.click()
                time.sleep(2)
                # get the data and write it to scrapy items
            except:
                break

        self.driver.close()

Любой намек на один из двух вопросов будет очень признателен.

1 Ответ

1 голос
/ 25 мая 2020

Вы можете реализовать это как промежуточное ПО, которое выполняет запросы через Selenium, а затем возвращает HtmlResponse.
Для этой цели вы должны сначала создать подкласс Request.

from scrapy import Request

class SeleniumRequest(Request):
    pass

Этот подкласс class служит только для того, чтобы помочь промежуточному программному обеспечению узнать, следует ли использовать Selenium.
Когда вы реализуете собственное промежуточное программное обеспечение, убедитесь, что вы вызываете quit для драйвера при закрытии паука.

class SeleniumMiddleware:
    def __init__(self):
        self.driver = webdriver.Chrome(ChromeDriverManager().install())

    @classmethod
    def from_crawler(cls, crawler):
        middleware = cls()
        crawler.signals.connect(middleware.spider_closed, signals.spider_closed)
        return middleware

    def process_request(self, request, spider):
        if not isinstance(request, SeleniumRequest):
            return None
        self.driver.get(request.url)

        # loop to click "load more" button on JS-page
        while True:
            try:
                button = WebDriverWait(self.driver, 10).until(
                    EC.presence_of_element_located((By.CLASS_NAME, 'btn btn-secondary'))
                )
                self.driver.execute_script("arguments[0].click();", button)
            except:
                break

        return HtmlResponse(
            self.driver.current_url,
            body=str.encode(self.driver.page_source),
            encoding='utf-8',
            request=request
        )

    def spider_closed(self):
        self.driver.quit()

Затем включите его в своих настройках.

DOWNLOADER_MIDDLEWARES = {
    'your_project.middleware_location.SeleniumMiddleware': 500
}

Теперь вы можете получить SeleniumRequest в методе start_requests вашего Паука.

def start_requests(self):
    yield SeleniumRequest(your_url, your_callback)

Теперь все ваши запросы должны go через Selenium и возвращать HtmlResponse s, которые должны работать с вашими LinkExtractor s и, конечно, могут быть переданы вашему методу parse. Также я считаю этот репозиторий github наиболее полезным, когда я делаю подобные вещи.

...