Blinkit’s product cards expose the data a price tracker needs: selling price, MRP, discount, rating and the units available at the serving dark store. The complication is that the numbers are not global. Three traps can make a seemingly complete export misleading: Cloudflare can block direct requests, prices and inventory change with delivery location, and the category page gives way to a POST-based pagination API.
A Python collector can still turn a category into a CSV. The important design choice is to treat transport, location and pagination as parts of the dataset—not as implementation details to handle after extraction.
Key facts:
- Direct requests to a Blinkit category page can return a Cloudflare HTTP 403 response.
- The reference workflow used Scrape.do with residential proxies in India and completed a successful page fetch in about three seconds across five attempts.
- Prices, stock levels and product selection belong to the dark store serving the delivery location.
- The initial HTML contains 30 product cards, while the pagination endpoint returns 15 products per POST call.
- Blinkit’s server-rendered page includes a preloaded state object that contains the first category results.
Cloudflare changes the transport plan
A normal `requests.get()` call to a Blinkit category URL does not guarantee access. In the reference workflow, even a browser-like User-Agent returned HTTP 403 with a Cloudflare error page.
The reported workaround routed requests through Scrape.do with residential proxies in India, using `super=true` and `geoCode=in`. The category-page request returned status 200 in roughly three seconds after five attempts. Each Blinkit request consumed 10 Scrape.do credits.
That does not establish the proxy service as a permanent requirement. It demonstrates why a scraper needs an explicit transport strategy and failure handling. A 403 is an access-path failure, not a valid response containing product data.
The pagination endpoint deserves separate testing. In the reference setup, a plain request to it failed, while requests through residential proxies worked. The initial page and the next-page endpoint should therefore be treated as distinct transport cases. One may succeed while the other fails.
Location is part of every observation
Blinkit uses the delivery area to select a dark store. Without a delivery address, the reference site used Gurugram. Prices, inventory and even the products shown can vary by store.
The comparison in the facts makes the risk concrete. Gurugram store 31719 had 673 products in “Chips & Crisps,” while Mumbai store 41649 had 524. Only eight products overlapped on the first page of 15. A banana-chips pack had five units in Gurugram and one in Mumbai.
For a price tracker, the implication is straightforward: a scraper that silently switches location can report a price or stock level belonging to another store. Comparing two cities without fixing the serving store can also compare different product mixes rather than equivalent observations.
Store identity, delivery coordinates and scrape time should therefore travel with each record. Without that provenance, a tidy spreadsheet can still be commercially misleading.
On the category page, location came from Blinkit cookies named `gr_1_lat` and `gr_1_lon`. The pagination endpoint read location from `lat` and `lon` headers. A collector must keep both representations synchronized. Cookies for the first request and headers for later requests are not interchangeable details when they are pointing the service at different dark stores.
Parse the initial state, then follow pagination
The category HTML contains a server-rendered state object named `window.grofers.PRELOADED_STATE`. Product cards are available under `ui.plpContainer.feedData.snippets`.
Each relevant card carries an `add_to_cart` payload with the product ID, name, brand, pack size, price, MRP, merchant ID and inventory. The card also indicates whether the product is sold out, while a rating object contains the score and a human-readable count.
Rating counts need normalization. The reference identifies `k` as 1,000 and `lac` as 100,000. A value such as `7 lac` therefore means 700,000. Storing only the displayed string makes later comparisons awkward because the same unit can appear in different formats.
The first HTML page contains 30 product cards and a `next_url` pointing to `/v1/layout/listing_widgets`. That endpoint requires POST and returns 15 products per call. Requesting a larger limit is ignored, so collection must continue until no next URL remains.
For Scrape.do, forwarding the POST body and custom headers required `extraHeaders=true`; forwarded headers used an `sd-` prefix. Cookies could be passed through `setCookies`. These are transport details from the reported implementation, not assumptions about Blinkit’s internal API.
A collector should preserve context
The workflow can be reduced to four decisions: fetch the category HTML with a fixed location, extract and decode the preloaded JSON state, normalize product cards into rows with store and location metadata, then follow every `next_url` until pagination ends and write the CSV.
The JSON decoder’s `raw_decode()` method is useful for parsing the state object. A regular expression can locate the assignment, and `json.JSONDecoder().raw_decode()` can parse the JSON without evaluating the surrounding JavaScript.
A normalized row should contain more than price and name. Useful fields include product ID, brand, pack size, selling price, MRP, inventory, sold-out state, rating, rating count, store ID, latitude, longitude and collection timestamp. The location fields prevent two stores from being silently combined.
Idempotency matters too. Product IDs can appear across pagination attempts, so the collector should deduplicate by a deliberate key such as product ID plus store ID. Keeping raw category pages or normalized responses is useful when debugging, because a schema change can otherwise look like missing inventory.
Decision checklist
Before treating an export as usable, check that:
- The delivery location is fixed and its store ID is recorded.
- The category page and pagination requests resolve to the same store.
- The first-page state and POST responses use the same product-card structure.
- Product IDs are deduplicated within each store context.
- Rating counts are normalized to integers.
- Inventory, price, store and collection time remain together in the output.
- The collector fails loudly when the state object, `next_url` or expected fields disappear.
What to watch next
Blinkit price scraping is workable, but fragile. The site can challenge requests, change state markup and replace the pagination contract without warning. A commercial proxy service can also change pricing or access rules.
Treat the collector as a time-sensitive data pipeline rather than a permanent API client. Verify a known product in two locations, compare page-one and paginated results for store consistency, and test both access paths after changes to the site or transport configuration.
The teams that benefit most are the ones that preserve provenance before analysis. Teams that want a simple national price sheet are the most likely to be misled: the relevant unit is not “Blinkit’s price,” but the price at a particular store, location and moment.
Key takeaways
- Expect direct Blinkit requests to receive Cloudflare responses and plan for controlled transport handling.
- Fix the delivery location before comparing prices or inventory.
- Extract the first 30 cards from server-rendered state, then continue through the POST pagination endpoint.
- Normalize Indian rating abbreviations and deduplicate products by store context.
- Store location, store ID and collection time alongside every price.
Frequently asked questions
Why does a Blinkit scraper return an HTTP 403 response?
Cloudflare can block direct requests even when the client supplies a browser-like User-Agent. The reported workflow used Scrape.do with residential proxies, but access behavior can change.
Why do Blinkit prices differ between runs?
Product selection and inventory are tied to the delivery location’s dark store. A run can target a different store if its coordinates or location cookies differ.
How many products does the Blinkit pagination endpoint return?
The reference implementation reported 15 products per POST call. Larger limits were ignored, so collection continued by following each returned `next_url`.
How should rating counts such as “7 lac” be stored?
Convert them to integer counts before analysis. In the reported data, `lac` represents 100,000, so `7 lac` equals 700,000.
What metadata should accompany each scraped price?
Keep the store ID, delivery coordinates, location and collection time with the price and inventory. Without that provenance, prices from different stores can be misleading.