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

121

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

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

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

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

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

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

  • 1
    Как я могу получить эти данные, данные, которые являются динамическими и живыми?
  • 1
    Если на вашей странице есть JavaScript, попробуйте это
Показать ещё 3 комментария
Теги:
scrapy
screen-scraping

7 ответов

73
Лучший ответ

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

Изображение 3600

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

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

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

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

  • 92
    Как, черт возьми, это может быть принятым ответом, если в нем даже нет слова «скрап»?
  • 5
    Я думал о том же самом
Показать ещё 3 комментария
80

Вот простой пример использования scrapy с помощью ajax-запроса. Давайте посмотрим на сайт http://www.rubin-kazan.ru/guestbook.html Все сообщения загружаются с помощью запроса ajax. Моя цель - получить эти сообщения со всеми их атрибутами (автор, дата,...).

Изображение 3601

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

Для этой цели я не перезагружаю всю страницу, а только часть страницы, содержащую сообщения. Для этого я нажимаю произвольное количество страниц внизу Изображение 3603, и я наблюдаю HTTP-запрос, который отвечает за тело сообщения Изображение 3604

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

и содержание данных формы запроса (метод Http - "Post" )

Изображение 3606

и содержание ответа, которое является Json файлом,

Изображение 3607

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

Теперь я должен реализовать все эти знания в области терапии. Позвольте определить паука для этой цели.

  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 файл со всей информацией.

  • 6
    Привет. Не могли бы вы объяснить, что такое url_list_gb_messages? Я не могу этого понять. Благодарю.
  • 3
    Этот, безусловно, лучше.
Показать ещё 1 комментарий
35

Много раз при обходе мы сталкиваемся с проблемами, когда контент, отображаемый на странице, генерируется с помощью 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/

  • 0
    Аккуратное решение! Есть ли у вас какие-либо советы по подключению этого скрипта к Firefox? (ОС - это Linux Mint). Я получаю "[Errno 111] Соединение отказано".
  • 0
    Нужно также взглянуть на селен с phantomjs.org
Показать ещё 3 комментария
23

Другим решением будет внедрение обработчика загрузки или промежуточного программного обеспечения обработчика загрузки. Ниже приведен пример промежуточного программного обеспечения с использованием селена с безгласным фантомным 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
    это должен быть принятый ответ.
  • 0
    Я немного опоздал, чтобы ответить на это, хотя>. <
Показать ещё 4 комментария
6

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

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

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

# 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, повторениями и т.д.

  • 0
    Мне нравится это решение!
  • 0
    Хорошее решение. Драйвер Selenium по-прежнему единственный вариант?
1

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

0

как можно использовать 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)
  • 0
    Мы снова сталкиваемся с той же проблемой: Scrappy не создан для этой цели, и именно здесь мы сталкиваемся с той же проблемой. Перейдите к phantomJS или, как предлагали другие, создайте свое собственное промежуточное ПО для загрузки.
  • 0
    @ rak007 PhantomJS против драйвера Chrome. Какой из них вы бы предложили?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню