Strona główna / Artykuły

Wyznaczanie ukrytych cen poprzez odwrotne projektowanie wywołań API w tle

Znajdź ceny, które nigdy nie pojawiają się w HTML, obserwując DevTools pod kątem wywołań XHR, REST i GraphQL w tle strony, a następnie odtwarzając te żądania bezpośrednio.

Wydawca: REACTAPP.TOP5 min czytania1213 słów

Źródła: Talking to the API Instead of the Page

Drugi tom serii „Jak naprawdę działa scraping”

W poprzednim artykule napotkaliśmy problem: czasami cena, której potrzebujemy, po prostu nie występuje nigdzie w HTML pobieranym przez requests. Nie jest ona zawarta w JSON-LD, nie znajduje się w tagu meta – po prostu jej nie ma. To nie oznacza, że utknęliśmy – zazwyczaj oznacza to jedynie, że strona ładuje te dane później po początkowym załadowaniu, poprzez oddzielną żądanie w tle. Celem jest znalezienie tego żądania i wykonywanie go samodzielnie.

Krok 1: Otwórz kartę Sieć

Będziemy pracować na konkretnym przykładzie: stronie produktu na stronie Target, target.com/p/dyson-ball-animal-3-upright-vacuum/-/A-85269291 (służy to tylko do demonstracji – ta sama metoda działa na praktycznie każdej stronie produktu).

  1. Zaloguj się na dowolną stronę produktu.
  • Kliknij prawym przyciskiem myszy na stronę i wybierz „Inspekcja” (lub naciśnij F12).
  • Przejdź do panelu Network.
  • Nałóż ponownie stronę.
  • Zastosuj filtr Fetch/XHR — to ukryje żądania obrazów, plików CSS i czcionek, pozostawiając tylko te żądania, które faktycznie przekazują dane.
  • Krok 2: Znajdź żądanie, którego naprawdę potrzebujesz

    Przejrzyj listę po jednym wpisie, klikając każdy z nich i sprawdzając panel Response lub Preview po prawej stronie. Szukasz treści w formacie JSON, która zawiera cenę, liczbę recenzji lub dowolną inną wartość, której szukasz.

    Istnieje szybsza droga: użyj pola wyszukiwania w postaci lupy znajdującego się na karcie Network i wpisz wartość widoczną bezpośrednio na stronie. W przykładzie Target wpisanie „449.99” prowadzi bezpośrednio do żądania POST o nazwie deferred_enrichment/modules. Jego ciało odpowiedzi to spory obiekt JSON, a w jego wnętrzu znajdziesz „formatted_current_price”: „$449.99”.

    Krok 3: Samodzielne odtworzenie żądania

    Wybierz to żądanie i zapisz jego pełną adres URL. W tym przypadku jest to żądanie POST, ale wszystko, czego potrzebuje, znajduje się już w łańcuchu zapytania:

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

    Oto jak to wygląda po odtworzeniu w Pythonie:

    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
    

    To właśnie cały sekret. Żadnych bibliotek do parsowania, żadnych selektorów CSS – po prostu wysyłasz to samo zapytanie, które w tle wysyła własny JavaScript strony.

    Wskazówka: w DevTools kliknij prawym przyciskiem myszy na żądanie i wybierz Kopiuj → Skopiuj jako cURL. Dzięki temu uzyskasz dokładne nagłówki i pliki cookie, które przeglądarka dołączyła, a które możesz wkleić do narzędzia takiego jak Postman lub przekształcić w kod w Pythonie, aby ustalić, które elementy są faktycznie wymagane.

    Krok 4: Gdy API jest typu GraphQL

    Część stron przekazuje wszystkie dane przez GraphQL zamiast tradycyjnego rozwiązania REST. Możesz rozpoznać ten wzorzec, ponieważ każde żądanie trafia do tego samego punktu końcowego (często /graphql), a rzeczywiste pytanie znajduje się w ciele żądania, a nie w adreście URL:

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

    Wiele stron idzie o krok dalej i używa trwałego zapytania, przy którym zamiast przesyłać pełny tekst zapytania, klient wysyła tylko krótki hash, który go zastępuje:

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

    To hasz nie jest czymś, co można samodzielnie wygenerować — trzeba je pobrać bezpośrednio z DevTools. A ponieważ jest ono powiązane z budową frontendu strony, aktualizacja po ich stronie może zmienić hasz i w ten sposób potajemnie zepsuć twój skrypt. To rzeczywisty, ciągły koszt polegania na tej technice.

    Krok 5: Sprawdzenie, czy ten klucz API jest rzeczywiście prywatny

    Rozpatrz ponownie adres URL z kroku 3 — zwróć uwagę na key=9f36aeafbe60771e321a7cc95a78140772ab3e96 widniejący otwarcie w ciągu zapytania. Czy to coś, do czego nie powinieneś mieć dostępu?

    Zazwyczaj nie. Naj szybszym sposobem na weryfikację jest otwarcie opcji Przegląd źródła strony (Ctrl+U), a następnie wyszukiwanie apiKey za pomocą Ctrl+F. Znajdziesz dokładnie ten sam klucz w formacie tekstowym, w bloku konfiguracyjnym, który Target wysyła do każdego odwiedzającego.

    Jeśli wartość znajduje się już w własnym markup strony — bez żadnego omijania mechanizmów i bez konieczności autoryzacji — to od początku nie była poufna. To po prostu klucz publiczny przeznaczony do działania w przeglądarce. (Czasami znajduje się on zamiast tego w oddzielnym pliku .js; jeśli opcja „Przegląd źródła” nic nie pokazuje, spróbuj panelu „Źródła” w narzędziach deweloperskich i wyszukaj za pomocą Ctrl+Shift+F).

    Dobra praktyka: zanim zaczniesz trudzić się nad odwróconą inżynierią jakiegoś punktu końcowego, sprawdź, czy firma nie oferuje już udokumentowanej publicznej lub partnerskiej API. Takie rozwiązania zazwyczaj są znacznie bardziej stabilne niż te odkryte przypadkowo w narzędziach deweloperskich.

    Krok 6: Gdy dane w ogóle nie pochodzą od sprzedawcy

    Czasami żądanie, które udaje się namierzyć, w ogóle nie trafia na domenę samego sprzedawcy — zamiast tego wskazuje na usługę third-party, taką jak bazaarvoice.com lub judge.me. To oznaka, że sprzedawca nie posiada własnej infrastruktury recenzji; korzysta z systemu dostawcy i po prostu wstawia jego widget na stronie.

    Te API widgetów zarządzane przez dostawców są zazwyczaj mniej chronione niż główna strona sprzedawcy, ponieważ zostały stworzone tak, by być używanymi w tysiącach niepowiązanych sklepów internetowych, a nie specjalnie do wykrywania i blokowania narzędzi do pobierania danych.

    To, co ten artykuł celowo pominął

    Jeśli postąpiłeś zgodnie ze wszystkimi powyższymi krokami, a żądanie, którego potrzebujesz, nadal zwraca błąd 403, pusty treść lub coś przypominającego punkt kontroli bezpieczeństwa zamiast rzeczywistych danych, to nie jest to błąd w twoim kodzie. Oznacza to, że strona aktywnie wykryła, iż nie jesteś standardową sesją przeglądarki. Ten problem jest na tyle poważny, by zasłużyć na osobną dyskusję, do której właśnie przechodzi następny artykuł.

    Glosariusz

    Karta Sieć — panel narzędzi deweloperskich, który rejestruje każde żądanie wysyłane przez stronę, wraz z tym, co zostało wysłane i co wróciło.

    XHR / Fetch — dwa mechanizmy używane przez JavaScript do wysyłania żądań w tle bez pełnego ładowania strony; kartę Sieć można filtrować, aby pokazywała tylko te żądania.

    REST API — powszechny wzorzec API, w którym każdy zasób ma swoją dedykowaną adresację URL, na przykład /products/123/price.

    GraphQL — styl API oparty na jednym punkcie końcowym, gdzie ciało żądania określa dokładnie, jakie dane są wymagane.

    Persisted query — skrót w GraphQL, przy którym pełny tekst zapytania jest zastępowany kompaktowym haszem, który serwer już rozpoznaje.

    Klucz API — wartość dołączana do żądania w celu jego identyfikacji; niektóre z nich są rzeczywiście prywatne, ale wiele to publiczne klucze przeznaczone dla klientów, widoczne w przeglądarce każdego użytkownika.

    Kopiuj jako cURL — opcja w narzędziach deweloperskich, która przekształca zapisane żądanie w wykonywalny polecenie w linii poleceń, przydatne do sprawdzenia dokładnych nagłówków i plików cookie.

    Widget od third party — funkcja, taka jak sekcja z recenzjami, której strona nie tworzy samodzielnie, lecz wstawia ją z produktu innej firmy.

    Literatura pokrewna