# Australian Supermarket Price Tracker (Coles, Woolworths, ALDI) (`luke-main/au-supermarket-prices`) Actor

Compare grocery prices across Coles, Woolworths and ALDI in one run: price, unit price, specials, was-price, stock, images. Weekly price-drop alerts and change mode return only what moved since your last run.

- **URL**: https://apify.com/luke-main/au-supermarket-prices.md
- **Developed by:** [Luke Main](https://apify.com/luke-main) (community)
- **Categories:** E-commerce, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## Australian Supermarket Price Tracker: Coles, Woolworths & ALDI

Get **current grocery prices from Coles, Woolworths and ALDI in one run**, in one consistent format: price, unit price (per kg / L / 100 g), specials and was-price, stock status, product URL and image. Use it as an **Australian grocery price comparison** API, a **weekly price drop alert**, or a **food inflation / cost-of-living tracker**.

Three ways to run it:

| Mode | Turn on | You get |
|---|---|---|
| **Full price list** | (default) | Every matching product with today's price, unit price and special flag. |
| **Weekly price drops** | `priceDropsOnly` | Only products whose price **fell since your last run**: old price, new price, $ and % drop, unit price, special flag. |
| **Change feed** | `changesOnly` | Every price rise, price drop and (optionally) newly listed product since your last run. |

> Independent tool. It isn't affiliated with, endorsed by or connected to Coles Group, Woolworths Group or ALDI. All trademarks belong to their owners. It reads only public product listings (no logins and no personal data) at a deliberately polite pace.

### Use cases

- **Grocery price comparison (Coles vs Woolworths vs ALDI).** Search `full cream milk 2l` or `tim tam` once and compare the same basket across all three supermarkets by price and unit price. Power a comparison site, app, budgeting tool or Google Sheet.
- **Weekly price drop alerts.** Turn on `priceDropsOnly`, list the products or aisles you care about (or a whole *On Special* page) and schedule it weekly. Each run returns just the products that got cheaper, ready to send to email, Slack, Telegram or Google Sheets with Apify integrations.
- **Food inflation and cost-of-living tracking.** Researchers, journalists and economists can schedule a fixed basket and build a grocery price index from `unitPrice` over time.
- **Brand and competitor monitoring.** FMCG brands and suppliers can track shelf price, promotions and was-price across the major chains and see when a competitor goes on special.
- **Retail analytics.** Track range (new products), specials frequency and pricing over time.

### What it does

- **3 supermarkets, 1 schema.** Every product comes back with the same fields whichever store it's from, so you can compare like-for-like straight away.
- **Search terms and/or category pages.** Type `tim tam` or `nappies`, or paste any aisle/category URL from the store's website (e.g. Coles *On Special*, Woolworths *Milk*, ALDI *Super Savers*).
- **Price drops mode ("only what got cheaper").** Remembers each product's last price and outputs only the ones that fell, with `oldPrice`, `newPrice`, `priceDrop` and `pctDrop`.
- **Change mode ("only what changed since last run").** Last-seen prices are stored in a named key-value store in your own account. Each run outputs only price rises, price drops and, optionally, newly listed products, with `oldPrice` and `pctChange`.
- **Built for reliability.**
  - Uses the same JSON data each retailer's own website loads, not fragile HTML selectors.
  - Every request is retried with exponential backoff and a fresh connection fingerprint when a bot wall appears. Woolworths and ALDI switch to Apify Proxy automatically if walls repeat. If Coles' bot protection blocks a page, the Actor loads it in a real Chrome browser instead (no proxy, no extra charge to you) and keeps using the browser for the rest of the Coles crawl.
  - A search or category that still fails is retried later in the same run after a cool-down, **resuming from the page where it stopped** (no duplicates, no lost pages).
  - Each store and each search runs independently. If one fails you still get everything else. A `RUN_SUMMARY` record lists exactly what succeeded, what was recovered and how many requests needed a retry.
- **Polite by design.** Each store gets one request at a time, with randomised delays (2 s by default). No proxy is needed for Woolworths or ALDI in our tests.

### Weekly price drop alerts

1. Put the products you buy in `searchTerms` (e.g. `coffee beans`, `nappies`, `olive oil`) and/or paste category URLs such as `https://www.coles.com.au/on-special`.
2. Turn on **Price drops only** and give the watch-list its own `stateStoreName` (e.g. `my-weekly-basket`).
3. Run it once. The first run saves a baseline (no rows yet, and the status message says so).
4. Create an **Apify Schedule** (e.g. every Monday 7 am). Each run now returns only the products that got cheaper since the previous run. Use `minPctChange: 10` to see only drops of 10 % or more.
5. Optional: add an integration (email, Slack, Google Sheets, Zapier, Make, webhook) to receive the drops automatically.

```json
{
  "stores": ["coles", "woolworths", "aldi"],
  "searchTerms": ["coffee beans", "olive oil", "nappies", "laundry liquid"],
  "maxItemsPerQuery": 60,
  "priceDropsOnly": true,
  "minPctChange": 10,
  "stateStoreName": "my-weekly-basket"
}
```

Each drop row looks like this (plus all the normal product fields):

```json
{ "store": "coles", "name": "Vegemite Spread", "size": "380g", "oldPrice": 8.7, "newPrice": 7.7,
  "priceDrop": 1.0, "pctDrop": 11.49, "pctChange": -11.49, "unitPrice": 2.03, "unitPriceText": "$2.03/ 100g",
  "isOnSpecial": true, "changeType": "price_down", "lastSeenAt": "2026-11-03T07:00:12+00:00" }
```

Pick the **Price drops** view in the dataset tab for a ready-made table.

### Use cases

- **Price comparison.** Power a comparison site, app or spreadsheet that shows Coles vs Woolworths vs ALDI for the same basket.
- **Brand and competitor monitoring.** FMCG brands and suppliers can track shelf price, promotions and was-price across the major chains, and see when a competitor goes on special.
- **Inflation and cost-of-living tracking.** Researchers, journalists and economists can build a daily grocery price index from unit prices.
- **Deal alerts.** Schedule change mode on a category (e.g. *On Special*), then send the output to email, Slack or Google Sheets through Apify integrations.
- **Retail analytics.** Track range (new products), specials frequency and pricing over time.

### Integrations: send Coles, Woolworths and ALDI prices anywhere

Run it on a schedule, then pipe the results where you work. No code needed.

- **Google Sheets.** Add the Google Sheets integration on the Actor's Integrations tab. Each weekly run appends the new prices or price drops to your sheet, ready for charts or a household budget.
- **Email or Slack alerts.** Schedule `priceDropsOnly` weekly and add the email or Slack integration to get only the products that got cheaper.
- **Zapier and Make.** Use the Apify app in Zapier or Make with the trigger "Actor run finished", then the action "Get dataset items" to push specials into Airtable, Notion, Telegram, a CRM or a shop price-matching sheet.
- **Webhooks.** On the Integrations tab, add a webhook for `ACTOR.RUN.SUCCEEDED` to call your own endpoint, or to start another Actor (for example a report builder) when prices are ready.
- **AI and LLM tools.** Load the dataset into LangChain (`ApifyDatasetLoader`), LlamaIndex or Flowise to let an assistant answer "where is milk cheapest this week?" or plan a weekly shop from the current specials.
- **Claude Code and other agents.** A free skill that compares a grocery list across the three stores with your own Apify token is on Agensi: https://www.agensi.io/skills/au-grocery-price-compare

Tip: change mode (`changesOnly`) and `priceDropsOnly` keep scheduled runs cheap, because you pay mostly for rows that changed.

### Input

| Field | Type | What it does |
|---|---|---|
| `stores` | array | Any of `coles`, `woolworths`, `aldi`. Default: all three. |
| `searchTerms` | array of strings | Each term is searched on every selected store. |
| `categoryUrls` | array of strings | Category/aisle URLs copied from the retailer's site. Each one runs only on its own store. |
| `maxItemsPerQuery` | integer | Max products per search term or category, per store (default 200). |
| `maxItems` | integer | Hard cap on products in the whole run (default 1,000). This also caps your cost. |
| `priceDropsOnly` | boolean | Price drops mode. Outputs only products whose price fell since the previous run (`oldPrice`, `newPrice`, `priceDrop`, `pctDrop`). First run saves a baseline. |
| `changesOnly` | boolean | Change mode. Outputs only products whose price changed since the previous run. |
| `includeNewProducts` | boolean | In change mode, also output products seen for the first time (`changeType: "new"`). Default true. |
| `minPctChange` | integer | In change or price-drops mode, ignore moves smaller than this %. |
| `stateStoreName` | string | Name of the key-value store holding last-seen prices. Use a different name for each watch-list. |
| `requestDelaySecs`, `maxRetries`, `retryPasses` | integer | Politeness and retry tuning. `retryPasses` (default 3) re-runs failed searches/categories later in the run, resuming where they stopped. |
| `includeRawData` | boolean | Adds the retailer's full original record as `raw`. |
| `colesBrowserFallback` | boolean | Default true. When a Coles page is blocked, load it in a real Chrome browser and continue with it. Needs at least 1 GB run memory (the default); with less, the Actor falls back to back-off and retry passes. |
| `proxyConfiguration` | object | Optional Apify Proxy. |

Example: track three products everywhere, plus the Coles specials aisle, daily:

```json
{
  "stores": ["coles", "woolworths", "aldi"],
  "searchTerms": ["tim tam", "full cream milk 2l", "vegemite"],
  "categoryUrls": ["https://www.coles.com.au/on-special"],
  "maxItemsPerQuery": 100,
  "changesOnly": true
}
```

Category URL formats that work:

- Coles: `https://www.coles.com.au/browse/dairy-eggs-fridge/milk`, `https://www.coles.com.au/on-special`
- Woolworths: `https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk`
- ALDI: `https://www.aldi.com.au/products/dairy-eggs-fridge/k/960000000` (the `/k/<number>` part is required)

### Output

One row per product:

```json
{
  "store": "woolworths",
  "productId": "84628",
  "name": "Arnott's Tim Tam Original Chocolate Biscuits Large Pack 365g",
  "brand": "Arnott's",
  "size": "365g",
  "price": 6.4,
  "unitPrice": 1.75,
  "unitPriceText": "$1.75 / 100G",
  "wasPrice": 8.0,
  "isOnSpecial": true,
  "specialText": null,
  "inStock": true,
  "url": "https://www.woolworths.com.au/shop/productdetails/84628/arnott-s-tim-tam-original-chocolate-biscuits-large-pack",
  "imageUrl": "https://cdn1.woolworths.media/content/wowproductimages/large/084628.jpg",
  "barcode": "9310072010816",
  "category": null,
  "source": "search:tim tam",
  "scrapedAt": "2026-11-09T05:41:07+00:00"
}
```

In change mode each row also has:

```json
{ "changeType": "price_down", "oldPrice": 8.0, "pctChange": -20.0 }
```

`changeType` is one of `price_up`, `price_down` or `new`.

Field notes:

- All prices are in AUD, including GST. They're the store's default online/catalogue price, and individual stores can differ.
- `unitPrice` is the retailer's own comparison price. The unit is in `unitPriceText` (per 1 L, per 100 g, per kg, each).
- `specialText` holds multibuy and offer text where the store gives it (e.g. Coles "Buy 2 for $7").
- `barcode` (EAN) is only provided by Woolworths.
- ALDI Australia doesn't sell groceries online, so it publishes no stock levels. `inStock` is `null` for ALDI, except for discontinued items, which are `false`.
- Sponsored/ad tiles are left out. A product found by two of your searches is output once per store per run.

You can download the dataset as JSON, CSV, Excel or HTML, or read it through the Apify API.

### Pricing

Pay per result. There's no subscription, and you only pay for what you get:

| Event | Price |
|---|---|
| Product returned (normal mode) | **US$1.00 per 1,000 products** |
| Product checked (change or price-drops mode) | **US$0.25 per 1,000 products** |
| Change row returned (change or price-drops mode) | **US$0.50 per 1,000 rows** |

Examples:

- A one-off pull of 5,000 products across the three stores costs about **US$5**.
- A daily change-mode watch on 2,000 products costs about **US$0.50 a day**, plus $0.50 per 1,000 changed rows, which is usually just a few cents.
- A weekly price-drop alert on 1,000 products costs about **US$0.25 a week**, plus $0.50 per 1,000 drop rows.

Use `maxItems` to cap any run. Apify's own "max cost per run" setting is respected too: the Actor stops cleanly when it's reached and keeps everything collected so far.

### Reliability notes and limits

- Retailers change their websites from time to time. When a store's format changes, the run still returns data from the other stores, and the failure is logged in `RUN_SUMMARY`. Fixes are prioritised. Please open an Issue if you see one.
- Big crawls take time, because the Actor deliberately stays polite. Stores are crawled in parallel. Woolworths and ALDI run at about 10–15 products/s each and completed every search and category in our tests. Coles runs at about 4–5 products/s. Coles' bot protection blocks plain requests from cloud servers from time to time, so when a Coles page is blocked the Actor switches to a real Chrome browser for the rest of the Coles crawl. In our November 2026 tests this recovered every Coles page (1,000–1,200 Coles products per run, 4–5 minutes, no proxy), where plain requests alone finished only about half of the same crawl. It isn't a guarantee: if Coles blocks the browser too (more likely when you run several big Coles crawls at the same moment), the Actor falls back to back-off and retry passes, and any pages it still can't get are listed in `RUN_SUMMARY`. For big daily Coles crawls, run them one at a time, keep `maxItemsPerQuery` sensible, or set a residential AU proxy.
- Run memory: keep the default 1 GB. The Coles browser fallback needs it; with less memory the Actor still works but skips the browser.
- Prices are the retailer's default/national online prices. Store-specific (postcode) pricing isn't supported yet.
- ALDI may block some data-centre IP ranges. If ALDI fails for you, enable Apify Proxy (residential, country AU) in the *Proxy* field.

### FAQ

**Is this legal?** It reads publicly available product and price information, with no login and no personal data, at a low request rate. You're responsible for how you use the data and for complying with each retailer's terms and Australian law in your use case. This isn't legal advice.

**Can I get historical prices?** Run it on an Apify Schedule (e.g. daily) and each run's dataset becomes a time series. Change mode keeps the feed small and cheap.

**Why did my first price-drops run return nothing?** There was nothing to compare with yet. The first run of each watch-list (`stateStoreName`) saves a baseline; drops appear from the second run. Change mode and price-drops mode can share the same state store.

**How do I compare Coles and Woolworths prices for the same product?** Search the same term (ideally with brand and size, e.g. `vegemite 380g`) on all stores, then compare `price` and `unitPrice`. Check `unitPriceText` first, because stores sometimes use different units (per 100 g vs per kg).

**Something's missing or broken?** Open an Issue on this Actor's page with the input you used. Feature requests (postcode pricing, more stores, nutrition data) are welcome.

# Actor input Schema

## `stores` (type: `array`):

Which supermarkets to check. Search terms run on every selected store; category URLs run only on the store they belong to.

## `searchTerms` (type: `array`):

Product searches, e.g. 'milk', 'tim tam', 'nappies'. Each term is searched on each selected store.

## `categoryUrls` (type: `array`):

Category / aisle pages copied from the browser, e.g. https://www.coles.com.au/browse/dairy-eggs-fridge/milk or https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk or https://www.coles.com.au/on-special.

## `maxItemsPerQuery` (type: `integer`):

Stops paging a search or category after this many products on each store.

## `maxItems` (type: `integer`):

Hard cap on products processed across all stores in this run. Also limits what you pay.

## `priceDropsOnly` (type: `boolean`):

Outputs only products whose price FELL since the previous run of this watch-list: store, product, old price, new price, $ and % drop, unit price and special flag. Uses the same saved-price store as change mode. The first run saves a baseline (no rows); schedule it weekly or daily to get a drop feed. Billed like change mode: per product checked plus per drop row returned.

## `changesOnly` (type: `boolean`):

Remembers each product's price in a named key-value store and outputs only products whose price changed (with oldPrice and pctChange), plus new products if enabled below. Schedule it daily for a cheap price-change feed.

## `includeNewProducts` (type: `boolean`):

In change mode, also output products never seen before (changeType = 'new'). On the very first change-mode run every product is new.

## `minPctChange` (type: `integer`):

In change mode or price-drops mode, ignore price moves smaller than this percentage. 0 = report every change.

## `stateStoreName` (type: `string`):

Named key-value store (in your account) holding last-seen prices. Use different names to keep separate watch-lists (e.g. one for a weekly price-drop alert, one for a daily change feed).

## `requestDelaySecs` (type: `integer`):

Polite pause between requests to the same store (randomised ±30%). Stores are crawled in parallel, one request at a time each.

## `maxRetries` (type: `integer`):

How many times a failed or blocked request is retried (with exponential backoff and a fresh browser fingerprint) before that search/category is skipped. Results collected so far are kept.

## `retryPasses` (type: `integer`):

If a search or category still fails after its per-request retries (e.g. a long bot-wall streak), the Actor cools down and retries it later in the same run, resuming from the page where it stopped. 0 disables this.

## `includeRawData` (type: `boolean`):

Adds a 'raw' field with the retailer's full original product record (bigger output; useful for fields we don't map yet, e.g. multibuy details, aisle locations).

## `autoProxyFallback` (type: `boolean`):

If Woolworths or ALDI shows repeated bot walls and no proxy is set above, retry that store through Apify Proxy (datacenter) automatically. Not used for Coles, whose bot protection walls datacenter IPs; Coles uses the browser fallback above, then back-off and retry passes (or set a residential AU proxy above).

## `colesBrowserFallback` (type: `boolean`):

If a Coles page is blocked by Coles' bot protection, load it in a real Chrome browser (no proxy, no extra cost to you) and keep using the browser for the following Coles pages while it works. Needs at least 1 GB of run memory (the default). Turn off to use plain requests with back-off only.

## `proxyConfiguration` (type: `object`):

Not needed in our tests (Coles, Woolworths and ALDI all worked without a proxy from Apify). Set one if a store keeps blocking you; residential AU is the strongest option.

## Actor input object example

```json
{
  "stores": [
    "coles",
    "woolworths",
    "aldi"
  ],
  "searchTerms": [
    "tim tam"
  ],
  "maxItemsPerQuery": 20,
  "maxItems": 100,
  "priceDropsOnly": false,
  "changesOnly": false,
  "includeNewProducts": true,
  "minPctChange": 0,
  "stateStoreName": "au-supermarket-prices-state",
  "requestDelaySecs": 2,
  "maxRetries": 4,
  "retryPasses": 3,
  "includeRawData": false,
  "autoProxyFallback": true,
  "colesBrowserFallback": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Product rows (or price changes in change mode) from Coles, Woolworths and ALDI.

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

Per-store counts and errors for this run.

## `priceDrops` (type: `string`):

Price-drop rows (old price, new price, $ and % drop) when run with priceDropsOnly.

# 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 = {
    "stores": [
        "coles",
        "woolworths",
        "aldi"
    ],
    "searchTerms": [
        "tim tam"
    ],
    "maxItemsPerQuery": 20,
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("luke-main/au-supermarket-prices").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 = {
    "stores": [
        "coles",
        "woolworths",
        "aldi",
    ],
    "searchTerms": ["tim tam"],
    "maxItemsPerQuery": 20,
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("luke-main/au-supermarket-prices").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 '{
  "stores": [
    "coles",
    "woolworths",
    "aldi"
  ],
  "searchTerms": [
    "tim tam"
  ],
  "maxItemsPerQuery": 20,
  "maxItems": 100
}' |
apify call luke-main/au-supermarket-prices --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,luke-main/au-supermarket-prices"
        }
    }
}
```

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/0wmqfsJHnmeYqJr6y/builds/w87uZmPVkOUn2jBgp/openapi.json
