# Coles & Woolworths Grocery Price Scraper (`cylindrical_lighthouse/au-grocery-prices`) Actor

Coles and Woolworths prices, specials, unit prices, stock and barcodes in one schema. Search, categories, per-store Coles pricing and price-change alerts.

- **URL**: https://apify.com/cylindrical\_lighthouse/au-grocery-prices.md
- **Developed by:** [Lighthouse Data](https://apify.com/cylindrical_lighthouse) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 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

## Coles & Woolworths Grocery Price Scraper

Get **product prices, specials, unit prices, stock and barcodes from Coles and Woolworths**, Australia's two largest supermarkets, in **one clean, normalised dataset**. Search by keyword, crawl categories, look up product URLs or pull the weekly specials. Schedule it and get **only the prices that changed since the last run**. Export to JSON, CSV or Excel, or use it through the API, integrations and AI agents.

> Independent tool. Not affiliated with, endorsed by or sponsored by Coles Group or Woolworths Group.

### What does the Coles & Woolworths Grocery Price Scraper do?

- 🛒 **Both chains, one schema.** Every product has the same fields whether it comes from Coles or Woolworths: `price`, `wasPrice`, `unitPrice` + `unitPriceUnit` (normalised to `1L`, `100g`, `1kg`, `1ea`…), `isOnSpecial`, `isHalfPrice`, `promoType`, `promoText`, multibuy deals, `inStock`, category path, `barcode`, `storeId` and the product URL. Prices are numbers in AUD.
- 🔎 **Four ways in:** keyword search, category (aisle) URLs, product URLs, and the **specials catalogue** (all specials or half price only).
- 🏪 **Per-store Coles pricing.** Prices differ by store and region; pick any of Coles' ~870 stores.
- 📉 **Price-change monitoring.** Turn on monitoring and each run outputs only products that are **new, changed or removed** since the previous run, with `previousPrice`, `priceChange` and `priceChangePct`.
- 🏷️ **Barcodes (GTIN/EAN).** Always included for Woolworths; optional for Coles listings.
- 🛡️ **Built for reliability.** Plain-HTTP JSON (no browser), session rotation, automatic retries, and loud failure when a site changes, so you never silently get an empty dataset.

### Why use it?

- **Coles and Woolworths in one run, one schema:** the same fields for both chains, ready to compare.
- **Unit prices normalised for comparison:** `1L`, `100g`, `1kg`, `1ea`.
- **"What changed since last run" built in:** new, changed and removed products, with the price delta.
- **Specials normalised:** half price, % off, multibuy and price drops in one `promoType` field.
- **Pick the Coles store** whose prices you want.
- **Low start fee:** $0.005 per run.

### Use cases

- **Price-comparison apps and deal sites.** Feed Coles vs Woolworths prices and weekly specials into your app.
- **FMCG brands and category managers.** Track your own and competitors' shelf prices, promotion cadence ("half price every 4th week") and pack-size changes.
- **Analysts, economists and journalists.** Measure grocery inflation, specials depth and "shrinkflation" over time with scheduled snapshots.
- **Researchers.** Build unit-price datasets across categories and regions (public health, nutrition pricing).
- **AI agents.** Give an assistant live Australian grocery prices ("cheapest 2L milk at Coles vs Woolworths this week").

### How to use it

1. Click **Try for free**.
2. Enter one or more **search keywords** (e.g. `milk`), and/or paste **category** or **product URLs**, and/or choose a **specials** mode.
3. Keep both supermarkets selected, or pick one.
4. Click **Start**. Download the results as JSON, CSV, Excel or HTML, or read them through the API.

#### Example input

```json
{
  "chains": ["coles", "woolworths"],
  "searchQueries": ["milk", "coffee beans"],
  "specials": "halfPrice",
  "categoryUrls": ["https://www.coles.com.au/browse/dairy-eggs-fridge/milk"],
  "maxItemsPerSource": 100,
  "colesStoreId": "313"
}
```

### 📅 Schedule it: weekly specials and price-change alerts

Coles and Woolworths change their specials **every Wednesday**. A typical monitoring setup:

1. Create a **Task** with your watch list, e.g. `{"searchQueries": ["nappies", "coffee beans"], "specials": "halfPrice", "monitorPriceChanges": true, "monitorKey": "weekly-watchlist", "maxItemsPerSource": 200}`.
2. Add a **Schedule** that runs it every Wednesday at 07:00 Australia/Sydney (cron `0 7 * * 3`).
3. Connect an **integration** (email, Slack, Google Sheets, Zapier, Make or a webhook) to the run's dataset.

Each run then delivers only what changed: new specials, price drops and rises, and products that disappeared, with `changeType`, `previousPrice`, `priceChange` and `priceChangePct`. Unchanged products are checked for a small fee but not written. Use a different `monitorKey` for each watch list.

### Input

| Field | Type | Description | Example |
|---|---|---|---|
| `chains` | array | Supermarkets to search and pull specials from. URLs are routed by domain. | `["coles", "woolworths"]` |
| `searchQueries` | array | Product keywords, as a shopper would type them. | `["milk", "coffee beans"]` |
| `specials` | string | `none`, `all` (every discounted product) or `halfPrice`. | `"halfPrice"` |
| `categoryUrls` | array | Category (aisle) pages from either site. | `["https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk"]` |
| `productUrls` | array | Specific product pages (always include the barcode). | `["https://www.coles.com.au/product/coles-full-cream-milk-3l-8150288"]` |
| `maxItemsPerSource` | integer | Products per source (one keyword on one chain, one category, or one chain's specials). Default 100. | `100` |
| `maxItems` | integer | Overall cap for the run; 0 = none. | `500` |
| `colesStoreId` | string | Coles store number or store page URL. Empty = Coles' default online store. | `"313"` |
| `includeDetails` | boolean | Add barcodes to Coles listing results (one extra request per Coles product). | `true` |
| `monitorPriceChanges` | boolean | Output only new, changed and removed products since the last run with the same monitor key. | `true` |
| `monitorKey` | string | Name of the saved snapshot to compare against. | `"weekly-dairy"` |
| `outputUnchanged` | boolean | In monitoring mode, also write unchanged products. | `false` |
| `proxyConfiguration` | object | Proxy for Coles, and the fallback for Woolworths. Defaults to Apify residential proxy in Australia (included in the price). | `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "AU"}` |

### Output

Each dataset item is one product at one store. The **Products**, **Specials** and **Price changes** views show the most useful columns. Example records:

```json
[
  {
    "chain": "coles",
    "productId": "1492374",
    "name": "Coca-Cola Zero Sugar Soft Drink Multipack Bottles 300ml",
    "brand": "Coca-Cola",
    "size": "12 Pack",
    "barcode": null,
    "url": "https://www.coles.com.au/product/coca-cola-zero-sugar-soft-drink-multipack-bottles-300ml-12-pack-1492374",
    "imageUrl": "https://cdn.productimages.coles.com.au/productimages/1/1492374.jpg",
    "price": 12,
    "wasPrice": 24,
    "currency": "AUD",
    "unitPrice": 3.33,
    "unitPriceUnit": "1L",
    "unitPriceText": "$3.33/ 1L",
    "isOnSpecial": true,
    "isHalfPrice": true,
    "promoType": "halfPrice",
    "promoText": "1/2 Price",
    "savingsAmount": 12,
    "multibuyQuantity": null,
    "multibuyPrice": null,
    "inStock": true,
    "category": "Drinks",
    "subCategory": "Soft Drinks",
    "categoryPath": ["Drinks", "Soft Drinks", "Soft Drink Cans"],
    "storeId": "7674",
    "source": "specials",
    "sourceQuery": "halfPrice",
    "position": 1,
    "scrapedAt": "2026-09-29T21:30:12.418Z",
    "changeType": null,
    "previousPrice": null,
    "priceChange": null,
    "priceChangePct": null
  },
  {
    "chain": "woolworths",
    "productId": "971011",
    "name": "OxyShred Energy Drink Passionfruit Cans",
    "brand": "OxyShred",
    "size": "355mL x 4 pack",
    "barcode": "810095635700",
    "url": "https://www.woolworths.com.au/shop/productdetails/971011/oxyshred-energy-drink-passionfruit-cans",
    "imageUrl": "https://cdn1.woolworths.media/content/wowproductimages/large/971011.jpg",
    "price": 16,
    "wasPrice": null,
    "currency": "AUD",
    "unitPrice": 11.27,
    "unitPriceUnit": "1L",
    "unitPriceText": "$11.27 / 1L",
    "isOnSpecial": true,
    "isHalfPrice": false,
    "promoType": "multibuy",
    "promoText": "3 for $30.00 - $7.04/1L",
    "savingsAmount": null,
    "multibuyQuantity": 3,
    "multibuyPrice": 30,
    "inStock": true,
    "category": "Drinks",
    "subCategory": "Sports & Energy Drinks",
    "categoryPath": ["Drinks", "Sports & Energy Drinks", "Energy Drinks"],
    "storeId": "1101",
    "source": "search",
    "sourceQuery": "energy drink",
    "position": 4,
    "scrapedAt": "2026-09-29T21:30:14.071Z",
    "changeType": "changed",
    "previousPrice": 18,
    "priceChange": -2,
    "priceChangePct": -11.11
  }
]
```

A `SUMMARY` record in the run's key-value store lists products per chain, the stores used, sources that returned nothing, products or categories not found, and any failures.

### Pricing

Pay-per-event: you pay only for what you get. The proxy and platform costs are **included**.

| Event | Free | Bronze | Silver | Gold | Platinum | Diamond |
|---|---|---|---|---|---|---|
| Actor start (once per run) | $0.005 | $0.005 | $0.004 | $0.003 | $0.003 | $0.003 |
| Product saved | $0.0015 | $0.0013 | $0.0011 | $0.0009 | $0.0009 | $0.0009 |
| Coles barcode enrichment (optional, per Coles product) | $0.001 | $0.0008 | $0.0006 | $0.0005 | $0.0005 | $0.0005 |
| Unchanged product checked (monitoring mode) | $0.0005 | $0.0004 | $0.0003 | $0.00025 | $0.00025 | $0.00025 |

**Cost examples (Free plan prices):**

- Default input (`milk` on both chains, 10 products each): $0.005 + 20 × $0.0015 = **$0.035**.
- 1,000 products: **≈ $1.51**.
- Weekly monitoring of a 500-product watch list where 60 products change: $0.005 + 60 × $0.0015 + 440 × $0.0005 = **≈ $0.32 per week**.

Set **Maximum cost per run** in the run options and the Actor stops cleanly when it is reached.

### FAQ

**How fresh are the prices?** They are fetched live from coles.com.au and woolworths.com.au when the Actor runs. Specials change every Wednesday.

**Which store are the prices for?** Every record carries the `storeId` its price applies to. Coles prices are for the store you set in `colesStoreId`, or Coles' default online store. Woolworths prices are for Woolworths' **national default online store**. The Actor keeps Woolworths on that store for every run, so scheduled comparisons stay consistent. Some fresh and regional products are priced differently, or only ranged, in other regions.

**Why is `barcode` empty for some Coles products?** Coles listings do not include barcodes. Turn on **Add Coles barcodes**, or use product URLs.

**Why is `price` null?** The product is currently unavailable and the supermarket shows no price. We never invent values: missing data is `null`.

**Does it log in or use my account?** No. It only reads public product pages. There is no login, no CAPTCHA solving and no personal data.

**Is it legal?** This Actor extracts only publicly available product and price data. It does not extract private user data. Your results may still contain personal data, which is protected by GDPR and other regulations. Do not scrape personal data unless you have a legitimate reason. If you are unsure, consult your lawyers. The supermarkets' website terms restrict use of their sites; you are responsible for how you use the data. Keep volumes reasonable.

**Can I use it from an AI agent or MCP client?** Yes. Every input and output field is described in the schema, and the Actor is callable through the Apify API and Apify's MCP server.

### Limitations

- **Woolworths regional store selection** is not supported yet: Woolworths prices are for its national default online store (reported in `storeId`). Coles store selection is fully supported.
- Woolworths **Everyday Market** (third-party marketplace) listings are excluded; they are not supermarket prices.
- Search results follow each site's own relevance order and fuzzy matching, so a nonsense keyword can still return a few loosely related products.
- Monitoring compares prices and promotions only. Stock changes are reported in `inStock` but do not count as a "change". Category listings also rotate a few sponsored cross-placements and ranged items between runs (about 0.4% in our tests); these show up as new or removed.
- "Removed" products are reported only when the run covered every source completely (no limits hit, no failures).
- ALDI and IGA are not covered.

### Changelog

See the [changelog](./CHANGELOG.md).

### Support

Found a bug, or need a feature (another supermarket, Woolworths store pinning, nutrition data)? Open an issue on the **Issues** tab. We reply within 48 hours.

# Changelog

This Actor's version history is a separate document: https://apify.com/cylindrical\_lighthouse/au-grocery-prices/changelog.md

# Actor input Schema

## `chains` (type: `array`):

Which supermarkets to search and to scrape specials from. Category and product URLs are routed automatically by their domain. Allowed values: "coles", "woolworths". Example: \["coles", "woolworths"].

## `searchQueries` (type: `array`):

Product keywords to search on each selected supermarket, as a shopper would type them. One source per keyword per supermarket. Example: \["milk", "coffee beans", "nappies size 4"].

## `specials` (type: `string`):

Also collect the current specials from each selected supermarket. "all" = every discounted product (price drops, half price, multibuys), "halfPrice" = half-price specials only, "none" = skip. Specials change every Wednesday. Example: "halfPrice".

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

Category (aisle) pages to crawl, copied from the browser. Coles: https://www.coles.com.au/browse/dairy-eggs-fridge/milk. Woolworths: https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk. Each URL is one source.

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

Specific product pages to look up (always includes the barcode). Coles: https://www.coles.com.au/product/coles-full-cream-milk-3l-8150288. Woolworths: https://www.woolworths.com.au/shop/productdetails/62636/pura-full-cream-milk. Unknown products are listed under notFound in the SUMMARY record, not treated as errors.

## `maxItemsPerSource` (type: `integer`):

Stop each source (one keyword on one supermarket, one category URL, or one supermarket's specials) after this many products. Keeps cost predictable. Example: 100.

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

Overall cap on products for the whole run, across all sources. 0 = no overall cap (the per-source limit still applies). Example: 500.

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

Price Coles products at a specific store (prices differ by store and region). Enter the store number, or paste the store page URL from coles.com.au/find-stores, e.g. "313" or "https://www.coles.com.au/find-stores/coles/wa/karratha-313". Leave empty for Coles' default online store. Woolworths prices are always for Woolworths' national default online store (reported in storeId).

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

Coles search, category and specials listings do not include the barcode (GTIN). Turn this on to open each Coles product and add it, charged as a separate 'product-details' event. Woolworths listings already include barcodes. Example: true.

## `monitorPriceChanges` (type: `boolean`):

Monitoring mode for scheduled runs: compare every product with the previous run that used the same monitor key, and output only new, changed and removed products (changeType, previousPrice, priceChange, priceChangePct). Unchanged products are checked but not written. Example: true.

## `monitorKey` (type: `string`):

Name of the saved price snapshot to compare against. Use a different key for each watch list (each schedule or task), so different inputs never compare against each other. Letters, digits, - and \_ only. Example: "weekly-dairy".

## `outputUnchanged` (type: `boolean`):

In monitoring mode, also write unchanged products (changeType = "unchanged") so you get a full price list plus the change flags. Example: false.

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

Proxy for Coles, and the fallback for Woolworths. The default, Apify residential proxy in Australia, is the most reliable, and its cost is included in the price. Woolworths goes through Apify datacenter proxy so its prices stay on Woolworths' national default online store; if that is blocked, it switches to this proxy automatically. Example: {"useApifyProxy": true, "apifyProxyGroups": \["RESIDENTIAL"], "apifyProxyCountry": "AU"}.

## Actor input object example

```json
{
  "chains": [
    "coles",
    "woolworths"
  ],
  "searchQueries": [
    "milk"
  ],
  "specials": "none",
  "categoryUrls": [
    "https://www.coles.com.au/browse/dairy-eggs-fridge/milk",
    "https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk"
  ],
  "productUrls": [
    "https://www.coles.com.au/product/coles-full-cream-milk-3l-8150288",
    "https://www.woolworths.com.au/shop/productdetails/62636/pura-full-cream-milk"
  ],
  "maxItemsPerSource": 10,
  "maxItems": 0,
  "includeDetails": false,
  "monitorPriceChanges": false,
  "monitorKey": "default",
  "outputUnchanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AU"
  }
}
```

# Actor output Schema

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

All products with price, was-price, unit price, promotion, stock, category, barcode and store.

## `specials` (type: `string`):

Promotion-focused view: promotion type and text, savings, half price and multibuy deals.

## `priceChanges` (type: `string`):

Monitoring mode: new, changed and removed products since the previous run, with previous price and delta.

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

Products per chain, stores used, sources without results, not-found items, failed requests and site-change errors.

# 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 = {
    "chains": [
        "coles",
        "woolworths"
    ],
    "searchQueries": [
        "milk"
    ],
    "maxItemsPerSource": 10,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "AU"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("cylindrical_lighthouse/au-grocery-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 = {
    "chains": [
        "coles",
        "woolworths",
    ],
    "searchQueries": ["milk"],
    "maxItemsPerSource": 10,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "AU",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("cylindrical_lighthouse/au-grocery-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 '{
  "chains": [
    "coles",
    "woolworths"
  ],
  "searchQueries": [
    "milk"
  ],
  "maxItemsPerSource": 10,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AU"
  }
}' |
apify call cylindrical_lighthouse/au-grocery-prices --silent --output-dataset

```

## MCP server setup

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