What the StockX scraper collects
Scrapes StockX search results, category/browse listings and single product pages. Listing rows carry live market data — lowest ask, highest bid, last sale, annual average price and sales count — read from the site's embedded Next.js payload; product pages add style ID, colorway, retail price and per-size offers. Output is JSON or CSV with a run-metadata sidecar.
Category listing
Input: A category slug such as sneakersOne row per product with brand, lowest ask, highest bid, last sale, release date, annual average price and sales count, paginated with ?page=N.
Search results
Input: A stockx.com/search URLThe same market-data rows for every product matching a search query.
Single product
Input: A product page URLOne product with style ID, colorway, retail price in USD and per-size offers from the page's JSON-LD.
From zero to StockX data in 3 steps
Install
Clone the repository and install one browser engine. Python 3.9+.
$ git clone https://github.com/2scraper/stockx-scraper.git && cd stockx-scraper $ python3 -m venv .venv && . .venv/bin/activate $ pip install -r requirements-playwright.txt && playwright install chromium $ cp .env.example .env
Run a scraper
Pick what you need and run it. Every flag is documented in the README.
$ python3 playwright_scraper.py --category sneakers --pages 3 --format json --out stockx_sneakers.json$ python3 playwright_scraper.py --url "https://stockx.com/search?s=jordan+4" --pages 2$ python3 playwright_scraper.py --url "https://stockx.com/air-jordan-4-retro-og-flight-club"Get structured data
Rows land in JSON or CSV (
--format), with a.meta.jsonsidecar recording status, pages fetched and why the run stopped.
StockX data fields
21 columns per row in a predictable schema. The most useful ones:
| Field | What it holds |
|---|---|
sku | The product's StockX URL slug |
title | Product name |
brand | Brand, e.g. Jordan |
price | Lowest ask — the price a buyer could act on now |
currency | Currency of the page, localized by exit country |
lowest_ask | Lowest ask |
highest_bid | Highest bid (listing rows) |
last_sale | Last sale price (listing rows) |
annual_avg_price | Average sale price over the year |
annual_sales_count | Number of sales over the year |
release_date | Release date |
style_id | Manufacturer style ID (product pages only) |
colorway | Colorway (product pages) |
retail_price_usd | Retail price, always in USD |
gender | Target gender/demographic |
price_source | embedded_json (listings) or jsonld (product pages) |
[
{
"sku": "air-jordan-4-retro-toro-bravo-2026",
"source": "stockx.com",
"category": "search:jordan 4",
"title": "Jordan 4 Retro Toro Bravo (2026)",
"brand": "Jordan",
"price": 123,
"currency": "EUR",
"price_source": "embedded_json",
"product_url": "https://stockx.com/air-jordan-4-retro-toro-bravo-2026",
"image_url": "https://images.stockx.com/images/Air-Jordan-4-Retro-Toro-Bravo-2026-Product.jpg?fit=fil…",
"scraped_at": "2026-09-10T13:36:18Z",
"style_id": null,
"colorway": null,
"lowest_ask": 123,
"highest_bid": 214,
"last_sale": 147,
"retail_price_usd": null,
"release_date": "2026-05-30",
"annual_avg_price": 158,
"annual_sales_count": 33540,
"gender": "men"
},
{
"sku": "air-jordan-4-retro-rare-air-tour-yellow",
"source": "stockx.com",
"category": "search:jordan 4",
"title": "Jordan 4 Retro Rare Air Tour Yellow",
"brand": "Jordan",
"price": 149,
"currency": "EUR",
"price_source": "embedded_json",
"product_url": "https://stockx.com/air-jordan-4-retro-rare-air-tour-yellow",
"image_url": "https://images.stockx.com/images/air-jordan-4-retro-rare-air-tour-yellow.jpg?fit=fill&b…",
"scraped_at": "2026-09-10T13:36:18Z",
"style_id": null,
"colorway": null,
"lowest_ask": 149,
"highest_bid": 135,
"last_sale": 152,
"retail_price_usd": null,
"release_date": "2026-09-05",
"annual_avg_price": 160,
"annual_sales_count": 2580,
"gender": "men"
}
]First 2 of 6 rows from a real run — sample_output.json. Long values shortened here.
Handles what breaks naive StockX scrapers
Market data from the embedded payload
Listing pages carry no product JSON-LD; the scraper reads the getDiscoveryData entry of StockX's __NEXT_DATA__ cache, which holds asks, bids, last sale and annual stats with no DOM read.
Both listing-node shapes handled
Some listing rows arrive as Variant-typed edges with catalogue fields nested one level down; the parser handles both shapes, verified against a real capture.
No false block from Cloudflare's precursor script
Cloudflare's challenge-platform script is present on fully successful pages too, so it is deliberately excluded from the block markers.
Detected is not paying
A captcha widget on a page whose products are already rendered is never solved — the log on a real Scraping Browser API run shows exactly that decision.
Scraping infrastructure included
The same tested core runs every 2scraper repository, so the hard parts work the same way on every site.
CAPTCHA handling
Detects the challenge a page really serves and, with a 2Captcha key, solves it only when it blocks the data — a capped number of solves per page.
Proxy rotation
Single proxy or a pool from a file, rotated on blocks with a fresh browser per exit. Credentials stay out of argv and logs.
Fingerprints
Optional complete device identity from the 2Captcha Fingerprint API — user agent, client hints, locale and timezone that agree with each other.
Scraping Browser API
Point --cdp-endpoint at a managed Chromium with its own exit country — no browser infrastructure to run yourself.
Honest run metadata
Every run writes a .meta.json sidecar and distinct exit codes: complete, blocked, empty and partial are never confused, and a failed run never overwrites good data.
Diff two runs
diff_runs.py compares two complete StockX snapshots by id — new, removed and changed rows for monitoring.
What teams do with StockX data
Sneaker resale pricing
Track lowest ask, highest bid and last sale across a category to spot spread and momentum.
Release performance
Compare annual average price and sales count against retail price to see which releases trade above retail.
Inventory and arbitrage research
Monitor bids and asks for a watchlist of models and diff runs over time.
Product catalogue enrichment
Pull style IDs, colorways and release dates from product pages to enrich your own catalogue.
Free to run. Pay only for what the site requires.
Free to run · paid add-ons optional
In live captures on 2026-09-10 an ordinary headless Chromium with no proxy, key or account cleared StockX's Cloudflare checks on every page requested. Results vary by path and exit: in later runs, automated sessions also met fast 403s and a Cloudflare block page carrying no captcha, while a category run through the Scraping Browser API came back complete with 41 products. The paid products buy volume from many addresses, a specific exit country (prices localize to it) and a consistent device identity.
Optional 2Captcha services — captcha solving, the Scraping Browser API, residential proxies and fingerprints — work behind one key.
Good to know
- Captcha token injection on a locally launched browser is not implemented; over --cdp-endpoint the Scraping Browser API handles solving itself.
- highest_bid and last_sale are only populated on listing rows.
- Automated sessions have been refused with HTTP 403 on some runs; results vary by path and exit.
2scraper vs. hosted scraper APIs
| 2scraper | Typical hosted API | |
|---|---|---|
| Code | Open source, MIT — read and patch it | Closed |
| Cost | Free; optional services billed only when used | Subscription or per request / per result |
| Runs on | Your machine, server or CI | The vendor's infrastructure |
| Output | JSON/CSV files + run metadata | API responses |
| When the site changes | Fix it yourself or open an issue / PR | Wait for the vendor |
StockX scraper FAQ
Do I need a proxy or a 2Captcha key?
Not necessarily: the September 2026 captures were taken with plain headless Chromium and no key. Results vary by exit and session, so proxies and the Scraping Browser API are there for volume, geography and consistency.
Why is style_id empty on listing rows?
StockX publishes style ID only on product pages. Scrape a product's own URL if you need it.
Why are highest_bid and last_sale empty on product rows?
The product page's server-rendered payload does not carry them; they appear on listing rows, so scrape the category or search listing for those.
What currency are prices in?
Prices are localized to the exit country — a Netherlands exit returned EUR throughout — except retail_price_usd, which StockX always states in USD.
How does pagination work?
StockX uses a real ?page=N parameter, and requesting a page directly server-renders that page's own data.
Is scraping StockX legal?
It reads public pages without an account. Follow StockX's terms and the law where you operate.