API vs HTML — pierwszy wybór przy scrapowaniu Walmart
Zanim napiszesz jedną linijkę kodu, musisz podjąć decyzję: czy uderzać w publiczne API Walmartu, czy pobierać HTML i wyciągać dane z niego? Odpowiedź nie jest oczywista, ale dla większości zespołów CPG i retail intelligence HTML z embedded JSON jest szybszą ścieżką.
Walmart nie oferuje publicznego API produktowego z otwartym dostępem. Oficjalne Walmart Affiliate API wymaga klucza partnera, ma restrykcyjne rate-limity i — co kluczowe — nie zwraca danych o 3P sellerach ani real-time inventory. Z kolei Walmart API wewnątrz ich własnej aplikacji (widoczne w network tab) wymaga podpisów kryptograficznych, które rotują z każdym requestem.
Stąd pragmatyczny wybór: scrapować HTML stron produktowych i wyciągać dane z __NEXT_DATA__, czyli ukrytego payloadu JSON, który Walmart embeduje w każdej stronie. Dostajesz w ten sposób wszystko, co widzi użytkownik — ceny, dostępność, ratings, seller info — bez re-engineeringowania ich API.
Struktura katalogu Walmart — URL-e, które musisz znać
Walmart ma trzy główne typy stron, które interesują scrapery:
Strony produktowe (Item Pages)
Format URL: /ip/{slug}/{itemId}
Przykład: https://www.walmart.com/ip/Apple-AirPods-Pro-2nd-Gen/1752667082
- itemId — unikalny numeryczny identyfikator produktu (często 8-13 cyfr). To jest twój klucz primary.
- slug — SEO-friendly nazwa, ale Walmart redirectuje do poprawnego sluga, więc nie musisz go zgadywać. Wystarczy itemId.
- Minimalny działający URL:
https://www.walmart.com/ip//1752667082— redirectnie do pełnego sluga.
Strony kategorii (Category / Shelf Pages)
Format URL: /cp/{category-slug} lub /browse/{category}
Przykład: https://www.walmart.com/cp/electronics/3944
Strony kategorii ładują listę produktów z paginacją. Problem: przy wyższych numerach stron (zwykle > 25) Walmart zwraca 404 lub puste wyniki. Realistycznie z jednej kategorii dostaniesz ~500-1000 produktów. Pełny katalog wymaga search API.
Wyszukiwanie (Search)
Endpoint: /search z parametrem q
Przykład: https://www.walmart.com/search?q=airpods+pro
Search jest najpotężniejszym wejściem do katalogu, ale też najbardziej strzeżonym. Zwraca do ~40 wyników na stronę z paginacją do ~25 stron. Kluczowe parametry query:
q— fraza wyszukiwaniasort—price_low,price_high,best_seller,ratingpage— numer strony (1-indexed)affinityOverride—default(bez personalizacji)
Akamai + PerimeterX — dlaczego residential proxy to nie luksus, lecz wymóg
Walmart używa dwóch warstw anti-bot jednocześnie:
| Warstwa | Technologia | Co robi | Jak omijać |
|---|---|---|---|
| Edge / L7 | Akamai Bot Manager | Fingerprinting TLS, HTTP/2, JA3; rate-limiting per IP; challenge pages (503) | Rotacja IP, residential pool, sensordata spoofing |
| Client-side | PerimeterX (HUMAN) | JS challenge, canvas fingerprint, behavioral analysis | Headless browser z stealth pluginem lub request-based z embedded data |
Co to oznacza w praktyce?
- Datacenter IP — blokowane w ciągu 1-3 requestów. Akamai utrzymuje listę ASN datacenterów i flaguje je natychmiast.
- Residential IP bez rotacji — po ~50-100 requestów z jednego IP dostajesz CAPTCHA lub 403. PerimeterX buduje behavioral profile.
- Residential IP z rotacją per-request — najlepszy kompromis. Każdy request leci z innego IP, więc behavioral profiling nie ma szans się zbudować.
Stąd konkluzja: bezpłatne proxy, datacenter proxy i nawet sticky residential proxy nie wystarczą do scrapowania Walmart na skalę. Potrzebujesz residential proxy z per-request rotacją i opcją geo-targetingu (Walmart serwuje różne ceny i inventory w zależności od lokalizacji).
Z ProxyHat konfiguracja wygląda tak:
# Per-request rotation — USA residential
http://user-country-US:PASSWORD@gate.proxyhat.com:8080
# Sticky session (np. do multi-step checkout research)
http://user-session-abc123-country-US:PASSWORD@gate.proxyhat.com:8080
# City-level targeting — Bentonville, AR (siedziba Walmart)
http://user-country-US-city-bentonville:PASSWORD@gate.proxyhat.com:8080
__NEXT_DATA__ — najłatwiejsza ścieżka do danych produktowych
Walmart rebuildował frontend na Next.js w 2022 roku. Efekt uboczny: każda strona produktowa embeduje pełny stan aplikacji w tagu <script id="__NEXT_DATA__"> w HTML. To jest złoto.
Dlaczego? Bo:
- Nie musisz parsować DOM — dostajesz gotowy JSON.
- Dane są aktualne — to dokładnie to, co frontend renderuje.
- Zawiera wszystko: cenę, inventory, ratings, seller info, varianty, rekomendacje.
- Nie wymaga JS execution — jest w surowym HTML, więc działa z prostym HTTP GET.
Jak znaleźć __NEXT_DATA__
Otwórz dowolną stronę produktową na Walmart, View Source, i szukaj:
<script id="__NEXT_DATA__" type="application/json">
{"props":{"pageProps":{"initialData":{...}}}}
</script>
Struktura kluczy (uproszczona):
{
"props": {
"pageProps": {
"initialData": {
"data": {
"product": {
"itemId": "1752667082",
"name": "Apple AirPods Pro 2nd Gen",
"priceInfo": {
"currentPrice": {"price": 189.00, "priceString": "$189.00" },
"wasPrice": { "price": 249.00 }
},
"availability": "IN_STOCK",
"averageRating": 4.6,
"numberOfReviews": 12453,
"sellerId": "F55CDC31AB754BB68FE0B39041159D63",
"sellerName": "Walmart.com",
"variants": [...],
"shortDescription": "...",
"category": { "categoryPath": "Electronics/Headphones" }
}
}
}
}
}
}
Python — pobieranie i parsowanie __NEXT_DATA__
Poniższy skrypt pokazuje pełny flow: request przez residential proxy, ekstrakcja __NEXT_DATA__, i wyciągnięcie kluczowych pól produktowych.
import json
import re
import requests
from datetime import datetime
PROXY = "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080"
PROXIES = {"http": PROXY, "https": PROXY}
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/125.0.0.0 Safari/537.36"
),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
}
def fetch_product(item_id: str) -> dict:
"""Pobierz stronę produktową i wyciągnij __NEXT_DATA__."""
url = f"https://www.walmart.com/ip//{item_id}"
session = requests.Session()
session.headers.update(HEADERS)
session.proxies.update(PROXIES)
resp = session.get(url, timeout=30)
resp.raise_for_status()
# Ekstrakcja JSON z __NEXT_DATA__
match = re.search(
r'<script id="__NEXT_DATA__" type="application/json">(.+?)</script>',
resp.text,
re.DOTALL
)
if not match:
raise ValueError("__NEXT_DATA__ nie znalezione w HTML")
next_data = json.loads(match.group(1))
product = next_data["props"]["pageProps"]["initialData"]["data"]["product"]
return product
def extract_product_info(product: dict) -> dict:
"""Normalizuj dane produktowe do płaskiej struktury."""
price_info = product.get("priceInfo", {})
current = price_info.get("currentPrice", {})
was = price_info.get("wasPrice", {})
return {
"item_id": product.get("itemId"),
"name": product.get("name"),
"brand": product.get("brand", ""),
"current_price": current.get("price"),
"was_price": was.get("price"),
"currency": current.get("currencyUnit", "USD"),
"availability": product.get("availability", "UNKNOWN"),
"average_rating": product.get("averageRating"),
"review_count": product.get("numberOfReviews"),
"seller_id": product.get("sellerId"),
"seller_name": product.get("sellerName"),
"category_path": product.get("category", {}).get("categoryPath", ""),
"short_description": product.get("shortDescription", ""),
"scraped_at": datetime.utcnow().isoformat(),
}
# --- Użycie ---
if __name__ == "__main__":
product_raw = fetch_product("1752667082")
product_info = extract_product_info(product_raw)
print(json.dumps(product_info, indent=2))
Przykładowy output:
{
"item_id": "1752667082",
"name": "Apple AirPods Pro 2nd Gen",
"brand": "Apple",
"current_price": 189.0,
"was_price": 249.0,
"currency": "USD",
"availability": "IN_STOCK",
"average_rating": 4.6,
"review_count": 12453,
"seller_id": "F55CDC31AB754BB68FE0B39041159D63",
"seller_name": "Walmart.com",
"category_path": "Electronics/Headphones",
"short_description": "Active Noise Cancellation...",
"scraped_at": "2025-07-15T10:30:00.000000"
}
Kluczowa uwaga: ten skrypt nie wymaga Selenium ani Playwright. Ponieważ __NEXT_DATA__ jest server-side rendered, wystarczy zwykły HTTP GET. To dramatycznie zmniejsza koszty infrastruktury i zwiększa throughput.
Walmart Marketplace — 3P sellers vs 1P catalog
To jest jeden z najważniejszych rozróżnień dla zespołów CPG:
| Aspekt | 1P (Walmart jako sprzedawca) | 3P (Marketplace seller) |
|---|---|---|
| sellerId | Zwykle F55CDC31AB754BB68FE0B39041159D63 lub podobny hash | Unikalny hash per seller |
| sellerName | "Walmart.com" | Nazwa firmy 3P |
| Cena | Walmart ustala (często MSRP lub niżej) | Seller ustala samodzielnie |
| Inventory | Walmart fulfillment centers | WFS lub seller self-fulfilled |
| Buy Box | Domyślnie 1P wygrywa | 3P może wygrać, jeśli cena + shipping jest niższa |
| Dane w __NEXT_DATA__ | W product.priceInfo | W tablicy product.buyBoxProducts lub product.offers |
Jak rozróżnić 1P od 3P w danych
W __NEXT_DATA__ szukaj dwóch miejsc:
product.sellerName— jeśli"Walmart.com", to 1P. Cokolwiek innego = 3P.product.buyBoxProducts— tablica wszystkich ofert na Buy Box. Każdy element masellerId,sellerName,price,availability.
Dla zespołów CPG monitorujących własne produkty, kluczowe jest scrapowanie obu warstw: 1P ceny (Walmart może sprzedawać poniżej MSRP) i 3P ofert (nieautoryzowani sprzedawcy, MAP violations).
def extract_all_offers(product: dict) -> list:
"""Wyciągnij wszystkie oferty (1P + 3P) z Buy Box."""
offers = []
# Oferta główna (zwykle 1P lub Buy Box winner)
main_offer = {
"seller_id": product.get("sellerId"),
"seller_name": product.get("sellerName"),
"price": product.get("priceInfo", {}).get("currentPrice", {}).get("price"),
"availability": product.get("availability"),
"is_buybox_winner": True,
}
offers.append(main_offer)
# Alternatywne oferty z Buy Box
buybox_products = product.get("buyBoxProducts", [])
for bbp in buybox_products:
offers.append({
"seller_id": bbp.get("sellerId"),
"seller_name": bbp.get("sellerName"),
"price": bbp.get("price", {}).get("price"),
"availability": bbp.get("availability", "UNKNOWN"),
"is_buybox_winner": False,
})
return offers
Rate-limit-aware scheduling — jak nie dostać bana na skalę
Walmart nie publikuje oficjalnych rate-limitów dla scrapowania (bo scrapowanie jest niezgodne z ich ToS). Na podstawie testów i doświadczeń społeczności:
| Scenariusz | Szacowany limit | Odpowiedź przy przekroczeniu |
|---|---|---|
| Jedno IP, rapid-fire | ~20-30 req/min | 403 / CAPTCHA po 3-5 min |
| Jedno IP, throttled (2-3 req/s) | ~100-150 req/h | Soft block po 1-2h |
| Rotowane residential IP | ~500-1000 req/h | Zależy od pool quality |
| Search pages (bardziej strzeżone) | ~50% niższy limit vs item pages | CAPTCHA szybciej |
Strategie harmonogramowania
1. Rozdziel item pages i search pages na osobne workery.
Search pages są 2-3x bardziej strzeżone. Nie wrzucaj ich do tej samej kolejki.
2. Używaj exponential backoff przy 403/503.
import time
import random
def fetch_with_retry(url, session, max_retries=3):
for attempt in range(max_retries):
try:
resp = session.get(url, timeout=30)
if resp.status_code == 200:
return resp
elif resp.status_code in (403, 503):
# Anti-bot challenge — backoff i zmień IP
wait = (2 ** attempt) + random.uniform(0, 2)
print(f"Anti-bot {resp.status_code}, czekam {wait:.1f}s...")
time.sleep(wait)
continue
else:
resp.raise_for_status()
except requests.RequestException as e:
print(f"Request error: {e}")
time.sleep(2 ** attempt)
return None
3. Rotuj IP per-request dla item pages, sticky session dla multi-step flows.
Przy ProxyHat: domyślny format rotuje IP przy każdym requeście. Dodaj session-* w username, gdy potrzebujesz sesji (np. add-to-cart, checkout research).
4. Dystrybuuj requesty w czasie — nie scrapuj 10,000 SKU w 10 minut.
Rozładowanie 10K SKU przez 4 godziny = ~0.7 req/s. To jest bezpieczny rate z residential proxy pool.
5. Weryfikuj odpowiedź przed parsowaniem.
Zawsze sprawdzaj czy __NEXT_DATA__ istnieje. Jeśli Walmart zwraca CAPTCHA page, nie znajdziesz tego tagu — i Twój parser wywali wyjątek. Dodaj graceful handling.
curl — szybki test z ProxyHat
Zanim postawisz pipeline, przetestuj pojedynczy request:
curl -x http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080 \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-H "Accept: text/html,application/xhtml+xml" \
-H "Accept-Language: en-US,en;q=0.9" \
"https://www.walmart.com/ip//1752667082" \
-o walmart_test.html
# Wyciągnij __NEXT_DATA__
grep -o '__NEXT_DATA__.*</script>' walmart_test.html | head -c 500
Kluczowe wnioski (Key Takeaways)
1. Scrapuj __NEXT_DATA__, nie DOM. To oszczędza 80% pracy — dane są już ustrukturyzowane jako JSON.
2. Residential proxy z per-request rotacją to wymóg, nie opcja. Datacenter IP dostanie bana w 3 requestach. Użyj ProxyHat z
country-USdla amerykańskich cen.3. Rozróżniaj 1P i 3P.
sellerName == "Walmart.com"= 1P. Wszystko inne = marketplace seller. Monitoruj obie warstwy.4. Item pages są łatwiejsze niż search. Search jest 2-3x bardziej strzeżony. Startuj od item pages, potem dodaj search.
5. Throttle do ~0.7-1 req/s z rotacją IP. Nie strzelaj w burstach. 10K SKU przez 4h jest bezpieczniejsze niż 10K SKU przez 10 min.
Etyczne i prawne ramy
Scrapowanie Walmart jest niezgodne z ich Terms of Use. To nie jest porada prawna — konsultuj się z radcą prawnym przed podjęciem działalności scrapowej. W praktyce:
- respect robots.txt — Walmart pozwala na częściowe crawlowanie, ale blokuje boty na stronach produktowych i search.
- nie scrapuj danych osobowych — recenzje z imionami i lokalizacjami mogą podlegać GDPR/CCPA.
- nie przeciążaj infrastruktury — throttling nie tylko chroni Twój IP, ale też jest po prostu przyzwoity.
Więcej o strategiach scrapowania znajdziesz w naszym przewodniku po najlepszych praktykach web scrapingu oraz na stronie SERP tracking.
Gotowy do startu? Załóż konto na dashboard.proxyhat.com i zacznij od residential proxy z geo-targetingiem USA — sprawdź ceny ProxyHat i dostępne lokalizacje.






