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.
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).
- Zaloguj się na dowolną stronę produktu.
F12). 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
- Wybór frameworku agenta AI w Pythonie w 2026 roku: Praktyczne porównanie — Porównuje pięć frameworków agentów AI w Pythonie pod kątem sposobu radzenia sobie z błędami i złożonością, pomagając czytelnikom dobrać odpowiedni narzędzie do swojego procesu pracy.
- Ukryte narzędzia JavaScripta: Symbole, WeakMaps, Proksy i generatorów — Dowiedz się, jak symbole, WeakMap/WeakSet, Proxy/Reflect, FinalizationRegistry oraz generatorów działają w tle, aby zapobiegać wyciekom pamięci i umożliwiać metaprogramowanie na poziomie frameworków.