Головна / Статті

Виявлення прихованих цін шляхом зворотної інженерії викликів API у фоновому режимі

Знайдіть ціни, які ніколи не відображаються в HTML, спостерігаючи за інструментами DevTools за запитами XHR, REST та GraphQL, що відбуваються на сторінці, а потім повторно надсилаючи ці запити безпосередньо.

Видавець: 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, який відкрито знаходиться у рядку запиту. Чи є це щось, до чого вам не слід мати доступ?

    Зазвичай ні. Найшвидший спосіб перевірки — це відкрити View Page Source (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 — опція у інструментах розробника, яка перетворює зафіксований запит на команду для виконання в командному рядку, що зручно для перевірки його точних заголовків та кукуріз.

    Віджет від сторонньої компанії — це функція, така як розділ відгуків, яку сайт не створює самостійно, а вбудовує з продукту іншої компанії.