Instacart Scraper - Grocery Prices, Products & Stores avatar

Instacart Scraper - Grocery Prices, Products & Stores

Pricing

from $1.50 / 1,000 product or store results

Go to Apify Store
Instacart Scraper - Grocery Prices, Products & Stores

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

Abot API

Maintained by Community

Actor 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. onSale is 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

FieldDescription
recordIdStable identity for the row, unique per store and product
nameProduct name as the store lists it
brandBrand name
sizePack size as the store states it
priceCurrent price as a number, for filtering and arithmetic
priceStringCurrent price as the store renders it, with currency
fullPriceRegular price, present only while the item is discounted
onSaleWhether the store is currently discounting the item
unitPricePrice per unit as a number, where published
unitPriceStringPrice per unit as rendered, for example $0.21/oz
pricingUnitThe unit the unit price is quoted in
availableWhether the store currently reports the item in stock
stockLevelMachine-readable stock level
stockLevelLabelThe store's own wording, for example Many in stock
dietaryAttributesOrganic, gluten free and similar, as published
tagsSource tags such as storeBrand
retailerNameThe store the price was quoted at
retailerSlugStore name as it appears in an Instacart address
shopIdNumeric storefront id
serviceTypedelivery or pickup
storeAddressStreet address of the branch quoting the price
productIdProduct id at that store
imageProduct image link
productUrlPublic product page link
searchQueryThe 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

ParameterTypeDefaultDescription
modestringsearchsearch, store or url
queriesarray["organic whole milk"]Keywords, search mode
retailersarray["costco","safeway"]Store names; empty searches every nearby store
urlsarraysee prefillLinks or bare product ids, url mode
includeDepartmentsbooleanfalseAdd each store's departments, store mode
postalCodestring10001US ZIP the prices are quoted for; alone is enough to run
latitudenumberauto from postalCodeOptional override, a more precise point than the ZIP centre
longitudenumberauto from postalCodeSet together with latitude; optional, same as above
sortBystringbestMatchAlso priceAsc, priceDesc, unitPriceAsc, unitPriceDesc
inStockOnlybooleanfalseKeep only available products
onSaleOnlybooleanfalseKeep only discounted products
minPriceintegerunsetLowest price to keep, USD
maxPriceintegerunsetHighest price to keep, USD
fetchDetailsbooleanfalseAdd category and description; charged per product
maxItemsinteger20The run's only cap; 0 is unlimited
resumeFromRunIdstringunsetContinue one interrupted run
incrementalModebooleanfalseReturn only what changed since last run
stateKeystringunsetName a monitoring campaign
emitUnchangedbooleanfalseAlso return, and bill, unchanged rows
emitExpiredbooleanfalseAlso return, and bill, vanished rows
proxyobjectUS residentialRequired; keep the default selection
mcpConnectorsarrayunsetOptional export to your apps
notionParentPageUrlstringunsetNotion connector only
maxNotifyListingsinteger50Export 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.

  1. Authorize a connector under Apify, Settings, API & Integrations.
  2. Select it in mcpConnectors.
  3. For Notion, also set notionParentPageUrl to the page new item pages are created under. Other connectors ignore it.
  4. maxNotifyListings caps 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 yogurt rather than yogurt.
  • 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.