Выбор динамически загружаемого контента

Некоторые веб-страницы показывают нужные данные, когда вы загружаете их в веб-браузер. Однако, когда вы загружаете их с помощью Scrapy, вы не можете получить желаемые данные с помощью селекторов.

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

Если вы этого не сделаете, но, тем не менее, можете получить доступ к нужным данным через DOM из своего веб-браузера, см. Предварительный рендеринг JavaScript.

Поиск источника данных

Чтобы извлечь желаемые данные, вы должны сначала найти их исходное местоположение.

Если данные представлены в нетекстовом формате, таком как изображение или документ PDF, используйте сетевой инструмент своего веб-браузера, чтобы найти соответствующий запрос, и воспроизвести его.

Если ваш веб-браузер позволяет выбрать желаемые данные в виде текста, данные могут быть определены во встроенном коде JavaScript или загружены из внешнего ресурса в текстовом формате.

В этом случае вы можете использовать такой инструмент, как wgrep, чтобы найти URL-адрес этого ресурса.

Если оказывается, что данные поступают с исходного URL-адреса, вы должны проверить исходный код веб-страницы определить, где находятся данные.

Если данные поступают с другого URL-адреса, вам потребуется воспроизвести соответствующий запрос.

Проверка исходного кода веб-страницы

Иногда вам нужно проверить исходный код веб-страницы (не DOM), чтобы определить, где находятся некоторые требуемые данные.

Используйте команду fetch Scrapy, чтобы загрузить содержимое веб-страницы в том виде, в каком его видит Scrapy:

scrapy fetch --nolog https://example.com > response.html

Если желаемые данные находятся во встроенном коде JavaScript в элементе <script/>, см. Разбор кода JavaScript.

Если вы не можете найти нужные данные, сначала убедиться, что это не просто Scrapy: загрузить веб-страницу с помощью HTTP-клиента, такого как curl или wget, и посмотрите, можно ли найти информацию в полученном ими ответе.

Если они получат ответ с желаемыми данными, изменить свой Scrapy Request, чтобы он соответствовал таковому у другого HTTP-клиента. Например, попробовать использовать ту же строку пользовательского агента (USER_AGENT) или ту же headers.

Если они также получат ответ без требуемых данных, вам нужно будет предпринять шаги, чтобы сделать ваш запрос более похожим на запрос веб-браузера. См. Воспроизведение запросов.

Воспроизведение запросов

Иногда нам нужно воспроизвести запрос так, как его выполняет наш веб-браузер.

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

Может быть достаточно получить Request с тем же методом HTTP и URL-адресом. Однако вам может также потребоваться воспроизвести тело, заголовки и параметры формы (см. FormRequest) этого запроса.

Поскольку все основные браузеры позволяют экспортировать запросы в формате cURL, Scrapy включает метод from_curl() для генерации эквивалентного Request из команды cURL. Для получения дополнительной информации посетите запрос от curl в разделе сетевых инструментов.

Как только вы получить ожидаемый ответ, можете извлечь желаемые данные из него.

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

Если вы получаете ожидаемый ответ «иногда», но не всегда, возможно, проблема не в вашем запросе, а в целевом сервере. Целевой сервер может быть глючным, перегруженным или запрещённым по некоторым вашим запросам.

Обратите внимание, что для преобразования команды cURL в запрос Scrapy вы можете использовать curl2scrapy.

Обработка разных форматов ответов

Если у вас есть ответ с желаемыми данными, то, как вы извлечете из него нужные данные, зависит от типа ответа:

  • Если ответ — HTML или XML, используйте селекторы как обычно.

  • Если ответ — JSON, используйте json.loads() для загрузки требуемых данных из response.text:

    data = json.loads(response.text)
    

    Если желаемые данные находятся внутри кода HTML или XML, встроенного в данные JSON, вы можете загрузить данный код HTML или XML в Selector, а затем используйте это, как обычно:

    selector = Selector(data['html'])
    
  • Если ответ представляет собой JavaScript или HTML с элементом <script/>, содержащим желаемые данные, см. Разбор кода JavaScript.

  • Если ответ — CSS, используйте регулярное выражение, чтобы извлечь нужные данные из response.text.

  • Если ответ представляет собой изображение или другой формат, основанный на изображениях (например, PDF), прочитать ответ как байты из response.body и используйте решение OCR для извлечения желаемых данных в виде текста.

    Например, вы можете использовать pytesseract. Для чтения таблицы из PDF-файла tabula-py может быть лучшим выбором.

  • Если ответом является SVG или HTML со встроенным SVG, содержащим желаемые данные, вы можете извлечь желаемые данные с помощью селекторов, поскольку SVG основан на XML.

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

Разбор кода JavaScript

Если желаемые данные жестко запрограммированы в JavaScript, вам сначала нужно получить код JavaScript:

  • Если код JavaScript находится в файле JavaScript, просто прочитайте response.text.

  • Если код JavaScript находится в элементе <script/> страницы HTML, используйте селекторы для извлечения текста внутри этого элемента <script/>.

Если у вас есть строка с кодом JavaScript, вы можете извлечь из нее нужные данные:

  • Возможно, вы сможете использовать регулярное выражение для извлечения нужных данных в формате JSON, которые затем можно будет проанализировать с помощью json.loads().

    Например, если код JavaScript содержит отдельную строку, например var data = {"field": "value"};, вы можете извлечь данные данные следующим образом:

    >>> pattern = r'\bvar\s+data\s*=\s*(\{.*?\})\s*;\s*\n'
    >>> json_data = response.css('script::text').re_first(pattern)
    >>> json.loads(json_data)
    {'field': 'value'}
    
  • chompjs предоставляет API для синтаксического анализа объектов JavaScript в dict.

    Например, если код JavaScript содержит var data = {field: "value", secondField: "second value"};, вы можете извлечь данные данные следующим образом:

    >>> import chompjs
    >>> javascript = response.css('script::text').get()
    >>> data = chompjs.parse_js_object(javascript)
    >>> data
    {'field': 'value', 'secondField': 'second value'}
    
  • В противном случае используйте js2xml для преобразования кода JavaScript в XML-документ, который можно проанализировать с помощью селекторов.

    Например, если код JavaScript содержит var data = {field: "value"};, вы можете извлечь данные данные следующим образом:

    >>> import js2xml
    >>> import lxml.etree
    >>> from parsel import Selector
    >>> javascript = response.css('script::text').get()
    >>> xml = lxml.etree.tostring(js2xml.parse(javascript), encoding='unicode')
    >>> selector = Selector(text=xml)
    >>> selector.css('var[name="data"]').get()
    '<var name="data"><object><property name="field"><string>value</string></property></object></var>'
    

Предварительный рендеринг JavaScript

На веб-страницах, которые получают данные из дополнительных запросов, воспроизведение тех запросов, которые содержат нужные данные, является предпочтительным подходом. Усилия часто окупаются: структурированные, полные данные с минимальным временем анализа и передачей по сети.

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

В данных случаях используйте службу Splash JavaScript-рендеринга вместе с scrapy-splash для бесшовной интеграции.

Splash возвращает в формате HTML DOM веб-страницы, чтобы вы могли проанализировать его с помощью селекторы. Он обеспечивает большую гибкость за счет конфигурации или сценариев.

Если вам нужно что-то помимо того, что предлагает Splash, например, взаимодействие с DOM на лету из кода Python вместо использования ранее написанного сценария или обработка нескольких окон веб-браузера, вам может потребоваться использовать безголовый браузер вместо этого.

Использование безголового браузера

Безголовый браузер — это специальный веб-браузер, который предоставляет API для автоматизации. Установив asyncio реактор, можно интегрировать библиотеки на основе asyncio, которые работают с автономными браузерами.

Одна из таких библиотек — playwright-python (официальный порт Python для playwright). Ниже приведён простой фрагмент, иллюстрирующий его использование в пауке Scrapy:

import scrapy
from playwright.async_api import async_playwright

class PlaywrightSpider(scrapy.Spider):
    name = "playwright"
    start_urls = ["data:,"]  # avoid using the default Scrapy downloader

    async def parse(self, response):
        async with async_playwright() as pw:
            browser = await pw.chromium.launch()
            page = await browser.new_page()
            await page.goto("https:/example.org")
            title = await page.title()
            return {"title": title}

Однако использование playwright-python напрямую, как в приведённом выше примере, позволяет обойти большинство компонентов Scrapy (промежуточное ПО, dupefilter и т. д.). Мы рекомендуем использовать scrapy-playwright для лучшей интеграции.