Track Nykaa Beauty Brand Pricing for Competitive Brand Recon
Monitor brand-level Nykaa pricing — MRP, discount depth, share-of-shelf, and MAP compliance. Daily snapshots and Slack alerts for growth and ops teams.

Thirdwatch's Nykaa Scraper returns Nykaa SKU-level pricing — price, original_price (MRP), discount_percent, rating_count, brand, and stable SKU IDs. Built for brand-pricing teams enforcing MAP compliance, growth analysts benchmarking competitor discount depth, and indie founders setting their own price ladder against the category.
Why scrape Nykaa for beauty brand pricing
Beauty pricing on Indian online marketplaces is the most fluid in retail. According to Nykaa's FY24 investor disclosures, the company runs three major BPC sale events per year (Pink Friday, Hot Pink Sale, Beauty Bonanza), each driving 3-5x baseline GMV across roughly 6,800 brands. Within those windows, individual SKUs swing 20-50 percent within hours. Between sale events, brand-led MRP revisions and Nykaa-led category promotions create steady but smaller moves. For brand teams at L'Oreal, Unilever, ITC, Hindustan Unilever, and the growing India D2C cohort (Mamaearth, Sugar, Plum, Minimalist, MyGlamm, WOW), Nykaa pricing intelligence is a daily-cadence operational requirement.
The job-to-be-done is structured. A brand pricing manager at MAC enforces MRP and MAP across 4-5 online channels and needs daily evidence of compliance. A growth lead at an indie cosmetics brand benchmarks weekly against five direct competitors. A category buyer at a beauty multi-brand retailer (Tira, Sephora India) tracks competitor pricing on shared SKUs. All reduce to brand-keyword queries with diff logic against yesterday's snapshot.
How does this compare to alternatives?
Three paths to systematic Nykaa brand price tracking:
| Approach | Reliability | Setup time | Maintenance |
|---|---|---|---|
| Manual analyst checking Nykaa URLs daily | Low; doesn't scale beyond ~20 SKUs/day | Continuous analyst time | Per-SKU drift |
| Commercial pricing intel SaaS (Wiser, DataWeave, Price.com) | High but commercial-only | 4-8 weeks contracting | Vendor-managed |
| Thirdwatch Nykaa Scraper | Production-tested with production-grade anti-bot tooling | 5 minutes | Thirdwatch tracks Nykaa changes |
Commercial India-coverage SaaS is reliable but priced for large enterprises. The Nykaa Scraper actor page sits between manual and SaaS — production-grade reliability with transparent per-result pricing.
How to track Nykaa brand pricing in 4 steps
Step 1: How do I set up Apify credentials?
Create a free Apify account, copy your token from Settings → Integrations:
export APIFY_TOKEN="apify_api_xxxxxxxxxxxxxxxx"Step 2: How do I pull my brand watchlist into daily snapshots?
Each brand goes in as a query. Set sortBy to popularity so the top results match what shoppers actually see.
import os, requests, datetime, pathlib, json
ACTOR = "thirdwatch~nykaa-scraper"
TOKEN = os.environ["APIFY_TOKEN"]
WATCHLIST = [
"lakme", "maybelline", "mac cosmetics", "sugar cosmetics",
"minimalist", "mamaearth", "plum goodness", "biotique",
"the ordinary", "huda beauty", "charlotte tilbury", "nykaa cosmetics",
]
resp = requests.post(
f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items",
params={"token": TOKEN},
json={
"queries": WATCHLIST,
"sortBy": "popularity",
"maxResults": 100,
},
timeout=900,
)
records = resp.json()
today = datetime.date.today().isoformat()
pathlib.Path("snapshots").mkdir(exist_ok=True)
pathlib.Path(f"snapshots/nykaa-brands-{today}.json").write_text(json.dumps(records))
print(f"{today}: {len(records)} SKUs across {len(WATCHLIST)} brands")Step 3: How do I compute brand-level pricing metrics?
Median price, discount-depth distribution, and share-of-shelf are the three operational metrics that matter.
import pandas as pd
df = pd.DataFrame(records).dropna(subset=["brand", "price"])
brand_metrics = df.groupby("brand").agg(
sku_count=("sku", "nunique"),
median_price=("price", "median"),
median_mrp=("original_price", "median"),
avg_discount=("discount_percent", "mean"),
deep_discount_skus=("discount_percent", lambda s: (s >= 25).sum()),
avg_rating=("rating", "mean"),
total_reviews=("rating_count", "sum"),
).sort_values("total_reviews", ascending=False)
# Share-of-shelf approximation by review volume
brand_metrics["share_of_voice_%"] = (
100 * brand_metrics.total_reviews / brand_metrics.total_reviews.sum()
).round(2)
print(brand_metrics)The deep_discount_skus column counts SKUs trading at 25+ percent off MRP — the standard threshold beyond which most brands consider it a margin event. Share-of-voice by review volume is a stable proxy for relative consumer engagement, more honest than search-position rank.
Step 4: How do I diff against yesterday and alert on material price moves?
Persist daily, join on sku, alert when price moves more than 5 percent.
import glob, requests as r
frames = []
for f in sorted(glob.glob("snapshots/nykaa-brands-*.json")):
date = pathlib.Path(f).stem.replace("nykaa-brands-", "")
for j in json.loads(pathlib.Path(f).read_text()):
frames.append({
"date": date, "sku": j.get("sku"), "brand": j.get("brand"),
"name": j.get("product_name"), "price": j.get("price"),
"url": j.get("url"),
})
ts = pd.DataFrame(frames).dropna(subset=["sku", "price"])
ts["date"] = pd.to_datetime(ts["date"])
last_two = sorted(ts.date.unique())[-2:]
yest = ts[ts.date == last_two[0]].set_index("sku")
today = ts[ts.date == last_two[1]].copy()
today["yest_price"] = today.sku.map(yest.price)
today["delta_pct"] = (today.price - today.yest_price) / today.yest_price
drops = today[today.delta_pct <= -0.05].sort_values("delta_pct")
for _, row in drops.iterrows():
msg = (f":chart_with_downwards_trend: *{row.brand}* {row['name'][:60]} "
f"INR {int(row.yest_price)} → INR {int(row.price)} "
f"({row.delta_pct*100:+.1f}%) <{row.url}|view>")
r.post("https://hooks.slack.com/services/.../...",
json={"text": msg}, timeout=10)
print(f"{len(drops)} price drops alerted")A 5 percent daily drop is the operational threshold for "investigate by hand". For brand-MAP enforcement, lower the threshold to 2 percent and pin the alert against your distributor's MAP floor.
Step 5: How do I detect MAP breaks specifically?
If you maintain a CSV of MAP floors per SKU, the alert logic becomes a join.
map_floors = pd.read_csv("map_floors.csv") # columns: sku, map_floor_inr
today = today.merge(map_floors, on="sku", how="left")
breaks = today[(today.map_floor_inr.notna()) & (today.price < today.map_floor_inr)]
print(breaks[["brand", "name", "price", "map_floor_inr"]])Catching a MAP break within 24 hours of it appearing is the typical SLA brand teams hold themselves to.
Sample output
A single snapshot row looks like this. Two hundred SKUs at this shape weigh ~100 KB.
[
{
"sku": "8904245700123",
"product_name": "Lakme Absolute Argan Oil Lip Color - Spiced Plum",
"brand": "Lakme",
"price": 600,
"original_price": 800,
"discount_percent": 25.0,
"currency": "INR",
"rating": 4.3,
"rating_count": 4128,
"in_stock": true,
"url": "https://www.nykaa.com/lakme-absolute-argan-oil-lip-color/p/1122334"
},
{
"sku": "0123456789012",
"product_name": "MAC Powder Kiss Liquid Lipcolour - Date-Maker",
"brand": "MAC",
"price": 2200,
"original_price": null,
"discount_percent": 0,
"currency": "INR",
"rating": 4.5,
"rating_count": 988,
"in_stock": true,
"url": "https://www.nykaa.com/mac-powder-kiss-liquid/p/9988776"
}
]original_price populated alongside price flags an active discount; null original_price means the SKU is trading at MRP. discount_percent is pre-computed by the actor so you don't need to derive it. sku is the stable join key for cross-snapshot diff logic.
Common pitfalls
Three things go wrong in production brand-pricing pipelines. Bundle SKU noise — Nykaa lists multi-pack bundles (e.g., "Pack of 3") as separate SKUs with different prices than the single unit; filter bundles out before computing brand-median price using regex on product_name. Sale-window false positives — during named sale events, 40 percent of SKUs show 30+ percent discounts and your alert volume will spike; gate the alert pipeline on a known calendar of Nykaa sale windows. MRP drift — Nykaa's original_price is occasionally stale and lags the actual brand MRP by weeks after a brand re-prices; cross-check against the brand's own DTC site monthly.
A fourth subtle pattern: Nykaa runs SKU-level personalized promotions that appear only when a session is logged in. The actor pulls the public anonymous-shopper price, which is correct for brand-MAP enforcement but misses logged-in offer dynamics.
Thirdwatch's actor handles Nykaa's production-grade anti-bot tooling by reading the page's embedded JSON for the listing payload, with a DOM-walk fallback when the JSON shape shifts. A 12-brand daily snapshot of 100 SKUs each finishes in under three minutes. Pair Nykaa with our Myntra Scraper, Amazon Scraper, and Flipkart Scraper for full Indian online-beauty channel coverage.
Related use cases
Frequently asked questions
How often should I refresh Nykaa price data?
For steady-state brand monitoring, daily at a fixed hour is sufficient — beauty MRPs change quarterly at most. During named sale events (Pink Friday, Hot Pink Sale, Beauty Bonanza, end-of-financial-year), switch to hourly cadence so you catch flash-discount cycles that often last 4-12 hours rather than full days.
Can I track Minimum Advertised Price (MAP) compliance?
Yes. Compare each SKU's price against your distributor's MAP floor. The actor returns price, original_price (MRP), and discount_percent per SKU — pipe these into your compliance workflow and alert when price falls below the brand-set MAP. Stable SKU identifiers make this a one-line join.
What is the smallest practical pull for daily monitoring?
Each query returns up to maxResults popularity-sorted SKUs. For a single brand with ~80 active SKUs on Nykaa, one query at maxResults=100 covers the brand's full Nykaa catalog. A 20-brand watchlist at 100 results each lands ~2K rows per snapshot — small enough to run every hour during sale windows.
How do I separate brand-listed price from third-party seller noise?
Nykaa is primarily a first-party retailer, so seller-versus-seller noise is far smaller than on Amazon India or Flipkart. The Nykaa-displayed price is the price the brand pays Nykaa to advertise. Treat each SKU as one row, with no seller deduplication needed in most cases.
What price moves are signal, and what is noise?
5+ percent daily price drops during non-sale windows are real signal (MAP breaks, clearance, MRP revisions). 10-50 percent drops during named sales (Pink Friday, Hot Pink Sale) are window-bounded promotional noise — flag the window separately. Persistent price drops sustained for 7+ days usually indicate an MRP revision.
Related
100 free credits, no credit card.
About 30 real searches. Add the MCP to Claude or Cursor in two minutes.