🎉 Premium Proxies · 24-Hour Free TrialClaim Now
Guides

How to Scrape Flipkart Product Data: Prices & Specs

Scrape Flipkart product data: pull prices, specs, ratings, and offers, decode pid and lid, clear the bot wall, and pin pincode pricing with a scraping API.

S SparkProxy 21 23 min read
Share
How to Scrape Flipkart Product Data: Prices & Specs

To scrape Flipkart product data reliably, you fight on two fronts: pulling clean fields out of a page whose CSS class names are minified and rotate, and getting past Flipkart's bot wall and India-only geo gate before that page will even load. Most guides hand you a selector that works for a week, then goes quiet when the class hash changes or your datacenter IP gets flagged. This guide covers the whole pipeline for public product and price data: which fields to pull, how Flipkart's pid and lid identifiers actually work, how to catch a soft block that returns HTTP 200, why prices and offers move by pincode and login, and how to paginate search at volume. Every code sample uses SparkProxy's Scraping API, so the anti-bot and India-IP work is one request parameter instead of an infrastructure project you babysit.

What product data you can extract (fields reference)

A public Flipkart product page exposes a consistent set of fields. The exact DOM location drifts because Flipkart minifies its class names, but the fields themselves are stable. Here is the reference set worth pulling:

FieldWhere it livesHow to get itNotes
TitleProduct heading near the topHeading `span`, or `og:title` metaThe most stable value is the `og:title` meta tag
Selling priceBuy box, large bold numberNearest `₹` amount to the buy boxFlipkart calls it "Special Price"
MRPStruck-through price above selling priceStrikethrough element near priceThe list price before discount
DiscountPercent next to priceText ending in "% off"Derived from MRP and selling price
RatingRating pill next to titleSmall badge with a value like `4.4`Out of 5
Ratings and reviewsUnder the titleText like "1,20,431 Ratings & 8,210 Reviews"Indian digit grouping (lakh format)
Highlights"Highlights" bullet list`
    ` of short bullets
RAM, storage, key specs
SpecificationsSpec tables lower on the pageLabel and value cells in tablesThe richest structured section
Seller"Sold by" lineSeller name near the buy boxTied to the `lid`, not the `pid`
Offers"Available offers" list`
  • ` items under the price
  • Bank, EMI, exchange, coupon offers
    Delivery / availabilityBuy box, below priceDelivery text after you set a pincodeChanges by pincode
    pidURL query string`pid=` parameterThe product identifier, your primary key
    lidURL query string`lid=` parameterThe listing (seller offer) identifier

    The pid is the anchor for everything. Store it as your primary key and every other field hangs off it. The next section explains why pid and lid are two different things, and why that distinction matters more on Flipkart than on any US marketplace.

    Free trial

    Scraping at scale? Skip the blocks.

    Fast, unblockable datacentre proxies with unlimited bandwidth.

    Decode Flipkart URLs: pid, lid, and the item path

    Flipkart product URLs look opaque, but they carry three identifiers, and knowing which is which saves you from silently mixing up products and sellers. A typical URL:

    https://www.flipkart.com/samsung-galaxy-m35-5g/p/itm9d1f7a...?pid=MOBGWFHZ8ZAABCDE&lid=LSTMOBGWFHZ8ZAABCDE7XZKQF&marketplace=FLIPKART
    

    Break it down:

    URL partExampleWhat it identifies
    Slug`samsung-galaxy-m35-5g`Human-readable, SEO only, not an ID
    Item path`/p/itm9d1f7a...`The item page, an opaque hash
    `pid``MOBGWFHZ8ZAABCDE`The **product**: a specific model and variant
    `lid``LSTMOBGWFHZ8ZAABCDE7XZKQF`The **listing**: one seller's offer on that product
    `marketplace``FLIPKART`The marketplace context

    Here is the part almost every Flipkart tutorial gets wrong. The pid is a 16-character code that names a product variant, for example a specific phone in a specific colour and storage size. The lid names a single seller's listing of that product. One pid can carry several lid values because several sellers offer the same product, each at a different price with a different delivery promise. The lid usually embeds the pid (it starts with LST plus the pid), which is a handy sanity check. The leading letters of the pid are a category token: MOB for mobiles, COM for computers, TSH for t-shirts, and so on.

    For price monitoring, decide up front what you are tracking. If you want "the price of this product", key on pid and record which lid won the buy box. If you want "this specific seller's price", key on the (pid, lid) pair. Pulling pid and lid out of a URL is a two-line job:

    import re
    from urllib.parse import urlparse, parse_qs
    
    def flipkart_ids(url: str) -> dict:
        q = parse_qs(urlparse(url).query)
        pid = q.get("pid", [None])[0]
        lid = q.get("lid", [None])[0]
        # item hash from the /p/itm... path segment
        m = re.search(r"/p/(itm[0-9a-z]+)", url)
        return {"pid": pid, "lid": lid, "item": m.group(1) if m else None}
    

    You can rebuild a canonical product URL from a bare pid alone (https://www.flipkart.com/x/p/itm?pid=), which is useful when you have collected pid values from search and want to revisit each product page directly.

    Why Flipkart is hard to scrape

    Flipkart runs a serious anti-bot stack behind a CDN, and it is built for Indian traffic. Four things break naive scrapers:

    The bot wall. Flipkart has long fronted its traffic with Akamai, whose Bot Manager fingerprints the TLS handshake and the browser, then serves an "Access Denied" page or a challenge when a request looks automated. The trap is that a block can arrive as a plain HTTP 200 with an error shell instead of the product, so trusting response.ok stores garbage. If you want the mechanics of that defense, see How to Bypass Akamai Bot Manager.

    The India geo gate. Flipkart serves India. Hit it from a US or EU datacenter IP and you get a degraded experience, wrong or missing pricing, redirects, or an outright block. To see correct INR pricing and real availability, you have to look like an Indian shopper, which means an Indian residential IP.

    Hashed, rotating class names. Flipkart minifies its CSS classes to short hashes like ._30jeq3. Those hashes change on redeploys, so a scraper pinned to one class name goes silently blank on the next Flipkart release. This is worse than Amazon's occasional A/B test, because the churn is routine.

    Pincode and login variance. Delivery, availability, and some offers depend on the 6-digit pincode you set and whether you are logged in. Scrape the same product from different sessions without pinning a location and your data jitters for reasons that have nothing to do with a real price change.

    SignalWhat you will seeHow to handle it
    Bot wall (Akamai)"Access Denied", a reference hash, or 200 with an error shellRotate residential IP, detect in the body, retry
    India geo gateWrong currency, redirect, or block from a non-IN IP`premium_proxy=true` plus `country_code=in`
    Hashed class churnA selector suddenly returns nothingCandidate list plus structural and regex fallbacks
    Pincode variancePrice, stock, or delivery differ run to runPin one pincode with a `js_scenario`
    Rate limiting429s or throttling after burstsBack off, keep concurrency modest

    A managed scraping API absorbs the first two rows and the last one for you. The hashed-class problem lives in the HTML, so it stays in your parser. The next sections handle each in turn.

    Set up the SparkProxy Scraping API

    The SparkProxy Scraping API takes a target URL and handles the proxy, rotation, browser rendering, and anti-bot layer for you. You send one request and get the rendered HTML back. For Flipkart, three parameters carry the weight:

    • render_js=true: Flipkart hydrates parts of the page with JavaScript, and rendering with a real Chromium browser gets the final DOM. It is the safe default.
    • premium_proxy=true: routes the request through residential IPs, which survive Flipkart's defenses where datacenter IPs get flagged fast.
    • country_code=in: sets the exit country to India (in is the ISO 3166-1 code for India). This is not optional for Flipkart. It gives you an Indian IP, INR pricing, and the availability a real shopper in India would see.

    The base endpoint is https://scrape.sparkproxy.io/api/v1 and auth is a single header, X-API-Key. A minimal request in cURL:

    curl -G "https://scrape.sparkproxy.io/api/v1" \
      -H "X-API-Key: YOUR_API_KEY" \
      --data-urlencode "url=https://www.flipkart.com/x/p/itm?pid=MOBGWFHZ8ZAABCDE" \
      --data-urlencode "render_js=true" \
      --data-urlencode "premium_proxy=true" \
      --data-urlencode "country_code=in"
    

    The full parameter list and response fields live in the Scraping API docs. If you are weighing this against running your own India proxy pool, Web Scraping API vs Self-Managed Proxies lays out the trade-off.

    Scrape a single product by pid

    Start with one product. Wrap the request so every call carries the Flipkart-specific parameters, and give it a generous timeout since a rendered request runs a real browser.

    import requests
    
    API = "https://scrape.sparkproxy.io/api/v1"
    API_KEY = "YOUR_API_KEY"
    
    def fetch(url: str) -> str:
        resp = requests.get(
            API,
            headers={"X-API-Key": API_KEY},
            params={
                "url": url,
                "render_js": "true",        # Flipkart hydrates parts of the page
                "premium_proxy": "true",    # residential IPs survive the bot wall
                "country_code": "in",       # Indian IP, INR pricing, real stock
            },
            timeout=90,
        )
        resp.raise_for_status()
        return resp.text
    
    def product_url(pid: str) -> str:
        return f"https://www.flipkart.com/x/p/itm?pid={pid}"
    

    Before you trust the HTML, check whether Flipkart handed you a block instead of a product. Because a block can arrive as HTTP 200, raise_for_status() will not catch it. Scan the body for the telltale markers:

    def is_blocked(html: str) -> bool:
        """A Flipkart or Akamai block can return HTTP 200, so the status code lies."""
        markers = (
            "Access Denied",                 # Akamai block page
            "Reference ",                # Akamai reference id
            "Something's not right",         # Flipkart error shell
            "Retry",                         # Flipkart retry interstitial
            "captcha",
            "unusual traffic",
        )
        lowered = html.lower()
        return any(m.lower() in lowered for m in markers) or len(html) < 2000
    

    The length check matters. When Flipkart returns an empty React shell, the body is short and contains none of the product fields, so a size floor catches the "200 but empty" case that marker matching alone misses.

    Parse the core fields

    Flipkart's hashed class names are the enemy of a durable parser. The fix is a layered strategy: try a short list of known class candidates, and when they miss, fall back to something structural that does not depend on a hash. For parsing at scale use selectolax, a C-backed parser that chews through Flipkart's large DOM roughly an order of magnitude faster than the pure-Python default. Install it with pip install selectolax.

    The single most durable Flipkart selector is not a class at all. The title lives in the og:title meta tag, and the price is always rendered as a rupee amount, so a regex over the buy-box region beats betting on a class hash:

    import re
    from selectolax.parser import HTMLParser
    
    # Known class candidates as of mid-2026. Flipkart rotates these, so treat
    # them as hints, not guarantees, and keep the fallbacks below.
    TITLE_SELECTORS = ["span.VU-ZEz", "span.B_NuCI", "h1 span"]
    PRICE_SELECTORS = ["div.Nx9bqj.CxhGGd", "div._30jeq3._16Jk6d", "div._30jeq3"]
    RATING_SELECTORS = ["div.XQDdHH", "div._3LWZlK"]
    
    PRICE_RE = re.compile(r"₹\s?([\d,]+)")
    
    def _first_text(tree, selectors):
        for sel in selectors:
            node = tree.css_first(sel)
            if node and node.text(strip=True):
                return node.text(strip=True)
        return None
    
    def parse_product(html: str, url: str) -> dict:
        tree = HTMLParser(html)
    
        # Title: prefer the stable og:title meta, then fall back to hashed classes.
        og = tree.css_first('meta[property="og:title"]')
        title = og.attributes.get("content") if og else _first_text(tree, TITLE_SELECTORS)
    
        # Price: try known classes, then regex the first rupee amount as a fallback.
        price = _first_text(tree, PRICE_SELECTORS)
        if not price:
            m = PRICE_RE.search(html)
            price = f"₹{m.group(1)}" if m else None
    
        ids = flipkart_ids(url)
        return {
            "pid": ids["pid"],
            "lid": ids["lid"],
            "title": title,
            "price": price,
            "rating": _first_text(tree, RATING_SELECTORS),
        }
    

    The specifications table is Flipkart's richest structured section, and it is far more stable than the buy-box classes because it is built from plain rows of label and value cells. Walk the tables and collect key-value pairs, which sidesteps the hash problem entirely:

    def parse_specs(html: str) -> dict:
        tree = HTMLParser(html)
        specs = {}
        for row in tree.css("table tr"):
            cells = row.css("td")
            if len(cells) >= 2:
                key = cells[0].text(strip=True)
                val = " ".join(c.text(strip=True) for c in cells[1:]).strip()
                if key and val:
                    specs[key] = val
        return specs        # {"RAM": "8 GB", "Battery Capacity": "6000 mAh", ...}
    

    A few things worth knowing. Flipkart writes counts in the Indian grouping system, so "1,20,431 Ratings" means 120,431. Strip commas before you cast to an integer, and do not assume Western thousands grouping. The rating badge is a bare number like 4.4, not "4.4 out of 5", so store the scale yourself. And the seller name is tied to the lid, not the pid, so record it alongside the listing id.

    Get structured JSON with extract_rules

    Maintaining selectors across Flipkart's class churn is the tax on self-parsing. The SparkProxy Scraping API can do the extraction server-side with the extract_rules parameter: you pass a map of field names to selectors, and the API returns JSON keyed by your names. This turns the scraping endpoint into a lightweight flipkart product data api where the response is already structured.

    import json
    import requests
    
    rules = {
        "title": "meta[property='og:title']@content",  # @attr pulls an attribute
        "price": "div._30jeq3._16Jk6d",
        "rating": "div._3LWZlK",
        "seller": "#sellerName span span",
    }
    
    resp = requests.get(
        "https://scrape.sparkproxy.io/api/v1",
        headers={"X-API-Key": "YOUR_API_KEY"},
        params={
            "url": "https://www.flipkart.com/x/p/itm?pid=MOBGWFHZ8ZAABCDE",
            "render_js": "true",
            "premium_proxy": "true",
            "country_code": "in",
            "extract_rules": json.dumps(rules),
        },
        timeout=90,
    )
    data = resp.json()   # {"title": "...", "price": "₹27,999", "rating": "4.4", ...}
    

    The @content suffix pulls an attribute instead of text, which is how you grab the title out of the og:title meta. The exact extract_rules syntax your plan exposes is in the docs, so confirm it there before you build on it. The trade is the same one from the previous section, just moved server-side: you do not ship a parser, but you still update the rules when Flipkart rotates its classes. For a small, fixed field set like price monitoring, extract_rules is usually the lower-maintenance path, and anchoring rules on meta tags and the spec table keeps them alive longer than buy-box hashes.

    Handle pincode and login price variance

    Here is the gotcha that quietly corrupts Flipkart price datasets: Flipkart localizes delivery, availability, and parts of the offer stack by the shopper's 6-digit pincode, and some prices and early-access deals depend on login and Flipkart Plus status. Scrape the same pid from rotating IPs without pinning a pincode and your "price history" will wobble because the delivery estimate and stock changed, not the price.

    There are two layers to control:

    Country. country_code=in puts you on an Indian IP so you see INR and Indian availability at all. Without it you are not measuring the Indian market, you are measuring whatever Flipkart shows a foreign visitor. For the wider theory on why the exit country changes what you see, see What Geo-Targeting Means in Proxies.

    Pincode, within India. Flipkart stores a delivery pincode and re-computes delivery and stock from it. To pin it, drive the pincode input with a browser automation scenario using the js_scenario parameter, so every request in a batch localizes to the same pincode:

    import json
    import requests
    
    pincode_scenario = {
        "instructions": [
            {"click": "span:contains('Enter Delivery Pincode')"},
            {"wait_for": "#pincodeInputId"},
            {"fill": ["#pincodeInputId", "560001"]},   # Bengaluru
            {"click": "span:contains('Check')"},
            {"wait": 2},
        ]
    }
    
    resp = requests.get(
        "https://scrape.sparkproxy.io/api/v1",
        headers={"X-API-Key": "YOUR_API_KEY"},
        params={
            "url": "https://www.flipkart.com/x/p/itm?pid=MOBGWFHZ8ZAABCDE",
            "render_js": "true",
            "premium_proxy": "true",
            "country_code": "in",
            "js_scenario": json.dumps(pincode_scenario),
        },
        timeout=120,
    )
    

    The pincode input selector is one of Flipkart's drift-prone ones, so confirm the current field in devtools if the scenario stops filling it. Pick one canonical pincode per run, a major metro like 560001 (Bengaluru) or 110001 (New Delhi), and hold it constant, so a price change in your data reflects a real price change and not a delivery-zone change.

    One more distinction that trips people up: the large number in the buy box is the selling price, but the "effective price" Flipkart advertises often bakes in a bank offer or coupon that only applies to specific cards. Record the selling price as your canonical number and capture the offer text separately, so you never conflate a conditional discount with the actual price. If you are building a price-comparison feed across retailers, this discipline is the whole game, and Datacenter Proxies for Price Comparison Websites goes into it.

    Scrape and paginate search results

    Search pages let you discover products by keyword. Rather than fight hashed result-card classes, anchor on structure: every product link on a Flipkart search page points at a /p/ path and carries a pid in its href. Read the anchors and pull the ids straight from the URL.

    from urllib.parse import quote_plus, urljoin
    from selectolax.parser import HTMLParser
    
    def search_url(query: str, page: int) -> str:
        return f"https://www.flipkart.com/search?q={quote_plus(query)}&page={page}"
    
    def parse_search(html: str) -> list[dict]:
        tree = HTMLParser(html)
        seen, rows = set(), []
        for a in tree.css("a[href*='/p/']"):
            href = a.attributes.get("href", "")
            full = urljoin("https://www.flipkart.com", href)
            ids = flipkart_ids(full)
            if not ids["pid"] or ids["pid"] in seen:
                continue
            seen.add(ids["pid"])
            rows.append({"pid": ids["pid"], "lid": ids["lid"], "url": full})
        return rows
    

    To walk every page, request &page=N and stop when a page returns no new products:

    def scrape_search(query: str, max_pages: int = 20) -> list[dict]:
        results = []
        for page in range(1, max_pages + 1):
            html = fetch(search_url(query, page))
            if is_blocked(html):
                continue
            rows = parse_search(html)
            if not rows:
                break
            results.extend(rows)
        return results
    

    Like most marketplaces, Flipkart caps how deep search pagination goes, so a broad query such as "laptop" leaves most of the catalog unreachable past the cap. The fix is to narrow: split by brand, price band, or category filter, run each tight query to its page limit, then dedupe on pid. Ten targeted queries surface far more of the catalog than one broad sweep.

    Scale without getting blocked

    At volume, three things keep the pipeline healthy: retries on soft blocks, backoff so you do not spike the service, and modest concurrency. With a scraping API the provider rotates the exit IP for you, so your ceiling is your plan's rate limit, not the number of Indian proxies you own. Keep worker counts sane, 5 to 12 is plenty, and let retries absorb the occasional block.

    import time
    import random
    from concurrent.futures import ThreadPoolExecutor, as_completed
    
    def fetch_product(pid: str, attempts: int = 3) -> dict | None:
        url = product_url(pid)
        for i in range(attempts):
            html = fetch(url)
            if not is_blocked(html):
                return parse_product(html, url)
            time.sleep(2 ** i + random.random())   # exponential backoff + jitter
        return None
    
    def scrape_catalog(pids: list[str], workers: int = 8) -> list[dict]:
        out = []
        with ThreadPoolExecutor(max_workers=workers) as pool:
            futures = {pool.submit(fetch_product, p): p for p in pids}
            for fut in as_completed(futures):
                row = fut.result()
                if row:
                    out.append(row)
        return out
    

    The backoff matters more than it looks. Jitter (random.random()) staggers retries so a batch of failures does not retry in lockstep and re-trigger the same throttle. Persist results as you go rather than holding everything in memory, so a crash at product 40,000 does not cost you the first 39,999. A flat CSV is enough to start, and stamping each row with the pincode and a timestamp gives you a clean, comparable time series:

    import csv
    from datetime import datetime, timezone
    
    def save_csv(rows: list[dict], pincode: str, path: str = "flipkart.csv") -> None:
        if not rows:
            return
        now = datetime.now(timezone.utc).isoformat()
        for r in rows:
            r["pincode"] = pincode
            r["scraped_at"] = now
        with open(path, "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
            writer.writeheader()
            writer.writerows(rows)
    

    For a running price tracker, write to a database keyed on (pid, scraped_at) and keep the winning lid and pincode on every row. That gives you a time series where every price point is comparable because the location and the seller context were held constant. The same patterns apply across other marketplaces: our How to Scrape Amazon Product Data guide covers the ASIN and ZIP equivalents, and How to Scrape Ecommerce Prices generalizes the monitoring loop across sites. If you keep hitting blocks, How to Avoid Getting Your Proxy Blocked goes deeper on the proxy side.

    Frequently asked questions

    FAQ

    Scraping publicly accessible pages (no login) generally does not trigger the US Computer Fraud and Abuse Act under hiQ v. LinkedIn (2022), but Flipkart is Indian, so Flipkart's Terms of Use and India's DPDP Act, 2023 are the frame that matters. Those Terms prohibit automated collection, so stick to public product data, never touch personal data about shoppers, avoid overloading the service, and get legal advice before any commercial use.

    The pid is a 16-character product identifier that names a specific model and variant, and it is your primary key. The lid is the listing identifier for one seller's offer on that product, and it usually starts with LST plus the pid. One pid can have several lid values when multiple sellers list the same item, so key on pid for the product and on (pid, lid) for a specific seller's price.

    Flipkart fronts its traffic with Akamai Bot Manager, which can return an "Access Denied" page or a challenge, sometimes with HTTP 200, so you must scan the response body rather than trust the status code. Reduce triggers with rotating Indian residential IPs and a real browser: set render_js=true, premium_proxy=true, and country_code=in on the SparkProxy Scraping API, detect block markers in the body, and retry with exponential backoff.

    Flipkart localizes delivery, availability, and parts of the offer stack by the 6-digit delivery pincode, and some deals and early access depend on login and Flipkart Plus status. Pin one pincode with a js_scenario that fills the delivery input, and record the plain selling price separately from conditional bank or coupon offers, so your price series reflects real changes rather than location or offer noise.

    Yes. Pass the extract_rules parameter to the SparkProxy Scraping API with a map of field names to CSS selectors, and the response comes back as JSON keyed by your names, which effectively gives you a flipkart product data api built on the scraping endpoint. Anchor rules on stable targets like the og:title meta tag and the specification table, since Flipkart's buy-box class names rotate.

    In practice, yes. Flipkart serves the Indian market, so a non-India IP can get wrong currency, degraded pricing, redirects, or a block. Setting country_code=in with premium_proxy=true gives you an Indian residential exit so you see INR prices and the availability a real shopper in India would see.

    Special Discount · 20% off

    Get 20% off your first month

    Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.

    Save up to 15% more on quarterly, half-yearly and yearly plans

    Claim Discount

    About the Author

    SparkProxy Technical Team. The SparkProxy engineering team builds and maintains global datacenter and residential proxy infrastructure plus a managed Scraping API. This guide reflects patterns tested against flipkart.com in 2026, using Python 3.11+, requests 2.32+, and selectolax 0.3+. Flipkart minifies and rotates its CSS class names, so the class-based selectors here are current as of mid-2026 and drift often. Anchor on the og:title meta tag, the specification table, and the pid and lid URL parameters, which are far more stable than buy-box hashes.

    Citations: hiQ Labs v. LinkedIn, 9th Cir. 2022 · India DPDP Act, 2023 · SparkProxy Scraping API docs

    Keep reading

    Related articles