# Walmart — Product Search & Data Extraction Field-tested against walmart.com on 2026-04-18 using `http_get` (no browser required). All code blocks were run and outputs verified against live responses. --- ## Fastest Approach: `http_get` with `__NEXT_DATA__` Walmart's Next.js SSR embeds the full search or product payload as JSON in a `', html, re.DOTALL) if not m: raise ValueError("__NEXT_DATA__ not found — page structure may have changed") return json.loads(m.group(1)) ``` --- ## Search Results ### URL patterns ```python # Keyword search "https://www.walmart.com/search?q=laptop" # Pagination — append &page=N "https://www.walmart.com/search?q=laptop&page=2" # Sort options (confirmed working) "https://www.walmart.com/search?q=laptop&sort=best_match" # default "https://www.walmart.com/search?q=laptop&sort=best_seller" "https://www.walmart.com/search?q=laptop&sort=price_low" "https://www.walmart.com/search?q=laptop&sort=customer_rating" # Price filter "https://www.walmart.com/search?q=laptop&min_price=200&max_price=500" # Browse by category (department ID path) "https://www.walmart.com/browse/electronics/laptops/3944_1089430_3951" ``` ### `__NEXT_DATA__` path to items ``` data .props.pageProps.initialData.searchResult .aggregatedCount — int: total matching products (e.g. 18818) .paginationV2.maxPage — int: last page number .itemStacks[] — array of stacks (usually 2: sponsored + organic) .items[] — array of product objects ``` ### Full extractor (field-tested) ```python def extract_search_results(html): """ Returns (items, total_count, max_page). items is a list of dicts with confirmed fields. """ data = parse_next_data(html) sr = data["props"]["pageProps"]["initialData"]["searchResult"] items = [] for stack in sr.get("itemStacks", []): for item in stack.get("items", []): pi = item.get("priceInfo") or {} img = item.get("imageInfo") or {} rating = item.get("rating") or {} avail = item.get("availabilityStatusV2") or {} items.append({ "usItemId": item.get("usItemId"), # str, Walmart item ID "name": item.get("name"), # str "brand": item.get("brand"), # str or None "price": item.get("price"), # int, current price in USD "linePrice": pi.get("linePrice"), # str "$429.00" "wasPrice": pi.get("wasPrice") or None, # str "$699.00" or None "savings": pi.get("savings") or None, # str "SAVE $270.00" or None "averageRating": rating.get("averageRating"), # float e.g. 4.3 "numberOfReviews": rating.get("numberOfReviews"), # int "availability": avail.get("value"), # "IN_STOCK" / "OUT_OF_STOCK" "isSponsored": bool(item.get("isSponsoredFlag")), "url": "https://www.walmart.com" + (item.get("canonicalUrl") or "").split("?")[0], "thumbnailUrl": img.get("thumbnailUrl"), }) total = sr.get("aggregatedCount") max_page = (sr.get("paginationV2") or {}).get("maxPage") return items, total, max_page # Usage html = fetch_walmart("https://www.walmart.com/search?q=laptop") items, total, max_page = extract_search_results(html) # items: 66 items on page 1, total=18818, max_page=11 # Filter out sponsored organic = [i for i in items if not i["isSponsored"]] ``` ### Field notes (confirmed) - **`usItemId`**: string, matches the numeric ID at the end of `/ip/.../ITEMID` URLs. Some non-product rows (ad widgets) have `usItemId=None` — filter with `if item.get("usItemId")`. - **`price`**: integer cents-less price (e.g. `429` for "$429.00"). Use `priceInfo.linePrice` for the formatted string including the dollar sign. - **`wasPrice` / `savings`**: only present when item is on sale. Always `None` for full-price items. - **`isSponsoredFlag`**: the first batch of results across both itemStacks are frequently sponsored. On a laptop search, ~56 of 66 SSR items carry `isSponsoredFlag: true`. - **`rating`**: present on ~91% of items (60/66 in test). `averageRating` is a float; `numberOfReviews` is int. - **`canonicalUrl`**: always includes `?classType=...&athbdg=...` query params — strip with `.split("?")[0]` to get a clean URL. - **Two itemStacks**: Walmart returns two stacks (`itemStacks[0]` and `itemStacks[1]`). Merge them. `itemStacks[0]` is the primary grid; `itemStacks[1]` is a secondary sponsored/related block. ### Pagination ```python for page in range(1, max_page + 1): html = fetch_walmart(f"https://www.walmart.com/search?q=laptop&page={page}") items, _, _ = extract_search_results(html) # process items... ``` Page responses average ~2.5 s each. No rate-limiting was observed across 3 sequential requests. For bulk scraping, add a 1–2 s delay between requests to be safe. --- ## Product Detail Page ### URL pattern ``` https://www.walmart.com/ip/{slug}/{usItemId} ``` The slug is ignored in routing — only the numeric `usItemId` matters. These work identically: ``` https://www.walmart.com/ip/anything/19717318352 https://www.walmart.com/ip/Apple-MacBook-Neo/19717318352 ``` ### `__NEXT_DATA__` path on a product page ``` data.props.pageProps.initialData.data .product — core product object .idml — long description, specs, highlights, warranty .reviews — rating breakdown + first 10 customer reviews (SSR) ``` ### Full extractor (field-tested) ```python def extract_product_detail(html): """ Returns a dict with all confirmed product fields. idml.specifications returns all spec rows as a flat dict. reviews returns the SSR-rendered first 10 customer reviews. """ data = parse_next_data(html) d = data["props"]["pageProps"]["initialData"]["data"] product = d["product"] idml = d.get("idml") or {} reviews = d.get("reviews") or {} pi = product.get("priceInfo") or {} cp = pi.get("currentPrice") or {} img = product.get("imageInfo") or {} avail = product.get("availabilityStatusV2") or {} specs = { spec.get("name"): spec.get("value") for spec in (idml.get("specifications") or []) } all_images = [ img_item.get("url") for img_item in (img.get("allImages") or []) if img_item.get("url") ] customer_reviews = [ { "title": r.get("reviewTitle"), "rating": r.get("rating"), # int 1-5 (field is "rating", NOT "overallRating") "text": r.get("reviewText"), "author": r.get("userNickname"), "date": r.get("reviewSubmissionTime"), } for r in (reviews.get("customerReviews") or []) ] return { # identity "usItemId": product.get("usItemId"), "name": product.get("name"), "brand": product.get("brand"), "model": product.get("model"), "upc": product.get("upc"), # price "price": cp.get("price"), # float, e.g. 599 "priceString": cp.get("priceString"), # "$599.00" "wasPrice": (pi.get("wasPrice") or {}).get("priceString"), "savings": (pi.get("savings") or {}).get("savingsString"), # availability "availability": avail.get("value"), # "IN_STOCK" / "OUT_OF_STOCK" "availabilityDisplay": avail.get("display"), # "In stock" # ratings "averageRating": product.get("averageRating"), "numberOfReviews": product.get("numberOfReviews"), # text "shortDescription": product.get("shortDescription"), "longDescription": idml.get("longDescription"), # HTML string # media "thumbnailUrl": img.get("thumbnailUrl"), "allImages": all_images, # up to 10 image URLs # specs "specifications": specs, # {"Brand": "Apple", "Processor": "A18 Pro", ...} "highlights": [ # top highlighted specs with icons {"name": h.get("name"), "value": h.get("value")} for h in (idml.get("productHighlights") or []) ], # URL "canonicalUrl": "https://www.walmart.com" + (product.get("canonicalUrl") or ""), # fulfillment "fulfillmentOptions": product.get("fulfillmentOptions") or [], # reviews (SSR-rendered, first 10) "reviewSummary": { "averageOverallRating": reviews.get("averageOverallRating"), "totalReviewCount": reviews.get("totalReviewCount"), "reviewsWithTextCount": reviews.get("reviewsWithTextCount"), "recommendedPercentage": reviews.get("recommendedPercentage"), }, "customerReviews": customer_reviews, } # Usage url = "https://www.walmart.com/ip/Apple-MacBook-Neo/19717318352" html = fetch_walmart(url) product = extract_product_detail(html) # Example output (confirmed live): # product["name"] → "Apple MacBook Neo 13-inch Apple A18 Pro chip..." # product["price"] → 599 # product["priceString"] → "$599.00" # product["availability"] → "IN_STOCK" # product["model"] → "MHFD4LL/A" # product["upc"] → "195950852745" # len(product["specifications"]) → 29 spec rows # len(product["allImages"]) → 10 # product["specifications"]["Processor"] → "A18 Pro" ``` ### Field notes (confirmed) - **`averageRating` / `numberOfReviews`** on the product node: present for items with reviews. New/few-review items may return `None` for both. - **`reviewSummary.averageOverallRating`** in the reviews node often differs slightly from `product.averageRating` — the reviews node is more precise (e.g. `4.75` vs `4.8`). - **`customerReviews`** (SSR): always the first 10 reviews. The per-review rating field is `"rating"` (int 1–5), **not** `"overallRating"` (which is always `None`). - **`longDescription`**: raw HTML string including `