Jak scrapować Walmart — praktyczny przewodnik po katalogu, anti-bocie i danych produktowych

Kompletny przewodnik scrapowania Walmart: struktura katalogu, omijanie Akamai+PerimeterX, parsowanie __NEXT_DATA__ z Pythonem i harmonogramowanie z rate-limitami. Dla zespołów CPG i retail intel.

Jak scrapować Walmart — praktyczny przewodnik po katalogu, anti-bocie i danych produktowych

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 wyszukiwania
  • sortprice_low, price_high, best_seller, rating
  • page — numer strony (1-indexed)
  • affinityOverridedefault (bez personalizacji)

Akamai + PerimeterX — dlaczego residential proxy to nie luksus, lecz wymóg

Walmart używa dwóch warstw anti-bot jednocześnie:

WarstwaTechnologiaCo robiJak omijać
Edge / L7Akamai Bot ManagerFingerprinting TLS, HTTP/2, JA3; rate-limiting per IP; challenge pages (503)Rotacja IP, residential pool, sensordata spoofing
Client-sidePerimeterX (HUMAN)JS challenge, canvas fingerprint, behavioral analysisHeadless 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:

Aspekt1P (Walmart jako sprzedawca)3P (Marketplace seller)
sellerIdZwykle F55CDC31AB754BB68FE0B39041159D63 lub podobny hashUnikalny hash per seller
sellerName"Walmart.com"Nazwa firmy 3P
CenaWalmart ustala (często MSRP lub niżej)Seller ustala samodzielnie
InventoryWalmart fulfillment centersWFS lub seller self-fulfilled
Buy BoxDomyślnie 1P wygrywa3P może wygrać, jeśli cena + shipping jest niższa
Dane w __NEXT_DATA__W product.priceInfoW tablicy product.buyBoxProducts lub product.offers

Jak rozróżnić 1P od 3P w danych

W __NEXT_DATA__ szukaj dwóch miejsc:

  1. product.sellerName — jeśli "Walmart.com", to 1P. Cokolwiek innego = 3P.
  2. product.buyBoxProducts — tablica wszystkich ofert na Buy Box. Każdy element ma sellerId, 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:

ScenariuszSzacowany limitOdpowiedź przy przekroczeniu
Jedno IP, rapid-fire~20-30 req/min403 / CAPTCHA po 3-5 min
Jedno IP, throttled (2-3 req/s)~100-150 req/hSoft block po 1-2h
Rotowane residential IP~500-1000 req/hZależy od pool quality
Search pages (bardziej strzeżone)~50% niższy limit vs item pagesCAPTCHA 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-US dla 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.

Gotowy, aby zacząć?

Dostęp do ponad 50 mln rezydencjalnych IP w ponad 148 krajach z filtrowaniem AI.

Zobacz cenyProxy rezydencjalne
← Powrót do Bloga