# Australia Supermarket Price Scraper: Coles, Woolworths, Aldi (`yappman/au-supermarket-price-scraper`) Actor

Returns current shelf prices from Australian supermarkets in one schema, charged per product returned.

- **URL**: https://apify.com/yappman/au-supermarket-price-scraper.md
- **Developed by:** [Russell Yapp](https://apify.com/yappman) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Australia Supermarket Price Scraper: Coles, Woolworths and Aldi prices API

This is an Australian grocery prices API returning current shelf prices from Coles, Woolworths and Aldi in one schema, as structured data, searched by keyword, by retailer category, or by product page, and charged only for the products you get back. Coles and Woolworths rows carry a GTIN-13 barcode on nearly every row; Aldi publishes no barcode anywhere, so its rows join on `matchKey` instead. One Actor, three chains, EAN barcodes on Coles and Woolworths rows.

### Sample output row

```json
{
  "retailer": "coles",
  "retailerProductId": "1902454",
  "ean": ["9310036048985"],
  "matchKey": "pauls zymil lactose free full cream milk 2000ml",
  "name": "Pauls Zymil Lactose Free Full Cream Milk",
  "brand": "Pauls",
  "packSize": "2L",
  "price": 5.95,
  "wasPrice": 6.9,
  "unitPrice": { "value": 2.98, "measure": "l" },
  "loyaltyPrice": null,
  "promotionText": "save $0.95",
  "promotionEndsAt": null,
  "inStock": true,
  "categoryPath": ["Dairy, Eggs & Fridge", "Milk", "Long Life Milk"],
  "url": "https://www.coles.com.au/product/pauls-zymil-lactose-free-full-cream-milk-2l-1902454",
  "imageUrl": "https://cdn.productimages.coles.com.au/productimages/...",
  "rating": null,
  "currency": "AUD",
  "market": "au",
  "storeId": "7674",
  "scrapedAt": "2026-09-24T10:15:00Z"
}
```

`ean`, `matchKey`, `name`, `brand`, `packSize`, `price`, `wasPrice`, `promotionText`, `inStock`, `storeId`, `currency` and `market` are from a captured Coles product page (24 Sep 2026, product 1902454). `unitPrice`, `categoryPath`, `imageUrl` and `scrapedAt` are illustrative, not real. `rating` and `promotionEndsAt` are always `null` for Coles rows; `loyaltyPrice` is always `null` for Coles and Woolworths (neither retailer's public data exposes a loyalty price).

### What you get: the Australian supermarket prices dataset

| Retailer | Modes | Barcode (EAN) coverage | Promotion fields | Stock |
| --- | --- | --- | --- | --- |
| Coles | search, category, product | On nearly every row; a small number ship without one | `wasPrice` on a markdown; `promotionText` carries multi-buy specials (e.g. `Pick any 2 for $5`) or a markdown's save text (e.g. `save $0.80`) | Coles's own `availability` flag |
| Woolworths | search, category, product | On nearly every row; a barcode that fails check-digit validation is left out and that row ships with an empty `ean` | `wasPrice` on a markdown; `promotionText` carries the special's own tag text, a `Special`/`25% Off`-style badge, or `1/2 Price` | Woolworths' own `IsInStock` flag |
| Aldi | search, category, product | None — Aldi publishes no barcode in its listing API, product API or product pages, so rows carry an empty `ean` and join on `matchKey` | `wasPrice` when Aldi shows a was price; no `promotionText` | Not reported online (`inStock` is `null`) |

Every row also carries `matchKey`, a normalised brand + name + pack size, so you can join Aldi products to Coles and Woolworths even without a barcode.

This Actor is not affiliated with, endorsed by, or partnered with Coles, Woolworths or Aldi. Retailer names are used only to describe the public product data returned.

### Coles, Woolworths and Aldi: what each retailer exposes

**Coles scraper and Coles API.** This Coles price scraper returns Coles's shelf price, unit price and current specials, with a GTIN-13 barcode on nearly every row (a small number ship without one). Prices are as shown to a visitor with no store selected (store `7674`); store selection is not offered in v0.1.

**Woolworths scraper and Woolworths API.** This Woolworths price scraper returns Woolworths' shelf price, a GTIN-13 barcode on nearly every row (a barcode that fails check-digit validation is left out and that row ships with an empty `ean`), unit price where Woolworths publishes one, and current specials. Woolworths assigns every logged-out session a fulfilment store and ranges and prices products per store; the Actor cannot choose one (Woolworths only offers store selection after login), so rows are priced at the store Woolworths assigns: usually `1101` (national default); sometimes a regional store, which changes the range. Every row records the store as `storeId`. Products the assigned store doesn't range or price are skipped rather than returned as $0 rows.

**Aldi Australia prices.** This Aldi Australia price scraper returns Aldi's own in-store reference prices, as shown to a visitor with no store selected (service point `G452`), exactly as published online. Aldi exposes no barcode anywhere — its listing API, product API and product pages all carry none — so rows carry an empty `ean` array and a `matchKey` (normalised name and pack size) instead, and `storeId` is `G452`. Aldi reports no stock online, so `inStock` is `null`. Aldi rows are usually the fastest of the three.

### Australian grocery price tracking and other use cases

- Price-comparison apps and deal-tracking channels
- Competitor and retail-analytics price monitoring
- Inflation trackers and basket-of-goods research
- AI agents doing Australian grocery price or basket lookups
- Building a supermarket price comparison across Coles, Woolworths and Aldi from one schema

### Input reference

| Field | Type | Notes |
| --- | --- | --- |
| `retailers` | array of `coles` (Coles) / `woolworths` (Woolworths) / `aldi_au` (Aldi Australia) | Which retailers to search; default all three. Each runs as its own adapter with its own circuit breaker, so one retailer's bad night doesn't fail the run. Ignored in `category` and `product` mode, where the retailer is inferred from each URL |
| `mode` | `search` / `category` / `product` | One mode per run; default `search` |
| `queries` | array of strings | Search mode; each query runs against every selected retailer; default `["milk"]` |
| `categoryUrls` | array of URLs | Category mode; the retailer is inferred from each URL's hostname |
| `productUrls` | array of URLs | Product mode; retailer inferred from each URL's hostname |
| `maxItems` | integer | Hard cap on rows pushed across all retailers, 1 or more; default `60` |

No store-selection input in v0.1: Coles prices are as shown to a visitor with no store selected, Woolworths prices are for whichever store the logged-out session is assigned, and Aldi prices are as shown to a visitor with no store selected.

The Actor keeps to the paths each site's robots.txt allows, as those files read on 28 Sep 2026. A Coles category or product URL whose data route robots.txt disallows (for example a category ending in `on-special`) is refused: nothing is requested or charged for it, and it is listed in the run's `RUN_SUMMARY` under `perRetailer.coles.refusedUrls`.

### Output field reference

One row per product per retailer, same shape for all three.

| Field | Type | Notes |
| --- | --- | --- |
| `retailer` | string | `coles` / `woolworths` / `aldi_au` |
| `retailerProductId` | string | The retailer's own id (Coles product id, Woolworths `Stockcode`, Aldi SKU) |
| `ean` | array of strings | Check-digit-validated GTIN-13 strings; empty where the retailer exposes none (Aldi) |
| `matchKey` | string | Normalised brand + name + pack size. The join key when `ean` is empty, and the recommended way to match the same product across retailers |
| `name` | string | Product name as shown on site |
| `brand` | string or null | Own-label rows carry the retailer's name |
| `packSize` | string or null | e.g. `2L`, `1L` |
| `price` | number | AUD, current shelf price |
| `wasPrice` | number or null | Previous price when a reduction is shown |
| `unitPrice` | object or null | `{ "value": 2.98, "measure": "l" }` |
| `loyaltyPrice` | number or null | Always `null` for Coles and Woolworths in v0.1 (no public loyalty price) |
| `promotionText` | string or null | e.g. `Pick any 2 for $5`, `25% Off`, `1/2 Price` |
| `promotionEndsAt` | date or null | Always `null` in v0.1: none of the three retailers publishes an end date in the data the Actor reads |
| `inStock` | boolean or null | `null` where the retailer does not report stock online |
| `categoryPath` | array of strings | e.g. `["Dairy, Eggs & Fridge", "Milk"]` |
| `url` | string | Product page |
| `imageUrl` | string or null | Largest image available |
| `rating` | object or null | `{ "value": 4.6, "count": 212 }`; always `null` for Coles in v0.1 |
| `currency` | string | `AUD` |
| `market` | string | `au` |
| `storeId` | string or null | Coles: `7674` (anonymous default). Woolworths: the assigned session's store, usually `1101`; sometimes a regional store, which changes the range. Aldi: `G452` (as shown to a visitor with no store selected) |
| `scrapedAt` | string | ISO 8601, UTC |

### Pricing

Pay per event; platform usage is included in that price, so this is everything you pay:

- **$0.005** start fee per run at the default 512 MB of memory (Apify charges it per GB, so a run given 2 GB pays it twice) — same as the UK listing's start fee
- **$2.00 per 1,000 products** returned (**$0.002** each) on the Free plan — lower on higher Apify plans

**Worked example.** A run searching 5 terms across all three retailers that returns 300 products costs 300 × $0.002 + $0.005 = **$0.605**.

Cap spend with Apify's maximum-charge setting on the run, and/or with the `maxItems` input.

The pricing table also lists two basket events (`basket-line`, `basket-line-live`). They belong to a basket endpoint that isn't available yet; batch runs (search, category and product) never charge them.

### Limitations

- Coles and Woolworths product ranges differ by store; products not ranged or priced at the store this Actor uses are skipped rather than returned as $0 rows.
- Woolworths rows are priced at the store Woolworths assigns: usually `1101` (national default); sometimes a regional store, which changes the range; see `storeId`. Coles prices are as shown to a visitor with no store selected (store `7674`). Store selection is not offered in v0.1.
- Aldi publishes no barcode for any product and no online stock; Aldi rows join on `matchKey` instead of `ean`.
- Public, logged-out data only: no login, basket or account data.
- Prices can change during the day; treat `scrapedAt` as the price's timestamp.
- If a retailer's site blocks a run, that retailer's run stops cleanly rather than returning bad rows.

### FAQ

**Is there a Coles API for prices?**
No. Coles does not publish one; this Coles price scraper is the nearest thing, in search, category and product mode, with a barcode on nearly every row.

**Is there a Woolworths API for prices?**
No. This Woolworths price scraper returns a GTIN-13 barcode on nearly every row, unit price where Woolworths publishes one, and current specials.

**Does it include Aldi Australia prices?**
Yes, in search, category and product mode, exactly as Aldi publishes them online, as shown to a visitor with no store selected (service point `G452`): no barcode, so join on `matchKey` instead of `ean`.

**Why do Aldi rows have no EAN?**
Aldi does not publish a barcode anywhere in its listing API, product API or product pages. Aldi rows carry an empty `ean` array and a `matchKey` (normalised name and pack size) instead.

**Can I build a supermarket price comparison across Coles, Woolworths and Aldi?**
Yes. Match rows on `ean` (Coles and Woolworths) or `matchKey` (all three, and the fallback for Aldi) to build an Australian grocery price comparison in one schema.

**Which store's prices does this use?**
Coles: the price shown to a visitor with no store selected (store `7674`). Woolworths: whichever store the logged-out session is assigned, usually `1101`; sometimes a regional store, which changes the range; see `storeId`. Aldi: as shown to a visitor with no store selected (service point `G452`). None is user-selectable in v0.1.

**How do I limit what I spend?**
Set Apify's maximum-charge cap on the run, and/or set `maxItems` to cap the number of rows returned.

**How fresh is the data?**
Every row is scraped live during the run; `scrapedAt` records when. Prices can move during the day, so treat each row as a snapshot.

**Does this use my Coles or Woolworths account?**
No. The Actor only reads public, logged-out product data — no login, basket or account information.

**How reliable is this?**
If a retailer's site blocks a run, that retailer stops cleanly for the rest of the run rather than returning bad rows. A scheduled canary run of the default input (all three retailers, `milk`, 60 items) runs every day; of its 3 scheduled runs since 26 Sep 2026, 3 returned rows from all three retailers.

After a successful run, please leave a review on this Actor's Store page.

### Changelog

See [CHANGELOG.md](./CHANGELOG.md) for version history.

### Need something custom?

I build and maintain Actors for retail pricing, property listings and sites behind Akamai or Cloudflare, plus scheduled data pipelines with monitoring. Fixed-scope work through Abley-Yapp Consulting — email info@yappman.com with the site and the fields you need.

# Changelog

This Actor's version history is a separate document: https://apify.com/yappman/au-supermarket-price-scraper/changelog.md

# Actor input Schema

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

Which retailers to search; default all three. Each runs as its own adapter with its own circuit breaker, so one retailer's bad night doesn't fail the run. Ignored in category and product mode, where the retailer is inferred from each URL.

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

One mode per run. 'search' runs each query against every selected retailer. 'category' and 'product' take retailer URLs.

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

Keyword searches to run in 'search' mode. Each query runs against every selected retailer.

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

Category page URLs to crawl in 'category' mode. The retailer is inferred from each URL's hostname.

## `productUrls` (type: `array`):

Product page URLs to fetch in 'product' mode. The retailer is inferred from each URL's hostname.

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

Hard cap on rows pushed across all retailers (1 or more).

## Actor input object example

```json
{
  "retailers": [
    "coles",
    "woolworths",
    "aldi_au"
  ],
  "mode": "search",
  "queries": [
    "milk"
  ],
  "categoryUrls": [
    "https://www.coles.com.au/browse/dairy-eggs-fridge/milk",
    "https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk",
    "https://www.aldi.com.au/products/dairy-eggs-fridge/milk/k/1111111160"
  ],
  "productUrls": [
    "https://www.coles.com.au/product/pauls-zymil-lactose-free-full-cream-milk-2l-1902454",
    "https://www.woolworths.com.au/shop/productdetails/261178/pauls-zymil-lactose-free-full-cream-milk",
    "https://www.aldi.com.au/product/farmdale-light-milk-2l-000000000000398691"
  ],
  "maxItems": 60
}
```

# Actor output Schema

## `results` (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_au"
    ],
    "mode": "search",
    "queries": [
        "milk"
    ],
    "categoryUrls": [
        "https://www.coles.com.au/browse/dairy-eggs-fridge/milk",
        "https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk",
        "https://www.aldi.com.au/products/dairy-eggs-fridge/milk/k/1111111160"
    ],
    "productUrls": [
        "https://www.coles.com.au/product/pauls-zymil-lactose-free-full-cream-milk-2l-1902454",
        "https://www.woolworths.com.au/shop/productdetails/261178/pauls-zymil-lactose-free-full-cream-milk",
        "https://www.aldi.com.au/product/farmdale-light-milk-2l-000000000000398691"
    ],
    "maxItems": 60
};

// Run the Actor and wait for it to finish
const run = await client.actor("yappman/au-supermarket-price-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 = {
    "retailers": [
        "coles",
        "woolworths",
        "aldi_au",
    ],
    "mode": "search",
    "queries": ["milk"],
    "categoryUrls": [
        "https://www.coles.com.au/browse/dairy-eggs-fridge/milk",
        "https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk",
        "https://www.aldi.com.au/products/dairy-eggs-fridge/milk/k/1111111160",
    ],
    "productUrls": [
        "https://www.coles.com.au/product/pauls-zymil-lactose-free-full-cream-milk-2l-1902454",
        "https://www.woolworths.com.au/shop/productdetails/261178/pauls-zymil-lactose-free-full-cream-milk",
        "https://www.aldi.com.au/product/farmdale-light-milk-2l-000000000000398691",
    ],
    "maxItems": 60,
}

# Run the Actor and wait for it to finish
run = client.actor("yappman/au-supermarket-price-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 '{
  "retailers": [
    "coles",
    "woolworths",
    "aldi_au"
  ],
  "mode": "search",
  "queries": [
    "milk"
  ],
  "categoryUrls": [
    "https://www.coles.com.au/browse/dairy-eggs-fridge/milk",
    "https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk",
    "https://www.aldi.com.au/products/dairy-eggs-fridge/milk/k/1111111160"
  ],
  "productUrls": [
    "https://www.coles.com.au/product/pauls-zymil-lactose-free-full-cream-milk-2l-1902454",
    "https://www.woolworths.com.au/shop/productdetails/261178/pauls-zymil-lactose-free-full-cream-milk",
    "https://www.aldi.com.au/product/farmdale-light-milk-2l-000000000000398691"
  ],
  "maxItems": 60
}' |
apify call yappman/au-supermarket-price-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yappman/au-supermarket-price-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/dkAJpQkcgwCjvmOhd/builds/ZbpREJvv0N5dZ5OuG/openapi.json
