Instacart Scraper - Grocery Prices, Products & Stores
Pricing
from $1.50 / 1,000 product or store results
Instacart Scraper - Grocery Prices, Products & Stores
Scrape Instacart grocery catalogs with per-store prices. Run keywords across several stores at once to compare what each banner charges, discover every store serving a US location, or paste Instacart links. Returns name, brand, size, price, regular price, unit price, stock and full store detail.
Pricing
from $1.50 / 1,000 product or store results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Instacart Grocery Catalog & Price Scraper
Scrape the Instacart grocery catalog with the price each store actually quotes.
Instacart is a local marketplace: the same box of cereal costs one thing at Costco and another at Safeway, and which stores exist at all depends on where you are shopping from. This actor is built around that fact.
Why This Scraper?
- Prices from several stores in one run. One keyword is run at every store you name, so the output is a price comparison rather than one store's list.
- Every price carries its store. Banner, service type, store id and the branch's street address ride on each row, because a grocery price without its store is not a fact about anything.
- Real unit prices. Price per ounce, pound or count where the store publishes it, plus a unit price ordering, so different pack sizes are actually comparable.
- Sale detection that means something.
onSaleis set from the regular price the store is striking through, not guessed from a badge. - Store discovery built in. Ask which stores serve an address before you decide what to scrape, with departments, coordinates and postal addresses.
- Change monitoring. Schedule it and get only what moved, with the changed fields named, so a price change is one row rather than a fresh catalog.
- Honest limits. Where the source caps what it serves, the run says so in the log and in the run status instead of quietly returning less.
Data You Get
| Field | Description |
|---|---|
recordId | Stable identity for the row, unique per store and product |
name | Product name as the store lists it |
brand | Brand name |
size | Pack size as the store states it |
price | Current price as a number, for filtering and arithmetic |
priceString | Current price as the store renders it, with currency |
fullPrice | Regular price, present only while the item is discounted |
onSale | Whether the store is currently discounting the item |
unitPrice | Price per unit as a number, where published |
unitPriceString | Price per unit as rendered, for example $0.21/oz |
pricingUnit | The unit the unit price is quoted in |
available | Whether the store currently reports the item in stock |
stockLevel | Machine-readable stock level |
stockLevelLabel | The store's own wording, for example Many in stock |
dietaryAttributes | Organic, gluten free and similar, as published |
tags | Source tags such as storeBrand |
retailerName | The store the price was quoted at |
retailerSlug | Store name as it appears in an Instacart address |
shopId | Numeric storefront id |
serviceType | delivery or pickup |
storeAddress | Street address of the branch quoting the price |
productId | Product id at that store |
image | Product image link |
productUrl | Public product page link |
searchQuery | The keyword that surfaced this row |
Turning on product details adds category, canonicalName, description and
unitCount. Incremental mode adds changeType, changedFields, firstSeenAt
and lastSeenAt. Store mode returns store rows instead, carrying
retailerName, retailerType, serviceType, address, latitude,
longitude, categories, storefrontUrl and optionally departments.
How to Use
Compare one product across two stores in Manhattan -- a postal code is all the location this needs:
{"mode": "search","queries": ["organic whole milk"],"retailers": ["costco", "safeway"],"postalCode": "10001","sortBy": "unitPriceAsc","maxItems": 40}
Find out which stores serve an address first:
{"mode": "store","includeDepartments": true,"postalCode": "94105","maxItems": 50}
Scrape pasted links, with the product category added:
{"mode": "url","urls": ["https://www.instacart.com/store/costco/s?k=greek+yogurt","https://www.instacart.com/store/costco/products/19208138"],"fetchDetails": true,"maxItems": 25}
Monitor sale prices daily and get only the changes:
{"mode": "search","queries": ["coffee"],"retailers": ["safeway"],"onSaleOnly": true,"incrementalMode": true,"stateKey": "coffee-watch","maxItems": 0}
A store name is the part of a store address after /store/: in
instacart.com/store/safeway/storefront the name is safeway.
Your postal code alone is enough. Its coordinates are looked up
automatically -- no separate lookup step, no external geocoding call, just a
bundled US ZIP-code table. latitude/longitude are optional and only worth
setting for a point more precise than a ZIP code's centre, or to shop from
somewhere the postal code doesn't describe. When you do set them, they decide
WHICH stores come back while the postal code still selects the pricing
context those stores quote in, so keep the two in agreement -- the actor
warns if they land more than 50 miles apart.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search, store or url |
queries | array | ["organic whole milk"] | Keywords, search mode |
retailers | array | ["costco","safeway"] | Store names; empty searches every nearby store |
urls | array | see prefill | Links or bare product ids, url mode |
includeDepartments | boolean | false | Add each store's departments, store mode |
postalCode | string | 10001 | US ZIP the prices are quoted for; alone is enough to run |
latitude | number | auto from postalCode | Optional override, a more precise point than the ZIP centre |
longitude | number | auto from postalCode | Set together with latitude; optional, same as above |
sortBy | string | bestMatch | Also priceAsc, priceDesc, unitPriceAsc, unitPriceDesc |
inStockOnly | boolean | false | Keep only available products |
onSaleOnly | boolean | false | Keep only discounted products |
minPrice | integer | unset | Lowest price to keep, USD |
maxPrice | integer | unset | Highest price to keep, USD |
fetchDetails | boolean | false | Add category and description; charged per product |
maxItems | integer | 20 | The run's only cap; 0 is unlimited |
resumeFromRunId | string | unset | Continue one interrupted run |
incrementalMode | boolean | false | Return only what changed since last run |
stateKey | string | unset | Name a monitoring campaign |
emitUnchanged | boolean | false | Also return, and bill, unchanged rows |
emitExpired | boolean | false | Also return, and bill, vanished rows |
proxy | object | US residential | Required; keep the default selection |
mcpConnectors | array | unset | Optional export to your apps |
notionParentPageUrl | string | unset | Notion connector only |
maxNotifyListings | integer | 50 | Export cap per connector, per run |
Output Example
{"recordId": "12|items_74-19208138","itemId": "items_74-19208138","productId": "19208138","name": "Kirkland Signature Whole Milk, 1 Gallon, 2-count","brand": "kirkland signature","size": "each","priceString": "$8.94","price": 8.94,"onSale": false,"available": true,"stockLevel": "highlyInStock","stockLevelLabel": "Many in stock","tags": ["storeBrand"],"retailerName": "Costco","retailerSlug": "costco","retailerId": "5","shopId": "12","storeLocationId": "74","serviceType": "delivery","storeAddress": "123 Main St, San Francisco, CA 94103","image": "https://d2lnr5mha7bycj.cloudfront.net/product-image/file/large_24511800.jpg","productUrl": "https://www.instacart.com/store/costco/products/19208138","category": "Plain Milk","canonicalName": "Kirkland Signature Homogenized Milk, 2 gal","unitCount": "2","searchQuery": "organic whole milk","scrapedAt": "2026-09-13T06:01:44+00:00"}
Send results into your apps (MCP connectors)
Results can be piped into the apps you already use, over Model Context Protocol, without touching the dataset output.
- Authorize a connector under Apify, Settings, API & Integrations.
- Select it in
mcpConnectors. - For Notion, also set
notionParentPageUrlto the page new item pages are created under. Other connectors ignore it. maxNotifyListingscaps how many items are written per connector per run. It does not affect the dataset.
Supported: Notion, Linear, Airtable, Apify. Leave mcpConnectors empty to skip.
An export that fails never fails the run.
Limits, stated plainly
- One page of results per keyword, per store. The source serves a single page
for a keyword and publishes no way to page past it, in practice roughly 30 to
180 products depending on the store. When more exist, the run says so. Narrow
the keyword to reach the rest:
greek yogurtrather thanyogurt. - Department browsing is not available. Department pages are served by a
route the source does not open to this actor, so a pasted
/collections/link is skipped with a message rather than silently returning nothing. Store mode can still list a store's department names and links. - No reviews or ratings. Instacart publishes no customer review surface for products, so no review fields are returned. That is an absence of data, not an omission.
- Nutrition is not promised. The source ships a nutrition panel for only some products, so the detail lookup returns category, description and canonical name rather than a field that would be empty on most rows.
- US only. The source serves US storefronts.
Recurring monitoring, and what counts as a change
The first run returns everything as NEW. Later runs return only NEW,
UPDATED and REAPPEARED, with changedFields naming exactly what moved. A
price change is one row saying price changed.
EXPIRED rows are produced only after a run that fully scanned the tracked
search, so never when the cap stopped it, when Resume was used, or when a keyword
had more results than the source serves at once. A partial run cannot tell "gone"
from "not reached".
The keyword that surfaced a row is not treated as a change, so widening your keyword list does not re-bill your whole catalog as updated.
Plan Requirement
Apify Proxy is required, and the US residential selection is the supported setting. This source checks the connection before it will serve a storefront: measured on 2026-09-13, 1 of 6 standard connections was served against 2 of 2 US residential ones. You can override it, and the run tells you once what that means.
What you are charged for (the Store page shows the current rates):
- Product or store result, charged for each record in the dataset.
- Product detail, charged once per product, only when Fetch product details is on, for the extra request that adds the category, description and canonical name. Off by default.
- Actor start, charged once per run, per GB of memory.
A record suppressed by incremental mode is never charged, including when its detail page was already fetched to work out that nothing changed.