Walmart Best Sellers Scraper for extracting ranked item titles, prices, ratings, review counts and sellers from Walmart.com deal and category grids. This repo has a free Walmart best-sellers script you can run right now, and a Walmart best sellers API that returns real structured JSON.
Last updated: 2026-07-20. Working against Walmart.com as of July 2026, and re-verified whenever Walmart changes their markup.
The uncut responses behind every block on this page are committed in walmart_best_sellers_api_data/, captured 2026-07-20, and each block states what was trimmed from it. Every code example calls the API and is runnable from walmart_best_sellers_api_codes/.
This repo covers one endpoint in depth. For Walmart product pages, search results, seller data and more, see the Walmart Scraper.
pip install requests
export CHOCODATA_API_KEY="your_key" # free: 1,000 requests, one-time, no card
python walmart_best_sellers_api_codes/bestsellers.py electronicsThose three lines return this, live from Walmart.com (results cut to 1 of 70; the object is complete, all 10 fields verbatim, and the full sample is uncut):
{
"rank": 1,
"id": "5117311260",
"title": "275 Ink Cartridges for Canon ink 275 and 276 Compatible for Canon PG 275 Black Ink Cartridge for printer ink for Pixma TS3522 TS3520 TS3500 TR4722",
"url": "https://www.walmart.com/ip/275XL-Ink-Cartridges-for-Canon-ink-275-and-276-for-Canon-PG-275-CL276-Ink-for-Pixma-TS3522-TS3520-TS3500-TR4722-2-Pack-275-Ink-Cartridges/5117311260?classType=REGULAR&athbdg=L1600",
"price": 32.89,
"currency": "USD",
"rating": 4.3,
"reviews_count": 817,
"thumbnail": "https://i5.walmartimages.com/seo/275XL-Ink-Cartridges-for-Canon-ink-275-and-276-for-Canon-PG-275-CL276-Ink-for-Pixma-TS3522-TS3520-TS3500-TR4722-2-Pack-275-Ink-Cartridges_4868aeef-38d5-4dbe-88f6-4dbf26478fd0.137a0e6c86f764c40464a5e58c05a649.jpeg?odnHeight=180&odnWidth=180&odnBg=FFFFFF",
"seller": "PYMBLE US"
}...multiplied by 70 ranked items, 10 fields each:
That is the whole point of this repo. The rest of this page is the free script, what the free path actually does, and the endpoint with its parameters and response.
- Free Walmart Best Sellers Scraper
- Scraping Walmart best sellers and deal grids
- Walmart Best Sellers Scraper API reference
- Track what is trending in a Walmart category
- Measured latency
Walmart server-renders its deal and category grids into the page's __NEXT_DATA__ JSON blob, so pulling the ranked items needs no headless browser and no JavaScript rendering. The whole job is getting the HTML. Here is the plain-client version, no key and no cost:
python free_scraper/walmart_best_sellers_free_scraper.py "https://www.walmart.com/shop/deals/electronics"Source: free_scraper/walmart_best_sellers_free_scraper.py. It fetches the page, parses __NEXT_DATA__, walks to the first itemStacks array and reads each grid item. It parses first and only calls a page blocked when the grid is genuinely absent, so anti-bot JavaScript shipped on a good page does not false-positive it. It tries a default client and a browser User-Agent, because on this surface the two behave differently:
We ran that script against six Walmart deal pages on 2026-07-20. Every run is recorded in free_scraper_survey.json. What we measured is narrower than a rule about Walmart, so here is exactly what happened:
| What we measured | Value |
|---|---|
Default python-requests client |
HTTP 200, 15,195 bytes, <title>Robot or human?</title>, grid absent |
| Browser User-Agent, valid category pages | parsed the grid on 5 of 5 (electronics, home, toys, baby, pets) |
| A default-client page that did come back | a byte-identical copy of the browser page (a cached response, not an independent fetch) |
| Walmart search surface, either client | refused, the same 15,195-byte page |
So the deal and category grids are not a hard wall the way Walmart search is: a browser-shaped client pulled them on the day we tested. What it hands you, though, is the raw page, and that is where the work lives.
Everything below follows from that.
| What bites you | Why | What it costs you |
|---|---|---|
| The page is inconsistent | The default client is refused on pages Walmart is not already serving from cache, and the search surface refuses everything. Walmart is flaky even through paid infrastructure. | A scheduled job that worked yesterday returns a 15 KB challenge page today, on the same URL. |
| You get a 1.4 MB blob, not rows | The grid lives inside __NEXT_DATA__, roughly 1.4 MB of nested JSON per page. You maintain the walk to itemStacks[].items and the field mapping. |
Ongoing parser maintenance, plus alerting smart enough to tell "empty grid" from "markup changed". |
rank is grid position, not a sales chart |
It is the order items appear on the page, top to bottom. A category browse grid held its order across back-to-back fetches; the /shop/deals homepage grid reordered between two fetches seconds apart. |
Treat rank as "where it sat on the page", and only diff rank over time on the same kind of page. |
| The row count is per page | electronics returned 70 items, /shop/deals 50, toys 52. There is no fixed "top N". |
A parser that assumes a fixed count drops rows on big pages and over-reads on small ones. |
| Price and availability are localised | Walmart localises both to the IP the request exits from. A grid pulled from one country describes prices in that country. | A US price list gathered from a non-US exit is quietly wrong, with a 200 and no error. |
Worth knowing before you go shopping for a free alternative: many popular "free Walmart scraper" repos on GitHub are abandoned or are wrappers that require a paid vendor's API key, so check a repo's own recent output and its dependencies before you clone.
The managed option, and the one this repo is built around. The Chocodata Walmart Best Sellers Scraper API returns Walmart's ranked deal and category grids as parsed JSON, with a ~99% success rate against the bot check, a country parameter for the egress location, and no proxy management. Free for the first 1,000 requests.
Below is the Walmart Best Sellers Scraper API reference to get you started: authentication, the parameters, errors and rate limits, then the endpoint with its response.
curl "https://api.chocodata.com/api/v1/walmart/bestsellers?api_key=YOUR_KEY&category=electronics"import requests
r = requests.get(
"https://api.chocodata.com/api/v1/walmart/bestsellers",
params={"api_key": "YOUR_KEY", "category": "electronics"},
timeout=90,
)
data = r.json()
print(data["results_count"], "ranked items")
print(data["results"][0]["rank"], data["results"][0]["title"])
# 70 ranked items
# 1 275 Ink Cartridges for Canon ink 275 and 276 Compatible for Canon PG 275 ...Get a key at chocodata.com (1,000 requests, one-time, no card).
Pass api_key as a query parameter on every request. That is the whole auth model.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
api_key |
string | yes | - | Your Chocodata API key. |
category |
string | one of category/url |
- | A deal/browse category. A bare token like electronics is routed to https://www.walmart.com/shop/deals/<token>; a path like /cp/electronics/3944 is used as-is. |
url |
string (URL) | one of category/url |
- | A full Walmart browse, deals or category URL you already have. Takes precedence over category. |
country |
string (ISO-2) | no | US | Requests that the fetch exit from a given location. Walmart localises price and availability to it. |
add_html |
boolean | no | false |
Also return the raw upstream HTML alongside the parsed JSON (debugging). |
Each request costs 1 request (5 credits). Responses are billed only on success (2xx).
category and url are two ways to name the same page. category=electronics and url=https://www.walmart.com/shop/deals/electronics return the same grid. Use url when you already have a browse link, including a /cp/... category path.
There is no page or sort parameter. Both were tested and neither is an input this endpoint reads; a ?page= in a url is passed to Walmart untouched, and paging behaviour is Walmart's, not ours.
Nothing below is billed: you are only charged on a 2xx.
| Status | error code |
Meaning | Billed | What to do |
|---|---|---|---|---|
400 |
invalid_params |
Neither category nor url was supplied, or a value is the wrong type. The body lists the exact issue. |
no | Fix the query string. |
401 |
INVALID_API_KEY |
Key missing, unrecognised, or revoked. | no | Check api_key. Get one at chocodata.com. |
402 |
INSUFFICIENT_CREDITS |
Balance exhausted. | no | Top up or upgrade at chocodata.com. |
404 |
item_not_found |
The page does not exist, e.g. a mistyped category. retryable: false. |
no | Fix the category or URL. Retrying will not help. |
429 |
RATE_LIMITED |
Over the rate limit or your plan's concurrency. | no | Back off and retry; see Rate limits. |
502 |
target_unreachable |
Walmart refused every attempt for this request. retryable: true. |
no | Retry after a short pause. This is Walmart being flaky, not a bad input. |
Two response shapes exist: auth and billing errors nest under error.code (uppercase), while param and scrape-layer errors are flat with a lowercase error string.
The scripts in this repo turn each of these errors into an actionable message, so a typo'd key does not hand you a stack trace:
A bad key, verbatim:
curl "https://api.chocodata.com/api/v1/walmart/bestsellers?api_key=totally_invalid_key_123&category=electronics"{"error":{"code":"INVALID_API_KEY","message":"Api key not recognised."}}Calling it with neither category nor url, verbatim:
curl "https://api.chocodata.com/api/v1/walmart/bestsellers?api_key=YOUR_KEY"{"error":"invalid_params","issues":[{"code":"custom","message":"either url or category is required","path":[]}]}Because 502 is retryable and Walmart is flaky, the sensible client retries once after a short pause before treating it as a failure. The use-case script does exactly that.
Two limits apply at once, and the one that binds first is usually the rate limit, not the concurrency cap.
Rate limit: 120 requests per 60 seconds, per key (sliding window). That is a hard ceiling of 2 requests/second sustained, whatever your plan.
Concurrency: how many requests you may have in flight at once, which varies by plan:
| Plan | Concurrent requests |
|---|---|
| Free | 10 |
| Vibe | 30 |
| Pro | 50 |
| Custom | 100 to 500+ |
Exceed either and you get 429, not a queue. Every request is a synchronous GET: there is no webhook, callback, or async job to poll. The examples use timeout=90 because a request that runs into the bot check and is re-attempted takes longer than the median suggests.
Size against the rate limit rather than the concurrency figure. Snapshotting 40 categories is 40 calls, about 20 seconds of rate-limited pulling; polling a single category every 10 minutes is 144 calls a day. Keep your pool at or under your plan's concurrency:
from concurrent.futures import ThreadPoolExecutor
import requests
def one(cat):
r = requests.get("https://api.chocodata.com/api/v1/walmart/bestsellers",
params={"api_key": KEY, "category": cat}, timeout=90)
return cat, r.json().get("results_count", 0) if r.ok else None
cats = ["electronics", "home", "toys", "baby", "pets"]
with ThreadPoolExecutor(max_workers=10) as pool: # <= your plan's concurrency
for cat, n in pool.map(one, cats):
print(cat, n)Walmart's ranked item grid for a deal or category page, one row per item in the order Walmart lays them out. This is the "what is selling and what does it cost" view for a whole category in one call, rather than one product page at a time.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
category |
string | one of category/url |
- | Deal/browse category, e.g. electronics. |
url |
string (URL) | one of category/url |
- | Full Walmart browse/deals/category URL. |
country |
string (ISO-2) | no | US | Egress location; Walmart localises price and availability to it. |
curl "https://api.chocodata.com/api/v1/walmart/bestsellers?api_key=YOUR_KEY&category=electronics"Response (results cut to 2 of 70; each object is complete, all 10 fields verbatim, and the full sample is uncut):
{
"category": "electronics",
"results_count": 70,
"results": [
{
"rank": 1,
"id": "5117311260",
"title": "275 Ink Cartridges for Canon ink 275 and 276 Compatible for Canon PG 275 Black Ink Cartridge for printer ink for Pixma TS3522 TS3520 TS3500 TR4722",
"url": "https://www.walmart.com/ip/275XL-Ink-Cartridges-for-Canon-ink-275-and-276-for-Canon-PG-275-CL276-Ink-for-Pixma-TS3522-TS3520-TS3500-TR4722-2-Pack-275-Ink-Cartridges/5117311260?classType=REGULAR&athbdg=L1600",
"price": 32.89,
"currency": "USD",
"rating": 4.3,
"reviews_count": 817,
"thumbnail": "https://i5.walmartimages.com/seo/275XL-Ink-Cartridges-for-Canon-ink-275-and-276-for-Canon-PG-275-CL276-Ink-for-Pixma-TS3522-TS3520-TS3500-TR4722-2-Pack-275-Ink-Cartridges_4868aeef-38d5-4dbe-88f6-4dbf26478fd0.137a0e6c86f764c40464a5e58c05a649.jpeg?odnHeight=180&odnWidth=180&odnBg=FFFFFF",
"seller": "PYMBLE US"
},
{
"rank": 2,
"id": "443574645",
"title": "XBOX Series X - Gaming Console - 1TB SSD - Includes XBOX Wireless Controller - 4K Gaming - 120FPS - Carbon Black",
"url": "https://www.walmart.com/ip/Xbox-Series-X-Video-Game-Console-Black/443574645?classType=REGULAR&athbdg=L1600",
"price": 742,
"currency": "USD",
"rating": 4.6,
"reviews_count": 18773,
"thumbnail": "https://i5.walmartimages.com/seo/Microsoft-Xbox-Series-X-1TB-Carbon-Black_9f8c06f5-7953-426d-9b68-ab914839cef4.5f15be430800ce4d7c3bb5694d4ab798.jpeg",
"seller": "Pro-Distributing"
}
]
}The field most people come for is rank read together with price and reviews_count: it is Walmart's own ordering of a category, with the current price and the review volume beside each item, which is what you diff over time to see what is moving. id is the stable us_item_id, the join key to the Walmart Scraper product and reviews endpoints. Two things to code against, both visible in the full sample: seller is the marketplace seller and is often a third party rather than Walmart.com, and price can be 0 when Walmart does not expose a price on the grid card (rank 9 in the electronics sample is one), so treat 0 as "no price on the card", not free.
The row count and the meaning of rank are worth seeing across pages:
Full runnable example: walmart_best_sellers_api_codes/bestsellers.py.
The reason most people scrape this: snapshot a category's ranked grid on a schedule and diff the snapshots to see what climbs, what drops out, and how prices move. walmart_best_sellers_api_codes/track_category.py writes one CSV row per item and retries once on a flaky 502:
python walmart_best_sellers_api_codes/track_category.py electronics --out electronics_2026-07-20.csvRun it against the same category daily and diff the files. Because rank is grid position, compare the same kind of page over time (a category browse grid held its order back to back; the /shop/deals homepage reordered between fetches), and pin country so the prices you compare share one currency.
One synchronous GET, wall-clock, including Walmart fetch and parse. Measured from one client on 2026-07-20, n = 8 category pulls. Small sample, and a retry on a flaky 502 lengthens the tail.
| Metric | Value |
|---|---|
| Median | 5.65 s |
| Range | 3.51 s to 9.62 s |
| n | 8 |
The full numbers are in walmart_best_sellers_api_data/latency.json. Size timeouts against the top of the range, not the median.
MIT, see LICENSE. Use it within Walmart's terms of service and applicable law.







