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

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

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

Сейчас мой опыт работы с динамическим веб-контентом низок, поэтому мне сложно получить эту вещьмоя голова вокруг

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

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

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

Ответы [ 8 ]

90 голосов
/ 03 января 2013

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

72 голосов
/ 21 декабря 2011

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

enter image description here

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

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

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

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

39 голосов
/ 17 июля 2013

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

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

Некоторые вещи на заметку:

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

  • Это довольно мощный инструмент, потому что теперь у вас есть весь отрисованный 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/

30 голосов
/ 23 июня 2014

Другим решением будет реализация обработчика загрузки или промежуточного программного обеспечения обработчика загрузки. (см. scrapy docs для получения дополнительной информации о промежуточном программном обеспечении загрузчика) Ниже приведен пример класса, использующего селен с веб-драйвером phantomjs без головы:

1) Определение класса в скрипте middlewares.py.

from selenium import webdriver
from scrapy.http import HtmlResponse

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'))

2) Добавить JsDownload() класс к переменной DOWNLOADER_MIDDLEWARE в settings.py:

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

3) Интеграция HTMLResponse в your_spider.py. Расшифровка тела ответа даст вам желаемый результат.

class Spider(CrawlSpider):
    # define unique name of spider
    name = "spider"

    start_urls = ["https://www.url.de"] 

    def parse(self, response):
        # initialize items
        item = CrawlerItem()

        # store data as items
        item["js_enabled"] = response.body.decode("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

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

middleware = set([])

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

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

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

10 голосов
/ 18 марта 2016

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

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

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

# 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 в корне папки «scraper», то вы можете добавить его в settings.py:

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

И, вуаля, JS проанализировал DOM,с кэш-памятью, повторными попытками и т. д.

3 голосов
/ 03 апреля 2017

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

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

Ознакомьтесь с сообщением в блоге от команды Scrapy СКРЕБКА БЕСКОНЕЧНЫХ СТРАНИЦ ПРОКРУТКИ . Пример scraps 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)
1 голос
/ 13 февраля 2015

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

0 голосов
/ 02 января 2019

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

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

Во-первых,

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

Второй,

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

...