Skip to content

ChocoData-com/walmart-filters-scraper

Repository files navigation

Walmart Filters Scraper

Walmart Filters Scraper

Walmart Filters Scraper for extracting search facets, filter values and sort options from Walmart.com. This repo has a free Walmart filters script you can run right now, and a Walmart filters 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_filters_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_filters_api_codes/.

This repo covers one endpoint in depth. For Walmart product pages, search listings, reviews and bestsellers, see the Walmart Scraper.

pip install requests
export CHOCODATA_API_KEY="your_key"     # free: 1,000 requests, one-time, no card
python walmart_filters_api_codes/filters.py "laptop"

Those three lines return this, live from Walmart.com (filters cut to 1 of 20 and its values cut to 2 of 5, to keep the lead readable; the full sample is uncut):

{
  "query": "laptop",
  "filters": [
    {
      "name": "RAM",
      "type": "ram_memory_general",
      "values": [
        { "name": "8GB", "id": "8GB", "count": null },
        { "name": "12GB & Up", "id": "12GB & Up", "count": null }
      ]
    }
  ],
  "sort_options": [
    { "name": "Best Match", "id": "best_match", "selected": true }
  ]
}

...multiplied out to 20 filters, 226 filter values and 6 sort options:

Retrieved Walmart filters data

That is the whole point of this repo. The rest of this page is the free script, the wall it hits, and the endpoint with its parameters and response.


Contents


Free Walmart Filters Scraper

Walmart server-renders its search pages into a __NEXT_DATA__ JSON blob, and the facet block rides along in it, so when a request gets through you can extract the filters and sort options without a headless browser or JavaScript rendering. No key, no cost:

python free_scraper/walmart_filters_free_scraper.py "laptop"

Source: free_scraper/walmart_filters_free_scraper.py. It fetches /search?q={query}, finds the __NEXT_DATA__ blob, walks to a content module's configs.allSortAndFilterFacets, and emits each filter's name, type and values, plus the sort options.

After running the command, your terminal should look something like this:

Free Walmart filters scraper blocked by the bot check

That is the outcome, and the next section is about why.

Avoid getting blocked when scraping Walmart search filters

We ran exactly that script against Walmart.com while writing this README, 6 times, on 6 different queries. All 6 returned the same thing, and every run is recorded in free_scraper_survey.json:

NO DATA: the response carried no facets node.
A 200 does not mean success. Check for the interstitial before parsing.

Here is the raw evidence behind that message:

What we measured Value
HTTP status 200 (not 403, not 429)
Response size 15,195 bytes (the same page through the API is 1,201,980 bytes)
<title> Robot or human?
__NEXT_DATA__ blob absent
Filters parsed 0 on 6 of 6 queries
Swapping in a browser User-Agent changed nothing: same 200, same 15,195 bytes, same title
walmart.com/ from the same client 200, 256,504 bytes
robots.txt, User-agent: * Disallow: /search

Free scraper blocked vs Chocodata API parsed

A 15 KB page titled "Robot or human?" returned with a 200 status. That single row is the whole problem in miniature, and it is the most important thing to understand about scraping Walmart search filters:

What bites you Why What it costs you
HTTP 200 is not success Walmart serves its bot check with a 200 status, not a 403. A naive scraper parses it, finds nothing, and logs "0 filters" rather than "blocked". The expensive failure is not a crash. It is weeks of empty rows that looked like "this query has no filters".
/search is robots-disallowed anyway Walmart's robots.txt carries Disallow: /search, and the plain client is refused with the bot check on top of that. A browser User-Agent did not change the result. The search path is both disallowed and defended. Reading robots.txt tells you to stay off it; getting the facets is a separate problem.
The facet block is not on every response Walmart does not inline the facet node on every fetch of the same query. When it is absent the API returns a retryable 502 rather than partial data, and a retry lands on a response that carries it. A plain scraper reading one fixed JSON path logs "0 filters" at random. You need a retry, and a way to tell "no filters" from "not served this time".
Per-facet counts are never shipped Every value carries a count field, and on this surface it is null on all of them (0 of 226 populated for laptop, 0 across all 6 queries sampled). If your plan was "read the facet counts to size a category", it is not on this endpoint. The values are there; the tallies are not.
page does not paginate Filters describe the whole result set, not a page of it. Adding page=2 returned the identical 20 filters and 6 sort options; only an internal stores= id inside Walmart's own facet URLs rotated between fetches. Do not build a pagination loop expecting new facets. One call is the complete facet set for the query.

Worth knowing before you go shopping for a free alternative: a plain HTTP client fetching /search is the shape of request that got the interstitial here, and it is the shape most free Walmart scrapers use, so check a repo's own recent output rather than its README. Several of the popular ones that look like they work are wrappers that require a paid vendor's API key, which is worth checking for before you clone.


Using the Chocodata Walmart Filters Scraper API

The managed option, and the one this repo is built around. The Chocodata Walmart Filters Scraper API returns the search facets, filter values and sort options as parsed JSON for Walmart search filter extraction at scale, with a ~99% success rate against the bot check and no proxy management. Free for the first 1,000 requests.


Walmart Filters Scraper API reference

Below is the Walmart Filters Scraper API reference to get you started: authentication, the parameters, errors and rate limits, then the endpoint with its response.

Quickstart

curl "https://api.chocodata.com/api/v1/walmart/filters?api_key=YOUR_KEY&query=laptop"
import requests

r = requests.get(
    "https://api.chocodata.com/api/v1/walmart/filters",
    params={"api_key": "YOUR_KEY", "query": "laptop"},
    timeout=90,
)
d = r.json()
print(len(d["filters"]), "filters,", len(d["sort_options"]), "sort options")
# 20 filters, 6 sort options

After running the command, your terminal should look something like this:

Running the Walmart Filters Scraper API

Get a key at chocodata.com (1,000 requests, one-time, no card).

Authentication

Pass api_key as a query parameter on every request. That is the whole auth model.

Parameters

Param Type Required Default Description
api_key string yes - Your Chocodata API key.
query string one of query/url - A search query, e.g. laptop. /search?q={query} is built from it.
url string (URL) one of query/url - Full Walmart search URL (.../search?q=...). Takes precedence over query.
country string (ISO-2) no us Egress location. Walmart localises its pages to the location it sees, so pin this for reproducible results.
add_html boolean no false Also return the raw upstream HTML alongside the parsed JSON (debugging).

Each request costs 5 credits (= 1 request). Responses are billed only on success (2xx).

There is no page and no sort parameter on this surface: one call returns the complete facet set for the query. Unknown parameters are ignored rather than rejected, so page=2 returns HTTP 200 with the same facets.

query and url are two ways to name the same search and they agree: on 2026-07-20 both forms returned identical filter sets for laptop, field for field.

Errors

Nothing below is billed: you are only charged on a 2xx.

Status error code Meaning Billed What to do
400 invalid_params A required param is missing or the wrong type. Body lists the exact issue and path. 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.
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, or did not inline the facet block on this response. retryable: true. no Retry once after a few seconds. See the note below.

Two response shapes exist: auth and billing errors nest under error.code (uppercase), while scrape-layer errors are flat with a lowercase error string plus retryable.

The scripts in this repo map each status onto an actionable message, so a typo'd key does not hand you a stack trace:

Walmart Filters Scraper API error handling

A bad key, verbatim:

curl "https://api.chocodata.com/api/v1/walmart/filters?api_key=totally_invalid_key_123&query=laptop"
{"error":{"code":"INVALID_API_KEY","message":"Api key not recognised."}}

Calling it with neither query nor url, verbatim:

curl "https://api.chocodata.com/api/v1/walmart/filters?api_key=YOUR_KEY"
{"error":"invalid_params","issues":[{"code":"custom","message":"either url or query is required","path":[]}]}

The 502 target_unreachable on this endpoint covers two cases that look the same to a caller: Walmart refused the fetch, or it returned a response without the facet block inlined (the missing-facet case described above). Both are retryable: true and neither is billed, so the scripts here retry once after an 8-second pause, which is enough to land on a response that carries the facets.

Rate limits and concurrency

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. One call covers one query, so mapping 500 category queries is 500 calls, or roughly 4.2 minutes at 120/minute. Keep your pool at or under your plan's concurrency and pace it to the rate limit:

from concurrent.futures import ThreadPoolExecutor
import requests

def one(query):
    r = requests.get("https://api.chocodata.com/api/v1/walmart/filters",
                     params={"api_key": KEY, "query": query}, timeout=90)
    d = r.json() if r.ok else {}
    return query, len(d.get("filters", [])), len(d.get("sort_options", []))

queries = ["laptop", "coffee maker", "running shoes", "tv"]
with ThreadPoolExecutor(max_workers=10) as pool:   # <= your plan's concurrency
    for query, n_filters, n_sorts in pool.map(one, queries):
        print(query, n_filters, n_sorts)

Filters: search facets, filter values and sort options

Every filter Walmart offers for a search query: the facet name, its machine-readable type, and the values you can slice by, plus the sort_options list. This is the block that drives Walmart's own left-hand filter rail.

Param Type Required Default Description
query string one of query/url - A search query, e.g. laptop.
url string (URL) one of query/url - Full Walmart search URL.
curl "https://api.chocodata.com/api/v1/walmart/filters?api_key=YOUR_KEY&query=laptop"

Real response. filters cut to 2 of the 20 returned and sort_options to 2 of 6; the objects shown are complete, all fields verbatim (full sample):

{
  "query": "laptop",
  "filters": [
    {
      "name": "RAM",
      "type": "ram_memory_general",
      "values": [
        { "name": "3GB & Under", "id": "3GB & Under", "count": null },
        { "name": "4GB", "id": "4GB", "count": null },
        { "name": "8GB", "id": "8GB", "count": null },
        { "name": "12GB & Up", "id": "12GB & Up", "count": null }
      ]
    },
    {
      "name": "Item Conditions",
      "type": "offer_condition_group_codes_filter_v0",
      "values": [
        { "name": "New", "id": "1", "count": null },
        { "name": "Pre-Owned", "id": "2", "count": null },
        { "name": "Restored", "id": "3", "count": null }
      ]
    }
  ],
  "sort_options": [
    { "name": "Best Match", "id": "best_match", "url": "https://www.walmart.com/search?query=laptop&sort=best_match...", "selected": true },
    { "name": "Price: Low to High", "id": "price_low", "url": "https://www.walmart.com/search?query=laptop&sort=price_low...", "selected": false }
  ]
}

The pair to code against is type and id. type is Walmart's machine name for the facet (ram_memory_general, screen_size_laptop, processor_type), which is what you send back to filter a search. id is the value token: on most facets it equals the display name ("8GB"), but on coded facets it is a short code, so Item Conditions returns id: "1" for New. Read name for humans and id for the request you build next.

Two more things worth knowing, both visible in the committed samples:

  • count is null on every value. Walmart does not inline per-facet result tallies on this surface. Across the six queries in filters_survey.json, 0 of every value's count came back populated. The field is in the schema; the number is not on this endpoint.
  • The filter set is category-specific. laptop returns 20 filters, coffee maker 13, tv 14, office chair 16. The 6 sort_options (best_match, price_low, price_high, best_seller, rating_high, new) are constant across every query sampled.

The filter set is category-specific; counts are not shipped

A note on url vs query: they resolve to the same search. When you pass a non-Walmart URL the endpoint fetches it but fails cleanly rather than guessing: url=https://example.com returns 502 (no facets node), not a fake 200. The control run is committed.

Runnable: walmart_filters_api_codes/filters.py


Map a Walmart category's filter taxonomy

The reason to scrape filters before listings is to learn the facet taxonomy a category exposes, so you know what you can slice by before you crawl the products. facet_taxonomy.py pulls that for one or more queries and writes a flat CSV you can drive a search crawl from:

python walmart_filters_api_codes/facet_taxonomy.py "laptop" "office chair"

Enumerating a category's filter taxonomy to CSV

The output has one row per facet value (query, filter, filter_type, value, value_id), so laptop alone is 226 rows across 20 filters. Feed the filter_type and value_id columns back into a Walmart search to fetch just the products under one facet, rather than paging the whole category. One API call per query.


Measured latency

Real end-to-end wall-clock, measured from a laptop against the live API on 2026-07-20. This includes the upstream fetch, the anti-bot handling, and the parse:

Endpoint Median Range n
/walmart/filters 4.1s 3.3 to 13.6s 12

Read the range, not just the median. A request that runs into Walmart's bot check, or lands on a variant without the facet block, is re-attempted upstream until it comes back with real data, which is where the tail comes from, and absorbing that silently is the thing you are actually buying. The free script in this repo hits the same wall and simply stops. Budget a retry rather than a failure for the occasional 502, which is retryable and not billed. Small sample (n=12); reproduce it yourself with the scripts here.


License

MIT. See LICENSE.