Главная / Статьи

Обнаружение скрытых цен путем обратной разработки вызовов API фона

Определите цены, которые никогда не отображаются в HTML, наблюдая за запросами XHR, REST и GraphQL в DevTools, а затем повторно отправляя эти запросы напрямую.

Издатель: REACTAPP.TOP5 мин чтения1213 слов

Источники: Talking to the API Instead of the Page

Вторая часть серии «Как на самом деле работает скрапинг»

В предыдущей статье мы наткнулись на проблему: иногда необходимая цена вообще отсутствует в HTML-коде, который загружает библиотека requests. Она не встроена в JSON-LD, не находится в мета-теге — её просто нигде нет. Но это не значит, что вы в тупике: обычно данные загружаются позже первоначальной загрузки страницы через отдельный фоновый запрос. Наша задача — найти этот запрос и выполнить его сами.

Шаг 1: Откройте вкладку «Сеть»

Мы рассмотрим конкретный пример: страницу товара на сайте Target, target.com/p/dyson-ball-animal-3-upright-vacuum/-/A-85269291 (это только для демонстрации — тот же подход работает практически с любой страницей товара).

  1. Откройте любую страницу товара.
  • Щелкните правой кнопкой мыши по странице и выберите «Просмотреть» (или нажмите F12).
  • Перейдите на панель Сеть.
  • Перезагрузите страницу.
  • Примените фильтр Fetch/XHR — это скроет запросы к изображениям, CSS и шрифтам, оставив только те запросы, которые действительно передают данные.
  • Шаг 2: Найдите необходимый запрос

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

    Существует более быстрый способ: используйте поле поиска в виде лупы на вкладке «Сеть» и введите значение, видимое непосредственно на странице. В примере с Target ввод «449.99» напрямую приводит к запросу типа POST с именем deferred_enrichment/modules. Тело ответа представляет собой крупный JSON-объект, и внутри него вы найдете строку "formatted_current_price":"$449.99.

    Шаг 3: Повторите запрос сами

    Выберите этот запрос и запишите его полный URL. В данном случае это запрос типа POST, но все необходимое уже содержится в строке запроса:

    https://www.target.com/cdui_orchestrations/v1/pages/pdp/deferred_enrichment/modules
        ?tcin=85269291
        &key=9f36aeafbe60771e321a7cc95a78140772ab3e96
        &channel=WEB
        &device_type=DESKTOP
    

    Вот как это выглядит при имитации на Python:

    import requests
    
    url = "https://www.target.com/cdui_orchestrations/v1/pages/pdp/deferred_enrichment/modules"
    params = {
        "tcin": "85269291",       # the product ID, from the page URL
        "key": "9f36aeafbe60771e321a7cc95a78140772ab3e96",
        "channel": "WEB",
        "device_type": "DESKTOP",
    }
    
    resp = requests.post(url, params=params, headers={"User-Agent": "Mozilla/5.0"})
    data = resp.json()
    
    price = data["modules"][0]["module_data"]["data"]["product"]["price"]["formatted_current_price"]
    print(price)  # $449.99
    

    В этом вся суть. Никаких библиотек для парсинга, никаких CSS-селекторов — вы просто отправляете тот же запрос, который в фоновом режиме использует JavaScript страницы.

    Совет: в DevTools щелкните правой кнопкой мыши по запросу и выберите Копировать → Копировать как cURL. Это позволит получить точные заголовки и куки, которые использовал браузер, их можно вставить в такой инструмент, как Postman, или преобразовать в код на Python, чтобы выяснить, какие именно элементы действительно необходимы.

    Шаг 4: Когда API основано на GraphQL

    Некоторые сайты используют для обработки запросов механизм GraphQL вместо традиционной схемы REST. Эту схему можно распознать по тому, что каждый запрос направляется на один и тот же конечный пункт (часто /graphql), а сам вопрос, задаваемый серверу, находится в теле запроса, а не в URL:

    {
      "operationName": "GetPrice",
      "variables": { "productId": "12345" },
      "query": "query GetPrice($productId: ID!) { product(id: $productId) { price } }"
    }
    

    Некоторые сайты идут еще дальше, используя персистентные запросы: вместо передачи полного текста запроса клиент отправляет лишь короткий хеш, который заменяет его:

    /graphql/GetPrice/a1b2c3d4e5f6...?variables={"productId":"12345"}
    

    Этот хеш невозможно получить самостоятельно — его нужно взять непосредственно из DevTools. Поскольку он связан с фронтенд-версией сайта, любое обновление с их стороны может изменить хеш и тихо сломать ваш скрипт. Это реальные постоянные издержки использования этой техники.

    Шаг 5: Проверка того, действительно ли ключ API является приватным

    Снова посмотрите на URL-адрес из шага 3 — обратите внимание на key=9f36aeafbe60771e321a7cc95a78140772ab3e96, который открыто присутствует в строке запроса. Разве к этому у вас не должен быть доступ?

    Как правило, нет. Самый быстрый способ проверки — открыть вкладку Просмотр исходного кода страницы (Ctrl+U), затем найти apiKey с помощью Ctrl+F. Вы увидите точно такой же ключ в простом текстовом виде внутри блока конфигурации, который Target отправляет каждому посетителю.

    Если значение уже присутствует в собственном маркировочном коде страницы — без какого-либо обхода механизмов и без необходимости аутентификации — оно изначально никогда не было конфиденциальным. Это просто публичный ключ, предназначенный для работы в браузере. (Иногда он находится в отдельном файле .js; если в режиме «Просмотр исходного кода» ничего не появляется, попробуйте использовать панель «Источники» в инструментах разработчика и выполнить поиск с помощью Ctrl+Shift+F).

    Хорошая практика: прежде чем тратить время на обратную разработку конкретного эндпоинта, проверьте, не предлагает ли компания уже документированный публичный или API для партнеров. Такие решения, как правило, гораздо стабильнее, чем те, которые были найдены путем экспериментов в инструментах разработчика.

    Шаг 6: Когда данные вообще не поступают от продавца

    Иногда запрос, который вы отслеживаете, даже не направляется на собственный домен продавца — вместо этого он указывает на сервис сторонней организации, такой как bazaarvoice.com или judge.me. Это свидетельствует о том, что продавец не использует собственную инфраструктуру для отзывов; он покупает лицензию на систему у поставщика и просто встраивает его вижет на страницу.

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

    Что в этой статье намеренно не упоминается

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

    Глоссарий

    Вкладка «Сеть» — панель DevTools, которая записывает каждый запрос, отправляемый страницей, а также то, что было отправлено и что вернулось.

    XHR / Fetch — два механизма, которые использует JavaScript для отправки запросов в фоновом режиме без полной перезагрузки страницы; вкладку «Сеть» можно отфильтровать, чтобы показывать только такие запросы.

    REST API — это распространённый шаблон API, при котором у каждого ресурса есть свой собственный URL, например /products/123/price.

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

    Persisted query — это сокращение в GraphQL, при котором полный текст запроса заменяется компактным хешем, уже известным серверу.

    API key — это значение, привязанное к запросу для его идентификации; некоторые из них действительно приватны, но многие являются публичными ключами, предназначенными для использования клиентами и видимыми в браузере любого пользователя.

    Copy as cURL — это опция в DevTools, которая преобразует зафиксированный запрос в команду для выполнения в командной строке, что удобно для проверки его точных заголовков и куки.

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