Можно ли использовать scrapy для очистки динамического содержимого с веб-сайтов, использующих AJAX?

Недавно я изучал Python и погружаюсь в создание веб-скребка. Ничего необычного; его единственная цель - вывести данные с веб-сайта ставок и внести эти данные в Excel.

Большинство вопросов разрешимы, и у меня неплохой беспорядок. Однако я столкнулся с массивным препятствием по одной проблеме. Если сайт загружает таблицу лошадей и перечисляет текущие цены ставок, эта информация не содержится ни в одном исходном файле. Подсказка заключается в том, что эти данные живут иногда, причем числа обновляются, очевидно, с какого-то удаленного сервера. HTML на моем ПК просто имеет отверстие, где их серверы проталкивают все интересные мне данные.

Теперь мой опыт работы с динамическим веб-контентом низкий, так что эта вещь - это то, с чем я сталкиваюсь.

Я думаю, что Java или Javascript - это ключ, это часто появляется.

Скребок - это просто механизм сравнения шансов. Некоторые сайты имеют API, но мне это нужно для тех, кто этого не делает. Я использую библиотеку scrapy с Python 2.7

Я извиняюсь, если этот вопрос слишком открытый. Короче говоря, мой вопрос: как можно использовать scrapy для очистки этих динамических данных, чтобы я мог его использовать? Чтобы я мог очистить данные ставок в реальном времени?

Ответ 1

Браузеры на основе Webkit (например, Google Chrome или Safari) имеют встроенные средства разработки. В Chrome вы можете открыть его Menu->Tools->Developer Tools. Вкладка Network позволяет вам видеть всю информацию о каждом запросе и ответе:

enter image description here

В нижней части рисунка вы можете видеть, что я отфильтровал запрос до XHR - это запросы, сделанные кодом javascript.

Совет: журнал очищается каждый раз, когда вы загружаете страницу, внизу изображения, черная кнопка будет сохранять журнал.

После анализа запросов и ответов вы можете имитировать эти запросы от своего веб-искателя и извлекать ценные данные. Во многих случаях легче получить данные, чем анализировать HTML, потому что эти данные не содержат логику представления и отформатированы для доступа к javascript-коду.

У Firefox есть аналогичное расширение, оно называется firebug. Некоторые утверждают, что firebug еще более мощный, но мне нравится простота webkit.

Ответ 2

Вот простой пример scrapy с запросом AJAX. Пусть увидят сайт rubin-kazan.ru.

Все сообщения загружаются с помощью запроса AJAX. Моя цель - получить эти сообщения со всеми их атрибутами (автор, дата,...):

enter image description here

Когда я анализирую исходный код страницы, я не вижу все эти сообщения, потому что веб-страница использует технологию AJAX. Но я могу с помощью Firebug от Mozilla Firefox (или аналогичного инструмента в других браузерах) проанализировать HTTP-запрос, который генерирует сообщения на веб-странице:

enter image description here

Он не перезагружает всю страницу, а только части страницы, которые содержат сообщения. Для этого я нажимаю произвольное количество страниц внизу:

enter image description here

И я наблюдаю HTTP-запрос, который отвечает за тело сообщения:

enter image description here

После окончания я анализирую заголовки запроса (я должен процитировать, что этот URL я извлечу из исходной страницы из раздела var, см. Код ниже):

enter image description here

И форма данных содержимого запроса (HTTP-метод "Пост"):

enter image description here

И содержание ответа, который представляет собой файл JSON:

enter image description here

Который представляет всю информацию, которую я ищу.

С сегодняшнего дня я должен применить все эти знания в медицине. Для этого определим паука:

class spider(BaseSpider):
    name = 'RubiGuesst'
    start_urls = ['http://www.rubin-kazan.ru/guestbook.html']

    def parse(self, response):
        url_list_gb_messages = re.search(r'url_list_gb_messages="(.*)"', response.body).group(1)
        yield FormRequest('http://www.rubin-kazan.ru' + url_list_gb_messages, callback=self.RubiGuessItem,
                          formdata={'page': str(page + 1), 'uid': ''})

    def RubiGuessItem(self, response):
        json_file = response.body

В функции parse меня есть ответ на первый запрос. В RubiGuessItem меня есть файл JSON со всей информацией.

Ответ 3

Много раз при обходе мы сталкиваемся с проблемами, когда контент, отображаемый на странице, генерируется с помощью Javascript, и поэтому scrapy не может обходить его (например, запросы ajax, сумасшествие jQuery).

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

Некоторые примечания:

  • У вас должна быть установленная на Python версия Selenium RC для этого, и вы должны правильно настроить Selenium. Также это просто искатель шаблонов. Вы могли бы стать намного более сумасшедшим и более продвинутым, но я просто хотел показать основную идею. Поскольку код стоит сейчас, вы будете делать два запроса для любого заданного URL-адреса. Один запрос сделан Scrapy, а другой - Selenium. Я уверен, что есть способы обойти это, чтобы вы могли просто сделать Selenium одним и единственным запросом, но я не потрудился реализовать это и выполнив два запроса, которые вы получаете, чтобы обходить страницу с помощью Scrapy.

    /li >
  • Это довольно эффективно, потому что теперь у вас есть весь предоставленный DOM для сканирования, и вы все равно можете использовать все приятные функции сканирования в Scrapy. Это приведет к более медленному сканированию, но в зависимости от того, сколько вам потребуется рендер DOM, возможно, стоит подождать.

    from scrapy.contrib.spiders import CrawlSpider, Rule
    from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
    from scrapy.selector import HtmlXPathSelector
    from scrapy.http import Request
    
    from selenium import selenium
    
    class SeleniumSpider(CrawlSpider):
        name = "SeleniumSpider"
        start_urls = ["http://www.domain.com"]
    
        rules = (
            Rule(SgmlLinkExtractor(allow=('\.html', )), callback='parse_page',follow=True),
        )
    
        def __init__(self):
            CrawlSpider.__init__(self)
            self.verificationErrors = []
            self.selenium = selenium("localhost", 4444, "*chrome", "http://www.domain.com")
            self.selenium.start()
    
        def __del__(self):
            self.selenium.stop()
            print self.verificationErrors
            CrawlSpider.__del__(self)
    
        def parse_page(self, response):
            item = Item()
    
            hxs = HtmlXPathSelector(response)
            #Do some XPath selection with Scrapy
            hxs.select('//div').extract()
    
            sel = self.selenium
            sel.open(response.url)
    
            #Wait for javscript to load in Selenium
            time.sleep(2.5)
    
            #Do some crawling of javascript created content with Selenium
            sel.get_text("//div")
            yield item
    
    # Snippet imported from snippets.scrapy.org (which no longer works)
    # author: wynbennett
    # date  : Jun 21, 2011
    

Ссылка: http://snipplr.com/view/66998/

Ответ 4

Другим решением будет внедрение обработчика загрузки или промежуточного программного обеспечения обработчика загрузки. Ниже приведен пример промежуточного программного обеспечения с использованием селена с безгласным фантомным webdriver:

class JsDownload(object):

@check_spider_middleware
def process_request(self, request, spider):
    driver = webdriver.PhantomJS(executable_path='D:\phantomjs.exe')
    driver.get(request.url)
    return HtmlResponse(request.url, encoding='utf-8', body=driver.page_source.encode('utf-8'))

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

def check_spider_middleware(method):
@functools.wraps(method)
def wrapper(self, request, spider):
    msg = '%%s %s middleware step' % (self.__class__.__name__,)
    if self.__class__ in spider.middleware:
        spider.log(msg % 'executing', level=log.DEBUG)
        return method(self, request, spider)
    else:
        spider.log(msg % 'skipping', level=log.DEBUG)
        return None

return wrapper

settings.py:

DOWNLOADER_MIDDLEWARES = {'MyProj.middleware.MiddleWareModule.MiddleWareClass': 500}

для обертки для работы все пауки должны иметь минимум:

middleware = set([])

для включения промежуточного программного обеспечения:

middleware = set([MyProj.middleware.ModuleName.ClassName])

Основным преимуществом его реализации, а не пауком, является то, что вы делаете только один запрос. Например, в решении AT: обработчик загрузки обрабатывает запрос, а затем передает ответ паук. Затем паук делает новый запрос в этой функции parse_page. Это два запроса для одного и того же контента.

Ответ 5

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

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

Здесь приведен рабочий пример здесь. Это выглядит так:

# encoding: utf-8
from __future__ import unicode_literals

from scrapy import signals
from scrapy.signalmanager import SignalManager
from scrapy.responsetypes import responsetypes
from scrapy.xlib.pydispatch import dispatcher
from selenium import webdriver
from six.moves import queue
from twisted.internet import defer, threads
from twisted.python.failure import Failure


class PhantomJSDownloadHandler(object):

    def __init__(self, settings):
        self.options = settings.get('PHANTOMJS_OPTIONS', {})

        max_run = settings.get('PHANTOMJS_MAXRUN', 10)
        self.sem = defer.DeferredSemaphore(max_run)
        self.queue = queue.LifoQueue(max_run)

        SignalManager(dispatcher.Any).connect(self._close, signal=signals.spider_closed)

    def download_request(self, request, spider):
        """use semaphore to guard a phantomjs pool"""
        return self.sem.run(self._wait_request, request, spider)

    def _wait_request(self, request, spider):
        try:
            driver = self.queue.get_nowait()
        except queue.Empty:
            driver = webdriver.PhantomJS(**self.options)

        driver.get(request.url)
        # ghostdriver won't response when switch window until page is loaded
        dfd = threads.deferToThread(lambda: driver.switch_to.window(driver.current_window_handle))
        dfd.addCallback(self._response, driver, spider)
        return dfd

    def _response(self, _, driver, spider):
        body = driver.execute_script("return document.documentElement.innerHTML")
        if body.startswith("<head></head>"):  # cannot access response header in Selenium
            body = driver.execute_script("return document.documentElement.textContent")
        url = driver.current_url
        respcls = responsetypes.from_args(url=url, body=body[:100].encode('utf8'))
        resp = respcls(url=url, body=body, encoding="utf-8")

        response_failed = getattr(spider, "response_failed", None)
        if response_failed and callable(response_failed) and response_failed(resp, driver):
            driver.close()
            return defer.fail(Failure())
        else:
            self.queue.put(driver)
            return defer.succeed(resp)

    def _close(self):
        while not self.queue.empty():
            driver = self.queue.get_nowait()
            driver.close()

Предположим, что ваш скребок называется "скребок". Если вы поместили указанный код в файл с именем handlers.py в корневой папке "скребок", вы можете добавить его в settings.py:

DOWNLOAD_HANDLERS = {
    'http': 'scraper.handlers.PhantomJSDownloadHandler',
    'https': 'scraper.handlers.PhantomJSDownloadHandler',
}

И voilà, JS проанализировал DOM, с кэшем scrapy, повторениями и т.д.

Ответ 6

Я обрабатываю запрос ajax, используя веб-драйвер Selenium и Firefox. Это не так быстро, если вам нужен сканер как демон, но гораздо лучше, чем любое ручное решение. Я написал короткий учебник здесь для справки

Ответ 7

как можно использовать scrapy для очистки этих динамических данных, чтобы я мог использовать это?

Интересно, почему никто не опубликовал решение, используя только Scrapy.

Отправляйте сообщение в блоге из команды Scrapy ОБРАБОТАТЬ БЕСКОНТАКТНЫЕ СТРАНИЦЫ. В примере обрывается http://spidyquotes.herokuapp.com/scroll сайт, который использует бесконечную прокрутку.

Идея состоит в том, чтобы использовать инструменты разработчика вашего браузера и заметить запросы AJAX, а затем на основе этой информации создайте запросы для Scrapy.

import json
import scrapy


class SpidyQuotesSpider(scrapy.Spider):
    name = 'spidyquotes'
    quotes_base_url = 'http://spidyquotes.herokuapp.com/api/quotes?page=%s'
    start_urls = [quotes_base_url % 1]
    download_delay = 1.5

    def parse(self, response):
        data = json.loads(response.body)
        for item in data.get('quotes', []):
            yield {
                'text': item.get('text'),
                'author': item.get('author', {}).get('name'),
                'tags': item.get('tags'),
            }
        if data['has_next']:
            next_page = data['page'] + 1
            yield scrapy.Request(self.quotes_base_url % next_page)

Ответ 8

да, Scrapy может удалять динамические веб-сайты, веб-сайты, которые отображаются с помощью javaScript.

Существует два подхода к поиску подобных веб-сайтов.

Первый,

Вы можете использовать splash для рендеринга кода Javascript, а затем для анализа обработанного HTML. Вы можете найти документ и проект здесь Scrapy splash, git

Во-вторых,

Как утверждают все, отслеживая network calls, да, вы можете найти вызов API, который извлекает данные, и имитировать, что вызов вашего паука-скрапа может помочь вам получить нужные данные.