Tony Wang6 min readHow to Scrape Kohl's in 2026 (API & Python)
Scrape Kohl's in 2026 — category and campaign product grids with facets, as structured JSON — DIY, no-code, or a structured API.
The fastest way to scrape Kohl's in 2026 is to call a structured API that returns a category or campaign page's normalized product grid, plus facets, as JSON — instead of parsing kohls.com's rendered pages yourself. Kohl's coverage is browsing, not search: there's no free-text search endpoint and no dedicated product-detail-by-id lookup, and the product grid itself isn't paginated — you get page 1 and a set of facets for narrowing further. This guide covers all three approaches, what each returns, where DIY breaks, and the legal basics.
Why scrape Kohl's?
Kohl's category and campaign data powers:
- Assortment research — see what a category or curated campaign carries and how the mix shifts over time.
- Price and markdown tracking — watch how products in a category are priced and discounted across a pull.
- Campaign monitoring — track what Kohl's is merchandising on a curated landing page (a sale event, a brand push) without manually revisiting the site.
- Category-taxonomy mapping — use returned facets to discover adjacent or narrower category and campaign slugs instead of guessing at URLs.
- Competitive benchmarking — compare category-level assortment and pricing against other department-store and big-box retailers.
Is it legal to scrape Kohl's?
Option 1: DIY in Python (and why it breaks)
Kohl's category and campaign pages render from an internal storefront API rather than a stable public JSON endpoint, so a DIY scraper has to fetch and parse the rendered page:
import requests
from bs4 import BeautifulSoup
resp = requests.get(
"https://www.kohls.com/catalog/womens-dresses.jsp",
headers={"User-Agent": "Mozilla/5.0 (compatible; research-bot/1.0)"},
)
soup = BeautifulSoup(resp.text, "html.parser")
# The product grid, prices, and filter facets are hydrated from an internal
# API call, not laid out as stable, selectable markup
It demos and then breaks:
- The grid isn't a static list. Products, prices, and available facets are hydrated client-side from an internal API — a raw HTML fetch often misses the data entirely, and any selector-based parser breaks silently on a redesign.
- There's no search or per-product endpoint to fall back on. Kohl's storefront doesn't expose a documented search API or a stable product-detail-by-id path for outside use, so a DIY approach is stuck reverse-engineering whatever internal calls the category page happens to make.
- The grid only shows page 1 anyway. Even a working scraper of the rendered page gets one screen of products — going further means simulating "load more" interactions against an undocumented, changeable internal call.
- No official developer API. Kohl's has no self-serve public API program for outside developers — there's no sanctioned way to request programmatic access.
- Anti-bot defenses at the edge. Like most large retailers, Kohl's storefront pushes back on naive, high-volume, or unauthenticated automated traffic, so a browser-based scraper needs ongoing upkeep as defenses change.
Option 2: No-code tools
Generic no-code scraper builders can point-and-click their way to a one-off pull of a Kohl's category page, but they inherit the same reverse-engineering fragility as DIY — no stable schema, no documented facets, and nothing that adapts when Kohl's changes how a category page hydrates its grid.
Option 3: A structured Kohl's API
For a repeatable workflow, Crawlora's Kohl's API returns a category or campaign page's product grid and facets as normalized JSON — no page parsing or bot-defense upkeep. Pass a category or campaign slug (from a kohls.com storefront URL):
curl "https://api.crawlora.net/api/v1/kohls/category?slug=womens-dresses" \
-H "x-api-key: $CRAWLORA_API_KEY"
In Python:
import requests
h = {"x-api-key": "YOUR_API_KEY"}
base = "https://api.crawlora.net/api/v1/kohls"
result = requests.get(
f"{base}/category",
headers=h,
params={"slug": "womens-dresses"},
).json()["data"]
products = result["products"]
facets = result["facets"]
A category response is normalized JSON (real fields — check the docs):
{
"code": 200,
"msg": "OK",
"data": {
"slug": "womens-dresses",
"title": "Women's Dresses",
"total": 1842,
"products": [
{
"id": "prod-4187562",
"title": "Nine West Sleeveless Fit & Flare Dress",
"brand": "Nine West",
"price": { "current": 44.99, "regular": 79.00, "sale": true },
"rating": { "average": 4.3, "count": 216 },
"image_url": "https://media.kohlsimg.com/is/image/kohls/4187562",
"url": "https://www.kohls.com/product/prd-4187562/nine-west-womens-sleeveless-fit-flare-dress.jsp"
}
],
"facets": [
{
"display_name": "Category",
"id": "campaign",
"options": [
{ "value": "womens-casual-dresses", "label": "Casual Dresses" },
{ "value": "womens-wedding-guest-dresses", "label": "Wedding Guest" }
]
},
{
"display_name": "Brand",
"id": "brand",
"options": [
{ "value": "nine-west", "label": "Nine West" },
{ "value": "apt-9", "label": "Apt. 9" }
]
}
]
}
}
Because the grid is page 1 only, use facets[].options[].value as the next slug to call instead of trying to page — a facet value like womens-casual-dresses returns its own product grid and its own, further-narrowed facets. Store one row per product per pull, keyed by id, and re-run a slug on a schedule to track price.current and sale flips.
What you can collect
Public category and campaign data from a single endpoint: a category or campaign's product grid (id, title, brand, current/regular price, sale flag, rating, image, url) for page 1, plus facets (category, brand, and similar) with the slug values needed to narrow into an adjacent or more specific category or campaign. No free-text search, no product-detail-by-id lookup, and no pages beyond the first — narrowing happens through facet-driven slugs, not offsets. Public storefront data only.
Limitations and common challenges
- No search endpoint. You need a known category or campaign slug to start — there's no way to query Kohl's catalog by free-text keyword through this API.
- No paginated grid.
/kohls/categoryreturns page 1 only; get broader coverage by using the returned facets to walk into narrower category and campaign slugs, not by requesting further pages. - No dedicated product-detail lookup. Everything you get about a product comes from its row in the category grid — there's no separate call to fetch a single product by id with additional fields.
- Slug discovery still starts on the live site. The first slug for a category or curated campaign has to come from a real kohls.com storefront URL; the API narrows from there via facets, but doesn't enumerate the full taxonomy from nothing.
- Public data only. This is storefront data Kohl's already shows publicly — never a way to reach account, order, or Kohl's Cash/loyalty information behind a login.
Where this gets used
- Assortment and markdown tracking — pull a category on a schedule and diff
productsfor new items, delisted items, andprice.current/salechanges. - Campaign monitoring — watch a curated campaign slug (a sale event or brand push) to see what Kohl's is merchandising and when it changes.
- Taxonomy mapping — walk
facetsrecursively to build an internal map of Kohl's category and campaign slugs without hand-browsing the site.
Sources
Start collecting
Try it first, free: run any public URL through the Free Web Scraper, or check whether a site blocks bots with the Anti-Bot Checker — no signup.
Test the category endpoint in the Playground, check the schema in the API docs, and review pricing. For the same big-box-retail pattern with a full search-to-detail flow, see how to scrape Target; for the closest general-merchandise competitor, see how to scrape Walmart. See also how to choose a web scraping API and is web scraping legal.
Part of our how-to-scrape guide series — every platform we cover, in one index.
Frequently asked questions
Does the Kohl's API support pagination?
No — /kohls/category returns page 1 of the product grid only. Use the returned facets to narrow into a more specific category or campaign slug instead of paging further.
Can I search Kohl's products with this API?
Not directly — Crawlora's Kohl's coverage is category/campaign browsing only (/kohls/category), with no free-text search endpoint yet.
Does Kohl's have an official public API?
No — Kohl's has no self-serve public API program for outside developers, and its storefront category and campaign pages render from an internal product API not published for third-party use. Crawlora reads Kohl's own public storefront rendering and returns it as normalized JSON.