Walmart Price Scraper avatar

Walmart Price Scraper

Pricing

from $10.00 / 1,000 items

Go to Apify Store
Walmart Price Scraper

Walmart Price Scraper

Walmart search, category and product prices, including the rollback items whose price the search response leaves empty.

Pricing

from $10.00 / 1,000 items

Rating

0.0

(0)

Developer

SellerKit

SellerKit

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 hours ago

Last modified

Categories

Share

Walmart search, category and product pages, with a price on every row. Including the rollback items, whose price the search response leaves empty.

What the search response gives you, and what this actor gives you

The problem this solves

Open a Walmart search response and look at the price fields:

"price": 0,
"priceInfo": { "itemPrice": "", "linePrice": "", "wasPrice": "", "minPrice": 0 }

All empty. On a laptop search we measured, that was true for 62 items out of 62.

The real number sits in priceInfo.priceDetails.priceLines, and the name of the line changes with the state of the item:

Item stateLine that holds the price
Regular priceCURRENT_PRICE
On rollbackDISCOUNTED_PRICE, with COMPARISON holding the was-price
Several optionsOPTIONS / OPTIONS_RANGE
Subscribe and saveSUBSCRIPTION_DUAL_PRICE

A scraper that reads CURRENT_PRICE gets a number for full-price items and a blank for discounted ones. On that same page 26 of the 62 items were on rollback and had no CURRENT_PRICE line at all. Those 26 are the rows a price tracker exists for.

Walmart's deals pages are different again. /shop/deals has no priceDetails and still uses the old linePrice / wasPrice / savingsAmt strings, on all 50 items of the page.

This actor reads every one of those places, and each row says which one the number came from (priceSource), so you can tell a genuinely missing price from a parser that gave up.

What you get

One row per item with price, was-price, discount, seller and stock

Field
usItemId name url imageidentity
price currencythe number a shopper sees
wasPrice discountPercent onSalethe rollback
optionsFrom optionsTothe range across colours, sizes and configurations
subscriptionPrice unitPricesubscribe and save, and price per unit
priceSourcewhich field the price was read from
inStock availabilitystock
sellerName sellerIdwho actually ships it, which is not always Walmart
rating reviewCountas shown
brand model upcproduct page only
sponsored position page querywhere the row sat, and whether it was an ad
postalCode storeIdthe location Walmart served these prices from
scrapedAt enrichedwhen, and whether the product page was opened

Input

{
"queries": ["gaming laptop"],
"maxItems": 100,
"maxPagesPerQuery": 5,
"sort": "best_match",
"enrichFromProductPage": false,
"onSaleOnly": false
}

You can also pass categoryUrls (any browse or deals page) or productUrls (20305508298, /ip/..., or the full URL). Paste a product URL into the category box and it is still read as a product, and the other way round.

Full URLs keep the parts that decide what you see. selectedSellerId, classType and conditionGroupCode change which offer Walmart shows, so they are preserved; ad tracking parameters are dropped.

onSaleOnly, inStockOnly, skipSponsored and requirePrice filter before anything is written, so the rows you did not want are never saved and never charged.

Brand, model and UPC

The product page adds brand, model and UPC to each row

Set enrichFromProductPage to open each item's own page. It adds brand, model, upc and the seller level stock status, and confirms the price on the product page itself. This is a second event, charged only for the rows where it worked.

Pricing

Charged per row that reaches the dataset, not per minute of compute. A run that finds nothing costs nothing, and a row you filtered out was never saved, so it was never charged. Turn on requirePrice and a row without a price is not saved either.

Notes

  • Walmart's block reads the client, not the address. Measured on one connection at the same minute: curl got a 15 KB page titled "Robot or human?", headless Chromium got the same page, and headed Chromium got the real 1.4 MB page. So this actor drives a real browser with a display, and reuses one session for every page rather than opening a new one each time.
  • Leave the proxy off unless you are bringing residential addresses. On a direct connection this actor returned 30 rows out of 30 with a price. Through datacenter proxy addresses every request was blocked before the first page, because those addresses are already burned for this site.
  • The block arrives as 200 OK, not as an error code. This actor recognises that page, retries on a fresh session, and fails loudly rather than writing an empty dataset.
  • One item that fails to load does not stop the run. It is recorded in RUN_SUMMARY with the reason, and the rest still come back. RUN_SUMMARY also counts how many rows carried a price. Watch that rather than the row count.
  • Sponsored placements repeat across pages and sit at pinned positions. Set skipSponsored when you are comparing prices rather than measuring the search page itself.
  • Prices come from one location, and the run tells you which one. RUN_SUMMARY.location and the storeId on each row record where Walmart served the run from. This matters more than it sounds: two runs a minute apart came back from store 3081 and store 2038 (Ashburn, 20149), because the location follows the address the run went out from. Comparing yesterday's prices with today's is only meaningful when this field matches.
  • Choosing that location is not offered, and it is worth saying why rather than leaving you to guess. Walmart keeps it in the session, not in the URL: ?stores= and ?storeId= are ignored, injecting the store cookie gets the session blocked, and the request that actually sets it (setFulfillmentIntent) answers with 412 and a CAPTCHA payload. Getting past that means defeating the bot check, which this actor will not do.