Scrapy - это фреймворк с открытым исходным кодом для создания веб-сканеров (также известных как пауки). Распространенным препятствием при разработке пауков Scrapy и веб-парсинга в целом является работа с сайтами, которые используют большое количество JavaScript. Поскольку многие современные веб-сайты построены на JavaScript, для правильного отображения страницы им требуется запускать скрипты.

Во многих случаях страницы также представляют модальные окна и другие диалоги, с которыми необходимо взаимодействовать, чтобы отобразить всю страницу. Поэтому мы разработали Splash, инструмент с открытым исходным кодом, который поможет вам получать структурированные данные из Интернета. В этом посте мы покажем вам, как можно использовать Splash для обработки JavaScript в ваших проектах Scrapy.

Что такое Splash?

Splash - это собственное решение Zyte для рендеринга JavaScript, реализованное на Python с использованием Twisted и QT. Splash - это легкий веб-браузер, способный обрабатывать несколько страниц параллельно, выполнять пользовательский JavaScript в контексте страницы и многое другое.

Настройка заставки

Проще всего настроить Splash через Docker:

$ docker pull scrapinghub/splash
$ docker run -p 5023:5023 -p 8050:8050 -p 8051:8051 scrapinghub/splash

Теперь Splash будет работать на localhost: 8050. Если вы используете машину Docker в OS X или Windows, она будет работать на IP-адресе виртуальной машины Docker.

Если вы хотите установить Splash без использования Docker, обратитесь к документации.

Использование Splash с Scrapy

Теперь, когда Splash запущен, вы можете протестировать его в своем браузере:

http://localhost:8050/

Справа введите URL (например, http://amazon.com) и нажмите Render me!. Splash отобразит снимок экрана страницы, а также диаграммы и список запросов с указанием их времени. Внизу вы должны увидеть текстовое поле, содержащее обработанный HTML.

Вручную

Вы можете использовать Запрос для отправки ссылок на Splash:

req_url = "http://localhost:8050/render.json"
body = json.dumps({
    "url": url,
    "har": 1,
    "html": 0,
})
headers = Headers({'Content-Type': 'application/json'})
yield scrapy.Request(req_url, self.parse_link, method='POST',
                                 body=body, headers=headers)

Если вы используете CrawlSpider, самый простой способ - переопределить функцию process_links в вашем пауке, чтобы заменить ссылки их эквивалентами в Splash:

def process_links(self, links):
    for link in links:
        link.url = "http://localhost:8050/render.html?" + urlencode({ 'url' : link.url })
    return links

Scrapy-Splash (рекомендуется)

Предпочтительный способ интеграции Splash со Scrapy - использование scrapy-splash. См. Здесь, чтобы узнать, почему рекомендуется использовать промежуточное ПО вместо того, чтобы использовать его вручную. Вы можете установить scrapy-splash с помощью pip:

pip install scrapy-splash

Чтобы использовать ScrapyJS в вашем проекте, вам сначала нужно включить промежуточное ПО:

DOWNLOADER_MIDDLEWARES = {
    'scrapy_splash.SplashCookiesMiddleware': 723,
    'scrapy_splash.SplashMiddleware': 725,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}

Промежуточное ПО должно иметь приоритет над HttpProxyMiddleware, который по умолчанию находится в позиции 750, поэтому мы устанавливаем позиции промежуточного ПО на числа ниже 750.

Затем вам нужно установить параметр SPLASH_URL в файле settings.py вашего проекта:

SPLASH_URL = 'http://localhost:8050/'

Не забывайте, что если вы используете Docker Machine в OS X или Windows, вам нужно будет указать IP-адрес виртуальной машины Docker, например:

SPLASH_URL = 'http://192.168.59.103:8050/'

Включите SplashDeduplicateArgsMiddleware для поддержки функции cache_args: это позволяет экономить дисковое пространство, не сохраняя повторяющиеся аргументы Splash несколько раз в очереди запросов к диску. Если используется Splash 2.1+, промежуточное ПО также позволяет экономить сетевой трафик, не отправляя эти повторяющиеся аргументы на сервер Splash несколько раз.

SPIDER_MIDDLEWARES = {
    'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}

В настоящее время Scrapy не предоставляет возможности глобально переопределить вычисление отпечатков пальцев, поэтому вам также придется установить пользовательский DUPEFILTER_CLASS и пользовательский бэкэнд для хранения кеша:

DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'

Если вы уже используете другой бэкэнд кеш-хранилища, вам нужно будет создать его подкласс и заменить все вызовы scrapy.util.request.request_fingerprint на scrapy_splash.splash_request_fingerprint.

Теперь, когда промежуточное ПО Splash включено, вы можете использовать SplashRequest вместо scrapy.Request для визуализации страниц с помощью Splash.

Например, если бы мы хотели получить обработанный HTML-код для страницы, мы могли бы сделать что-то вроде этого:

import scrapy
from scrapy_splash import SplashRequest

class MySpider(scrapy.Spider):
    start_urls = ["http://example.com", "http://example.com/foo"]

    def start_requests(self):
        for url in self.start_urls:
            yield SplashRequest(url, self.parse,
                endpoint='render.html',
                args={'wait': 0.5},
            )

    def parse(self, response):
        # response.body is a result of render.html call; it
        # contains HTML processed by a browser.
        # …

Словарь args содержит аргументы для отправки в Splash. Вы можете найти полный список доступных аргументов в документации HTTP API. По умолчанию конечная точка установлена ​​на render.json, но здесь мы переопределили ее и установили для нее значение render.html, чтобы предоставить ответ HTML.

Запуск пользовательского JavaScript

Иногда вам может потребоваться нажать кнопку или закрыть модальное окно для правильного просмотра страницы. Splash позволяет запускать собственный код JavaScript в контексте запрашиваемой веб-страницы. Это можно сделать несколькими способами:

Использование параметра js_source

Вы можете использовать параметр js_source для отправки JavaScript, который вы хотите выполнить. Код JavaScript выполняется после завершения загрузки страницы, но до отображения страницы. Это позволяет использовать код JavaScript для изменения отображаемой страницы. Например, вы можете сделать это с помощью Scrapy-Splash:

# Render page and modify its title dynamically
yield SplashRequest(
    'http://example.com',
    endpoint='render.html',
    args={'js_source': 'document.title="My Title";'},
)

Скрипты заставки

Splash поддерживает сценарии Lua через конечную точку выполнить. Это предпочтительный способ выполнения JavaScript, поскольку вы можете предварительно загрузить библиотеки, выбрать, когда выполнять JavaScript, и получить результат.

Вот пример сценария:

function main(splash)
    assert(splash:go(splash.args.url))
    splash:wait(0.5)
    local title = splash:evaljs("document.title")
    return {title=title}
end

Вам необходимо отправить этот сценарий в конечную точку execute в аргументе lua_source.

Это вернет объект JSON, содержащий заголовок:

{
    "title": "Some title"
}

Каждый сценарий требует, чтобы функция main действовала как точка входа. Вы можете вернуть таблицу Lua, которая будет отображаться как JSON, что мы и сделали здесь. Мы используем функцию splash: go, чтобы указать Splash посетить URL-адрес. Функция splash: evaljs позволяет выполнять JavaScript в контексте страницы, однако, если вам не нужен результат, вы должны использовать вместо него splash: runjs.

Вы можете протестировать свои сценарии Splash в браузере, посетив страницу индекса вашего экземпляра Splash (например, http: // localhost: 8050 /). Также можно использовать Splash с записной книжкой IPython в качестве интерактивной веб-среды разработки, подробнее см. Здесь.

Часто бывает так, что вам нужно нажать кнопку перед отображением страницы. Мы можем сделать это с помощью функции splash: mouse_click:

function main(splash)
    assert(splash:go(splash.args.url))
    local get_dimensions = splash:jsfunc([[
        function () {
            var rect = document.getElementById('button').getClientRects()[0];
            return {"x": rect.left, "y": rect.top}
        }
    ]])
    splash:set_viewport_full()
    splash:wait(0.1)
    local dimensions = get_dimensions()
    splash:mouse_click(dimensions.x, dimensions.y)
    -- Wait split second to allow event to propagate.
    splash:wait(0.1)
    return splash:html()
end

Здесь мы используем splash: jsfunc, чтобы определить функцию, которая будет возвращать координаты элемента, затем убедитесь, что элемент отображается с помощью splash: set_viewport_full, и нажмите элемент. Затем Splash возвращает обработанный HTML.

Заворачивать

Вы можете найти больше информации о запуске JavaScript с помощью Splash в документации, а для более подробного руководства ознакомьтесь с Руководством по скриптам Splash.

Мы надеемся, что это руководство дало вам хорошее представление о Splash, и, пожалуйста, дайте нам знать, если у вас есть какие-либо вопросы или комментарии!

Этот пост написал Ричард Дауинтон, бывший разработчик программного обеспечения в Zyte (ранее Scrapinghub).

Пожалуйста, сердечно «Рекомендую» поделиться этим учебником повсюду.

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

Хакерский полдень - это то, с чего хакеры начинают свои дни. Мы часть семьи @AMI. Сейчас мы принимаем заявки и рады обсудить рекламные и спонсорские возможности.

Если вам понравился этот рассказ, мы рекомендуем прочитать наши Последние технические истории и Современные технические истории. До следующего раза не воспринимайте реалии мира как должное!