Tony Wang5 min readHow to Scrape Ulta Beauty in 2026 (API & Python)
Search Ulta Beauty's product catalog and get full product detail — pricing, ratings, reviews, and every color/shade variant — as structured JSON.
The fastest way to scrape Ulta Beauty in 2026 is to call a structured API that returns normalized JSON for keyword product search and full product detail — pricing, ratings, reviews, images, and every color/shade variant — instead of parsing Ulta.com's pages yourself. Ulta.com is browsable without an account, but its Terms and Conditions explicitly name scraping and automated data collection as prohibited without permission. This guide covers all three approaches, what each returns, where DIY breaks, and the legal basics.
Why scrape Ulta Beauty?
Ulta Beauty's product catalog powers:
- Pricing intelligence — track how a product's price moves over time across brands and categories.
- Assortment research — see which brands and shades a category carries and how coverage shifts.
- Review and sentiment analysis — pull rating and review-count trends for a product line.
- Shade and variant coverage — map every color/shade a product ships in, useful for beauty-industry catalog and inventory research.
- Competitive benchmarking — compare pricing and ratings against other beauty retailers.
Is it legal to scrape Ulta Beauty?
Option 1: DIY in Python (and why it breaks)
Ulta Beauty's product pages render from embedded page data 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.ulta.com/p/product-name-pimprod2020260",
headers={"User-Agent": "Mozilla/5.0 (compatible; research-bot/1.0)"},
)
soup = BeautifulSoup(resp.text, "html.parser")
# Price, rating, and color/shade variants are embedded in page data,
# not consistently exposed as selectable markup
It demos and then breaks:
- The Terms and Conditions are explicit. Ulta Beauty names "scraping technology or fully automated systems" directly and separately prohibits software that gathers information generated by the Services without permission — this isn't an ambiguous case.
- Active bot defenses at the edge. Naive, unauthenticated requests get challenged or blocked, and a browser-based scraper needs ongoing upkeep as the defense updates.
- Every product carries a variant tree, not one price. A single Ulta Beauty product typically ships in several colors or shades, each with its own SKU — a plain page fetch gives you whatever variant renders by default, not the full set.
- No self-serve public API. Ulta Beauty has no official developer program — there's no sanctioned channel to request programmatic access as an outside developer.
Option 2: No-code tools
Marketplace scraper actors for Ulta Beauty exist and suit a one-off pull of a category or a short product list, but they inherit the same bot-defense fragility as DIY and don't give you a stable, versioned schema — or reliable variant coverage — to build a pipeline on.
Option 3: A structured Ulta Beauty API
For a repeatable workflow, Crawlora's Ulta Beauty API returns normalized JSON for search and product detail — no page parsing or bot-defense upkeep, and credential-free. Search by keyword:
curl "https://api.crawlora.net/api/v1/ulta/search?query=mascara" \
-H "x-api-key: $CRAWLORA_API_KEY"
Then resolve a product id and pull full detail, including every color/shade variant, in Python:
import requests
h = {"x-api-key": "YOUR_API_KEY"}
base = "https://api.crawlora.net/api/v1/ulta"
hits = requests.get(f"{base}/search", headers=h, params={"query": "mascara"}).json()["data"]["products"]
product_id = hits[0]["product_id"]
detail = requests.get(f"{base}/product/{product_id}", headers=h).json()["data"]
# Select a specific color/shade variant with an optional sku
variant = requests.get(
f"{base}/product/{product_id}",
headers=h,
params={"sku": detail["variants"][0]["sku"]},
).json()["data"]
A search response is normalized JSON (check the docs for the full schema):
{
"code": 200,
"msg": "OK",
"data": {
"query": "mascara",
"page": 1,
"products": [
{
"product_id": "pimprod2020260",
"title": "Lash Sensational Sky High Mascara",
"brand": "Maybelline",
"price": 12.99,
"rating": 4.4,
"review_count": 5231
}
]
}
}
Product detail nests pricing, rating, and every color/shade variant by SKU:
{
"data": {
"product_id": "pimprod2020260",
"title": "Lash Sensational Sky High Mascara",
"brand": "Maybelline",
"price": 12.99,
"rating": 4.4,
"review_count": 5231,
"images": ["https://media.ulta.com/..."],
"variants": [
{ "sku": "2020261", "shade": "Very Black", "in_stock": true },
{ "sku": "2020262", "shade": "Blackest Black", "in_stock": true }
]
}
}
An unrecognized keyword returns a genuine empty result, and a page beyond the available results returns a normal empty result rather than an error — build pagination logic that stops on an empty products array instead of expecting an error signal. An unrecognized product_id returns a 404. An omitted or invalid sku still resolves the base product using its own default variant, so a detail call never fails just because the SKU wasn't found. Store one row per product_id (or per SKU if you're tracking shade-level stock) and re-run on a schedule to track price and rating changes.
What you can collect
Public product data: paginated search results (product id, title, brand, price, rating, review count); full product detail (pricing, rating, review count, images, and every purchasable color/shade variant, each with its own SKU). Public product-page data only — not account, order, or payment information.
Limitations and common challenges
- Active bot defenses on the front end. Expect challenges on naive or high-volume automated requests to Ulta.com directly.
- The Terms and Conditions are explicit. Ulta Beauty prohibits scraping technology and fully automated data extraction, and separately bars software that gathers information generated by the Services, without explicit permission.
- Variant fan-out. A product's full color/shade lineup lives behind the same detail call, keyed by an optional SKU — decide up front whether you need the base product, a specific shade, or the whole variant tree.
- No nationwide bulk export. There's no official feed of Ulta Beauty's full catalog; build coverage by iterating search queries.
- Public data only. This is what a product page already shows publicly — not a way around a login or into account-level data.
Where this gets used
- Pricing intelligence — track price and rating changes across brands and categories over time.
- Shade and assortment research — map how many color/shade variants a product line carries and which are in stock.
- Review analysis — track rating and review-count trends per product.
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 search and product endpoints in the Playground, check the schema in the API docs, and review pricing. For the same big-box general-merchandise pattern, see how to scrape Target and how to scrape Costco; for a specialty-retail catalog with the same search-to-detail shape, see how to scrape Nike. 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 Ulta Beauty search return empty results for an obscure keyword?
Yes — /ulta/search returns a genuine empty result for an unrecognized or nonsense keyword, and a normal empty result (not an error) when you request a page beyond the available results.
Can I get a specific shade or color variant for an Ulta Beauty product?
Yes — pass an optional sku to /ulta/product/{productId} to select a specific color/shade variant. An omitted or invalid sku still resolves the base product using its own default variant. An unrecognized productId returns 404.
Does Ulta Beauty's Terms of Service prohibit scraping?
Yes, explicitly — Ulta Beauty's Terms and Conditions name "scraping technology or fully automated systems" and separately prohibit software that gathers information generated by the Services, both without explicit permission. Stick to public product-page facts, respect rate limits, and never bypass a login or reach account-level data.