# Coles, Woolworths, Aldi & IGA Prices Scraper (Australia) (`varnmill/au-supermarket-prices`) Actor

Australian supermarket prices from Coles, Woolworths, Aldi and IGA in one table: search, categories or the full catalog, specials (half price, multibuy), watchlists, store prices, barcodes and a changed-only price watcher. No login, no browser.

- **URL**: https://apify.com/varnmill/au-supermarket-prices.md
- **Developed by:** [Jay Novello](https://apify.com/varnmill) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 3 total users, 0 monthly users, 47.6% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 products

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

## Coles, Woolworths, Aldi & IGA Prices Scraper (Australia)

Australian supermarket prices from **Coles, Woolworths, Aldi and IGA in one run and one table**. Search by keyword, crawl a category or the whole catalog, pull every half-price and multibuy special, price-check a watchlist, get store-specific prices, and add barcodes, ingredients and nutrition panels. A built-in **price watcher** outputs only what changed since the last run. No login, no browser, no API key. **$0.50 per 1,000 products**, with no per-run fee and no compute charges on top.

### Real example

Run on Apify on **October 14, 2026 (07:53 UTC)**. Search mode, keyword `full cream milk 2l`, all four retailers, 6 products each, Coles store 357 (North Perth), product details on. It finished in 23 seconds and returned 24 products, every one with a barcode or nutrition data where the retailer publishes it.

| Retailer | # | Product | Size | Price | Unit price | Store | Barcode |
|---|---|---|---|---|---|---|---|
| Coles | 1 | Coles Full Cream Milk | 2L | $3.40 | $1.70 per 1L | Coles North Perth | 9300601186938 |
| Coles | 3 | Masters Full Cream Milk | 2L | $4.30 | $2.15 per 1L | Coles North Perth | 9310232957012 |
| Woolworths | 1 | Woolworths Full Cream Milk | 2L | $3.40 | $1.70 per 1L | national online price | 9300633556143 |
| Woolworths | 6 | Pauls Zymil Lactose Free Full Cream Milk | 2L | $5.95 (was $6.90, price drop) | $2.98 per 1L | national online price | 9310036048985 |
| Aldi | 1 | Full Cream Milk 2L | 2L | $3.39 | $1.70 per 1L | national shelf price | (Aldi publishes none) |
| Aldi | 4 | Lactose Free Full Cream Milk 2L | 2L | $4.79 | $2.40 per 1L | national shelf price | (Aldi publishes none) |
| IGA | 3 | Pauls Full Cream Milk | 1L | $2.50 (was $2.60) | $2.50 per 1L | Ashcroft's IGA Erskine Park | (IGA publishes none) |
| IGA | 4 | A2 Milk Full Cream Milk | 3L | $10.50 | $3.50 per 1L | Ashcroft's IGA Erskine Park | (IGA publishes none) |

House-brand 2L milk came out within a cent everywhere: Aldi $3.39, Coles $3.40 and Woolworths $3.40. Unit prices come in one format for every retailer (`unitPrice` plus `unitOfMeasure` such as `1L`, `100g`, `1kg` or `each`), so you can sort a mixed table directly.

Other runs on the same day:

- **Half-price specials** at Coles, Woolworths and IGA, 200 each: 600 products in 46 seconds. The deepest cuts were a Philips Sonicare toothbrush at Coles ($35, was $70) and an Oral-B Pro Expert at Woolworths ($59.50, was $119).
- **Load test**: the whole *Dairy, Eggs & Fridge* category at Coles (2,265 products) and Woolworths (1,726) in 3 minutes 34 seconds, 103 requests, no blocks.
- **Price watcher**: `milk` and `tim tams` at all four retailers. The first run output 138 products marked `new`; the second run a few minutes later output nothing, skipped 138 unchanged products and charged $0.

One full row from the first run (nutrition cut to 3 of 10 lines):

```json
{
  "type": "product",
  "retailer": "coles",
  "productId": "439693",
  "name": "Coles Full Cream Milk",
  "brand": "Coles",
  "size": "2L",
  "price": 3.4,
  "wasPrice": null,
  "saving": null,
  "isOnSpecial": false,
  "specialType": null,
  "promoText": null,
  "unitPrice": 1.7,
  "unitOfMeasure": "1L",
  "unitPriceText": "$1.70 per 1L",
  "inStock": true,
  "barcode": "9300601186938",
  "categories": ["Dairy, Eggs & Fridge", "Milk", "Full Cream Milk"],
  "imageUrl": "https://cdn.productimages.coles.com.au/productimages/4/439693.jpg",
  "url": "https://www.coles.com.au/product/coles-full-cream-milk-2l-439693",
  "storeId": "357",
  "storeName": "Coles North Perth",
  "priceScope": "store",
  "currency": "AUD",
  "searchQuery": "full cream milk 2l",
  "position": 1,
  "details": {
    "barcode": "9300601186938",
    "description": "Coles Australian Dairy Full Cream Milk, 2L. Fresh full cream milk sourced from Australian farmers for wholesome goodness. Better together with Australian Farmers.",
    "ingredients": "Homogenised and Pasteurised Full Cream Milk",
    "allergens": "Contains Milk",
    "nutrition": [
      {"name": "Energy (kJ)", "per100": "260 kJ", "perServing": "650 kJ"},
      {"name": "Protein", "per100": "3.5 g", "perServing": "8.75 g"},
      {"name": "Sugars - Total", "per100": "4.4 g", "perServing": "11 g"}
    ],
    "servingSize": "250mL",
    "servingsPerPack": "8.00",
    "countryOfOrigin": "Australian Milk"
  },
  "scrapedAt": "2026-10-14T07:53:52Z"
}
```

### What you can do

| Mode | Input | Output |
|---|---|---|
| `search` | Keywords, e.g. `milk`, `tim tams`, `nappies size 4` | Products in each retailer's own ranking (`position`) |
| `category` | Category names, IDs or URLs, or `all` for the full catalog | Every product in the category, de-duplicated |
| `specials` | `all`, `halfPrice`, `multibuy` or `onlineOnly` | Products on special with was price, saving and promo text |
| `products` | Product URLs or IDs (`coles:439693`, `woolworths:888140`, Woolworths barcodes) | A price check of your watchlist, in input order |
| `categories` | Nothing | Each retailer's category tree with IDs and product counts |
| `stores` | A postcode or suburb | Nearby stores with store IDs for store pricing |

Turn on **Only changed prices** and schedule the same input daily to get a feed of price drops, price rises, specials that started or ended, and stock changes. Each row then carries `changeType`, `previousPrice` and `priceChange`. Unchanged products are not output and not charged.

### Retailer coverage

| | Coles | Woolworths | Aldi | IGA |
|---|---|---|---|---|
| Prices | Per store (default: Coles online store 7674, Melbourne) | National online price | National shelf price | Per store (default: 32600, Erskine Park NSW) |
| Full catalog (`all`) | ~31,900 lines | 22,000+ lines (Everyday Market excluded) | ~3,250 lines incl. Special Buys | ~13,800 lines per store |
| Specials | half price, multibuy, online only, price drops | half price, multibuy, online only, lower shelf price | Special Buys (with on-sale date), Super Savers, Lower Prices | half price and % off, with start and end dates |
| Barcode | With product details, or in products mode | Always | Not published | Not published |
| Details | Ingredients, allergens, nutrition, country of origin | Ingredients, allergens, nutrition, health star rating | Description, ingredients, allergens | Ingredients, nutrition, allergens, country of origin |
| Stock | Yes | Yes | Not published | Yes |
| Store finder | Postcode or suburb | Postcode or suburb | Postcode or suburb | Postcode, suburb, state or all 536 stores |

### Input examples

Compare one keyword across all four supermarkets:

```json
{"mode": "search", "queries": ["milk", "bread", "eggs"], "maxItemsPerQuery": 50}
```

Every half-price product at Coles and Woolworths, unlimited:

```json
{"mode": "specials", "specialsType": "halfPrice", "retailers": ["coles", "woolworths"], "maxItemsPerQuery": 0}
```

Daily price-change feed for a full category at one Coles store:

```json
{"mode": "category", "retailers": ["coles"], "categories": ["Dairy, Eggs & Fridge"], "colesStoreId": "357",
 "maxItemsPerQuery": 0, "onlyChanged": true, "watcherName": "coles-357-dairy"}
```

Watchlist with barcodes and nutrition:

```json
{"mode": "products", "includeDetails": true,
 "products": ["https://www.coles.com.au/product/coles-full-cream-milk-2l-439693", "woolworths:888140", "iga:726347"]}
```

### Pricing

Pay per event. Platform usage (compute, proxy, storage) is included.

| Event | Price | When |
|---|---|---|
| Product | **$0.50 per 1,000** | Each product row output |
| Product details add-on | $1.00 per 1,000 | Only with *Include product details*, and only when details were found |
| Store or category | $0.50 per 1,000 | Each row in stores or categories mode |
| Actor start | $0.00005 | Once per run |

Examples: 1,000 products from a search or specials list cost $0.50. The whole Coles catalog (about 31,900 products) costs about $16. A daily watcher over 2,000 products where 150 change costs $0.08 a day. Error rows and blocked requests are never charged. Other Coles and Woolworths actors charge $1.00 to $6.00 per 1,000 results on the free plan, several of them plus a per-run fee.

### Accuracy and limits

- **Store prices.** Coles and IGA prices depend on the store. Every row says which store the price is for (`storeId`, `storeName`, `priceScope`). Woolworths and Aldi show one national online or shelf price to anonymous visitors, so their rows say `priceScope: "national"`. Woolworths store-level prices need a login and are not collected.
- **Small Coles stores.** Coles Central and Coles Local Express stores have no online range; choosing one returns an uncharged error row. Pick a full-size supermarket from stores mode.
- **Aldi** sells in store only, so its rows have no stock flag, and Aldi publishes no barcodes. Aldi has no half-price, multibuy or online-only specials; those types return an uncharged error row for Aldi.
- **Coles "other" specials.** Coles lists some products as specials without a previous price; they come out with `specialType: "other"` and no `wasPrice`.
- **Sponsored products** appear in Coles and Woolworths results as they do on the website; each product appears once per list.
- **Woolworths Everyday Market** (over a million third-party listings) is left out unless you turn on *Include Woolworths Everyday Market*.
- **Proxy.** Coles blocks most direct connections from cloud servers, so keep the default Apify proxy on. When a site refuses repeatedly, the run stops that list with an error row instead of hanging, and keeps what was already delivered.
- Prices change during the day and specials usually change on Wednesdays. `scrapedAt` is the time each row was read.

### FAQ

**Do I need an account at the supermarkets?** No. The actor reads the same public prices any visitor sees, without a login.

**How do I get prices for my local store?** Run mode `stores` with your postcode, copy the Coles or IGA `storeId`, and put it in *Coles store ID* or *IGA store ID*.

**How do I match the same product across retailers?** Use `barcode`: Woolworths always has it and Coles has it with product details or in products mode. Otherwise match on `brand`, `name` and `size`.

**Can I get a price history?** Schedule the actor with *Only changed prices* on. Each run outputs only the changes, so the datasets together form the history. Use a different *Watcher name* per schedule.

**Can I call it from code or an AI agent?** Yes. For example:

```bash
curl -X POST "https://api.apify.com/v2/acts/varnmill~au-supermarket-prices/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode": "search", "queries": ["coffee beans"], "maxItemsPerQuery": 20}'
```

**What if a search finds nothing or a product id is unknown?** The run still succeeds and the dataset gets an uncharged `error` row that explains why, so schedules and integrations keep running. A run fails only when a supermarket blocks or errors and nothing comes back.

**Do I have to set `mode`?** Not if you fill only one kind of input. `products`, `categories` or `location` on their own select that mode, and product URLs typed into `queries` are price-checked as products. Common field names from other scrapers also work: `query`, `keyword(s)`, `searchTerms`, `productUrls`, `startUrls`, `category`, `postcode`.

**Something is wrong or missing?** Open an issue on the actor page. Issues are checked daily.

Watcher scheduling: use a separate `watcherName` for each independent workflow and keep its filters/location consistent. Runs using the same name wait up to 50 seconds for the previous run; if it is still busy, retry after it finishes. Local and cloud baselines are separate. A failed or interrupted delivery can appear again on retry, so deduplicate downstream by product or creative ID and observation time. Existing names keep their baselines, but a baseline previously shared by colliding names cannot be disentangled automatically.

### Changelog

- **0.1.7 (2026-10-19)**: Watcher runs with the same name now wait for each other before loading and saving their baseline. Losing the lock stops the run without saving stale state. Cancelled runs keep only confirmed output in watcher state.

- **0.1.6 (2026-10-19)**: Spending-limit fix. When your maximum charge per run runs out, a product and its details add-on are charged together or not at all, and only delivered products are counted and remembered by the watcher, so a product cut off by the limit shows up again on the next run. Watcher names with spaces or other special characters now get their own record (existing watchers carry over); state is merged before saving. Full overlap serialization was added in 0.1.7.

- **0.1.5 (2026-10-17)**: Runs no longer fail when a search finds nothing or a product id is unknown (uncharged error rows instead). The mode is now picked from the filled fields, and common field names from other scrapers (`query`, `productUrls`, `startUrls`, `postcode` and more) are accepted. An unusable proxy group now falls back to the datacenter proxy.

- **0.1 (2026-10-14)**: First release. Coles, Woolworths, Aldi and IGA; search, category and full catalog, specials, products, categories and stores modes; store pricing for Coles and IGA; product details add-on; changed-only price watcher.

# Actor input Schema

## `mode` (type: `string`):

What to collect. Every mode returns the same product fields for every retailer, so you can compare Coles, Woolworths, Aldi and IGA side by side.

## `retailers` (type: `array`):

Supermarkets to collect from. Empty means all four. Each retailer runs in parallel.

## `queries` (type: `array`):

What you would type into the supermarket's search box, e.g. 'milk', 'tim tams', 'nappies size 4'. Each keyword is searched at every selected retailer.

## `categories` (type: `array`):

Category names, IDs or category page URLs, e.g. 'Dairy, Eggs & Fridge' or a coles.com.au/browse/... link. Use 'all' for the whole grocery catalog of each retailer (about 30k Coles, 22k Woolworths, 3k Aldi and 14k IGA lines). Run mode 'categories' first to see the exact names.

## `specialsType` (type: `string`):

Which specials to collect. Aldi only has 'all' (Special Buys and price drops); a type a retailer does not have returns an uncharged error row for that retailer.

## `products` (type: `array`):

Product page URLs from any of the four sites, or IDs prefixed with the retailer, e.g. 'coles:5013085' or 'woolworths:888140'. Bare IDs work when exactly one retailer is selected. Use the 'productId' field from an earlier run.

## `location` (type: `string`):

Find stores near this postcode or suburb, e.g. '2000' or 'Fitzroy'. Use the returned 'storeId' as the Coles or IGA store below to get that store's prices.

## `colesStoreId` (type: `string`):

Optional. Coles prices and ranges differ by store. Leave empty for Coles's default online store, or enter a 'storeId' from stores mode to get that store's prices.

## `igaStoreId` (type: `string`):

Optional. IGA stores are independently owned and set their own prices. Leave empty for the default store named in each row, or enter a 'storeId' from stores mode.

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

Stop each keyword, category or specials list at this many products per retailer. 0 = no limit (all pages).

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

Hard cap on billable rows for the whole run across all retailers. 0 = no cap (your maximum charge per run still applies).

## `includeDetails` (type: `boolean`):

Adds a 'details' object to each product: barcode (GTIN), description, ingredients, allergens, nutrition panel, country of origin and health star rating, where the retailer publishes them. Charged as an extra 'product-details' event. Coles needs one extra request per product, so this is slower.

## `onlyChanged` (type: `boolean`):

Remember every product's price between runs and output only products that are new, changed price, started or ended a special, or changed stock. You are charged only for rows that are output. Schedule the same input daily for a price-change feed.

## `watcherName` (type: `string`):

With 'Only changed prices': name of the remembered price list. Use a different name for each schedule so they do not overwrite each other.

## `includeMarketplace` (type: `boolean`):

Woolworths also lists over a million third-party marketplace items (Everyday Market). They are left out by default so results show supermarket lines only.

## `maxConcurrency` (type: `integer`):

Requests in flight per retailer. Keep it low; supermarkets throttle aggressive clients.

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

The Apify datacenter proxy (default) is needed for Coles, which blocks most direct cloud connections. Woolworths, Aldi and IGA also work through it.

## Actor input object example

```json
{
  "mode": "search",
  "retailers": [
    "coles",
    "woolworths",
    "aldi",
    "iga"
  ],
  "queries": [
    "milk"
  ],
  "specialsType": "all",
  "maxItemsPerQuery": 20,
  "maxItems": 0,
  "includeDetails": false,
  "onlyChanged": false,
  "watcherName": "default",
  "includeMarketplace": false,
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `productsCsv` (type: `string`):

No description

# 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 = {
    "retailers": [
        "coles",
        "woolworths",
        "aldi",
        "iga"
    ],
    "queries": [
        "milk"
    ],
    "maxItemsPerQuery": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("varnmill/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 = {
    "retailers": [
        "coles",
        "woolworths",
        "aldi",
        "iga",
    ],
    "queries": ["milk"],
    "maxItemsPerQuery": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("varnmill/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 '{
  "retailers": [
    "coles",
    "woolworths",
    "aldi",
    "iga"
  ],
  "queries": [
    "milk"
  ],
  "maxItemsPerQuery": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call varnmill/au-supermarket-prices --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,varnmill/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/xTfAuqrCgVGpjl8Rd/builds/7UEqZXeKhVBervwWw/openapi.json
