# Instacart Scraper - Grocery Prices by Retailer (`s-r/instacart-scraper`) Actor

Scrape Instacart storefronts for any retailer: product name, price, unit price per lb or oz, pack size and image. Flags weight-estimated prices separately so loose produce is never read as a fixed price.

- **URL**: https://apify.com/s-r/instacart-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** E-commerce, Business
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 run start fees

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Instacart Scraper

Scrape **Instacart storefronts** for any retailer: product name, price, unit
price per pound or ounce, pack size and image. Give it a retailer slug like
`wegmans`, `costco` or `publix` and it returns the storefront's products.

### Why other Instacart scrapers struggle

Instacart serves an unrendered stub to most clients: about 133 KB with no
products in it. It renders the full storefront for link previews on purpose, so
that a pasted Instacart link shows a product card in a chat app, and this Actor
reads that same rendered page.

Nothing underhand, and no browser or solver needed on your side. Ten
consecutive fetches came back usable ten times out of ten.

### Prices come from the accessibility text

Worth explaining because it is the difference between correct prices and
plausible-looking wrong ones.

The visible price is split across three separate spans — `$`, then `1`, then
`98` — which a scraper has to glue back together, and which produce nonsense the
moment the markup shifts. Every card also carries a screen-reader line:

```
Current price: $1.98 each (estimated)
```

One string, in plain language, and it states the unit as well. This Actor reads
that. It is both more robust and more honest than reassembling spans.

### Estimated prices are marked, never hidden

Loose produce is sold by weight, and Instacart quotes it against an **assumed**
weight. Bananas come back as `$1.78` with `is_estimated: true`, `unit_price:
0.89`, `unit_measure: "lb"` and `size: "About 2.0 lb each"`.

That flag matters. If you are comparing basket prices or tracking inflation,
treating an estimated banana price as a fixed one quietly corrupts the number,
and nothing in the raw page forces you to notice. The run summary counts how
many rows are estimates.

### Fields

| Field | What it is |
|---|---|
| `product_id` | Instacart's own id. Stable, use it to join runs |
| `name` | Product name |
| `price` | Current price |
| `is_estimated` | True when priced against an assumed weight |
| `price_unit` | What the price is per, e.g. `each (estimated)` |
| `unit_price`, `unit_measure` | Price per lb, oz, ct and so on |
| `size` | Pack size or approximate weight as printed |
| `image` | Product image |
| `retailer` | Which retailer's storefront the row came from |
| `url` | Instacart product page |

### Input reference

| Field | Type | Default |
|---|---|---|
| `retailer` | retailer slug | `wegmans` |
| `store_urls` | list of full Instacart URLs, overrides `retailer` | — |
| `limit` | 1-2000 | 100 |
| `retries` | 1-8 | 4 |

Passing several `store_urls` scrapes several retailers in one run, and every row
carries its `retailer`, so a cross-retailer price comparison comes out of a
single run. A non-Instacart URL is rejected with a message rather than fetched.

### Typical uses

- **Grocery price comparison across retailers.** Run two or three storefronts in
  one go and group by `name` or `product_id` per retailer. Mind `is_estimated`
  when comparing fresh produce.
- **Unit-price analysis.** `unit_price` and `unit_measure` are the fields that
  make a 12-pack comparable with a 24-pack.
- **Assortment tracking.** Which products a retailer carries on Instacart, and
  how that changes week to week.
- **Inflation and basket tracking.** Schedule it and join snapshots on
  `product_id`.

### Notes on behaviour

A page that comes back short is the unrendered stub, and it is retried and then
reported as an error rather than returned as an empty store. A storefront that
renders but carries no product cards is reported as `no_products`. Both cases
are made loud on purpose: a successful-looking run with zero rows is the worst
possible outcome for price data.

Pages are paced with a short
randomised gap.

Storefront pages show a slice of a retailer's catalogue, not the entire
inventory. For deeper coverage pass specific aisle or category URLs in
`store_urls` rather than expecting one storefront to return everything.

Prices are US dollars, and they are Instacart's prices, which frequently differ
from the retailer's in-store shelf price.

### Finding a retailer slug

The slug is the part of the Instacart URL after `/store/`. Browse to a retailer
on Instacart and read it off the address bar:
`instacart.com/store/wegmans/storefront` gives `wegmans`. Common ones include
`costco`, `publix`, `kroger`, `safeway`, `aldi`, `sprouts` and `wegmans`.

Availability is regional. Instacart serves different retailers in different
parts of the United States, so a slug that works in one metro may return nothing
useful in another. When that happens the run reports `no_products` for that
storefront rather than returning an empty dataset with no explanation.

### What this Actor does not do

**No search.** Instacart's search results behave differently from storefront
pages and did not render for link-preview agents in testing. Pass aisle or
category URLs instead.

**No delivery fees, tips or service charges.** Those are calculated at checkout
against an address and a basket, and nothing in the storefront page carries
them. The `price` field is the item price as shown on the storefront.

**No stock levels.** Instacart does not publish a count. A product that appears
on the storefront is being offered; whether the shopper finds it on the shelf is
decided at pick time.

# Actor input Schema

## `retailer` (type: `string`):

Retailer slug as it appears in the Instacart URL, for example wegmans, costco, publix or kroger. The quickest way in.

## `store_urls` (type: `array`):

Full Instacart store URLs, if you want specific storefront or aisle pages rather than a retailer's main storefront. Overrides the retailer field.

## `limit` (type: `integer`):

How many products to return across all storefronts.

## `retries` (type: `integer`):

Retries with backoff before a page is reported as an error. Each retry uses a fresh exit IP.

## Actor input object example

```json
{
  "retailer": "wegmans",
  "limit": 100,
  "retries": 4
}
```

# Actor output Schema

## `products` (type: `string`):

One row per product card.

## `summary` (type: `string`):

Counts, price coverage and how many prices are weight estimates.

## `errors` (type: `string`):

Failures with a code and a redacted message.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "retailer": "wegmans",
    "limit": 100,
    "retries": 4
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/instacart-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "retailer": "wegmans",
    "limit": 100,
    "retries": 4,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/instacart-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "retailer": "wegmans",
  "limit": 100,
  "retries": 4
}' |
apify call s-r/instacart-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/instacart-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ShuuY9utgd9ChKZ8G/builds/nVr3AlVYRhmZKLNDe/openapi.json
