# Coles Australia Scraper - Prices, Half Price Specials & Stock (`memo23/coles-scraper`) Actor

Scrape Coles (coles.com.au) prices, half-price and multibuy specials, unit price per kg/L, stock, barcode and aisle by keyword, category, specials page or product ID. Pull the whole specials catalogue in one run. Per-store pricing, store finder, price-drop alerts. No proxy needed.

- **URL**: https://apify.com/memo23/coles-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** E-commerce
- **Features:** Limited permissions
- **Stats:** 18 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 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 Australia Scraper - Prices, Half Price Specials & Stock

Scrape **Coles** (`coles.com.au`) products, prices and weekly specials. Search by keyword, paste a category, specials or product URL, list product IDs, or pull the **whole half-price or multibuy catalogue** in one run. Each row has the price, was-price, the deal text (`1/2 Price`, `Any 2 $10`), unit price per kg / L / each, stock, category, and optional barcode, country of origin, nutrition and aisle. Per-store pricing, a nearby store finder, and change tracking that flags price drops and restocks.

| Input | Rows emitted |
|---|---|
| Keyword (`milk`, `tim tams`) | Every matching product |
| Category URL (`/browse/pantry`) | Every product in that category |
| Specials page (`/on-special`, `/specials/half-price`) or the *Specials catalogue* setting | Every product on that kind of special, across the whole store |
| Product URL or product ID (`8150288`) | That product |
| Latitude / longitude | Nearby Coles and Liquorland stores with their store ids |

> Reads Coles' own product API directly. No browser, no proxy to configure.

### Why use this scraper

- **Runs finish.** Each page of results is one API call to Coles, with no browser to load. In the 30 days to 2026-10-09, 33 of 33 public runs on the Apify Store succeeded.
- **The whole specials catalogue, not a sample.** Pick *Half price*, *Multibuy*, *Online-only* or *All specials* and get every product Coles lists under it (half price alone was 1,406 products on 2026-10-09), with paging handled for you.
- **The deal itself, parsed.** `promoLabel` (`1/2 Price`, `Any 3 $4.50`), `promoDescription` (`Pick any 3 for $4.50`), `savePercent`, and a `multiBuy` object with `quantity`, `totalPrice` and `priceEach`, next to `now`, `was` and `saved`.
- **Unit prices you can sort.** `unitPricePerKg`, `unitPricePerL` or `unitPricePerEach` as numbers, so different pack sizes compare directly.
- **Product IDs fetched directly.** Each ID is one call to Coles' product API. An ID Coles does not know is named in the run log, and a run where fetches fail and nothing comes back ends FAILED with the reason, not as an empty "success".
- **Per-store pricing.** Pass a store id (`COL:<storeNo>`) for that store's prices and stock. Don't know it? Run once with a latitude/longitude to list nearby stores.
- **Change tracking.** With *Tracking* on, each row is marked `NEW`, `PRICE_DROP`, `PRICE_RISE`, `BACK_IN_STOCK`, `OUT_OF_STOCK` or `UNCHANGED` against the last run of the same watchlist. *Changed products only* delivers and bills only the changes.

### How it works

1. Keywords, category URLs, specials pages and product IDs are classified and sent to Coles' product API (the one coles.com.au itself uses), for the store you chose.
2. Specials use Coles' own specials filter, so a half-price run returns what Coles lists as half price, across the store or inside your keyword or category.
3. Results are paged until `maxItems` or the end of the list. If the API path fails, the actor falls back to reading the Coles web pages.
4. Each product becomes one flat row. In change-tracking mode each row is compared with the last run of the same watchlist.

### Input

In the order of the input form:

| Field | Type | Notes |
|---|---|---|
| `searchTerms` | string\[] | Words to search, each on its own. Prefilled with `milk`. |
| `skuList` | string\[] | Coles product numbers, e.g. `8150288`. |
| `startUrls` | string\[] | Coles search, `/browse/` category, specials (`/on-special`, `/specials/half-price`) or single product links. |
| `specials` | `none` / `all` / `halfPrice` / `multibuy` / `onlineOnly` | Keeps one kind of weekly special. With nothing else filled in, returns Coles' whole catalogue of that kind. Default `none`. |
| `specialsOnly` | boolean | Keeps only products on any special (lower was-price, multibuy or online special). Default `false`. |
| `store` | string | Store as `COL:<number>`. Empty = Coles' online store. |
| `includeDetails` | boolean | Adds `gtin` (barcode), `countryOfOrigin`, `nutrition`, `aisle`. Default `false`. |
| `includeDescription` | boolean | Product description text. Default `true`. |
| `stateName` | string | Watchlist name; runs with the same name are compared. Default `default`. |
| `monitorMode` | `snapshot` / `changes` | `changes` adds `changeType` and the earlier price and availability. Default `snapshot`. |
| `onlyChanged` | boolean | Leaves out `UNCHANGED` rows (a watchlist's first run returns everything as `NEW`). Default `false`. |
| `maxItems` | integer | Products in the run, all billed. Default `1000`. |
| `maxRequestsPerCrawl` | integer | Page limit, `0` = none. |
| `maxConcurrency` | integer | Pages at once, 1-30. Default `10`. |
| `storeLookup` | `{latitude, longitude}` | Lists nearby Coles and Liquorland stores instead of products. |

#### Example inputs

Whole half-price catalogue (clear the prefilled keyword):

```json
{ "searchTerms": [], "specials": "halfPrice", "maxItems": 2000 }
```

Multibuy deals on chocolate and in the pantry category:

```json
{
  "searchTerms": ["chocolate"],
  "startUrls": ["https://www.coles.com.au/browse/pantry"],
  "specials": "multibuy"
}
```

Daily price watch on a fixed basket, billing only changes:

```json
{ "skuList": ["8150288", "2351709"], "monitorMode": "changes", "onlyChanged": true, "stateName": "my-basket" }
```

Find store ids near Sydney CBD:

```json
{ "storeLookup": { "latitude": -33.86, "longitude": 151.21 } }
```

### Output

#### Product row (multibuy)

```json
{
  "name": "Dairy Milk Chocolate Caramello Koala Sharepack 12 Pack",
  "brand": "Cadbury",
  "size": "180g",
  "price": {
    "now": 7.5,
    "was": null,
    "saved": null,
    "currency": "AUD",
    "onlineSpecial": false,
    "onSpecial": true,
    "specialType": "MULTI_SAVE",
    "promoLabel": "Any 2 $10",
    "promoDescription": "Pick any 2 for $10",
    "savePercent": null,
    "multiBuy": { "quantity": 2, "totalPrice": 10, "priceEach": 5 },
    "comparable": "$4.17/ 100g",
    "unitPrice": 4.17,
    "unitMeasure": "100g",
    "unitPricePerKg": 41.7,
    "unitPricePerL": null,
    "unitPricePerEach": null,
    "unitPriceBasis": "kg"
  },
  "availability": true,
  "availableQuantity": null,
  "availabilityType": "InStoreAndOnline",
  "categoryGroup": "IMPULSE",
  "category": "Chocolates",
  "subCategory": "Chips, Chocolates & Snacks",
  "description": "CADBURY DAIRY MILK CHOCOLATE CARAMELLO KOALA SHAREPACK 12 PACK 180G",
  "restrictions": { "retailLimit": 20, "promotionalLimit": 12, "liquorAge": false, "tobaccoAge": false, "delivery": [] },
  "featured": false,
  "productUrl": "https://www.coles.com.au/product/cadbury-dairy-milk-chocolate-caramello-koala-sharepack-12-pack-180g-2353056",
  "imageUrl": "https://productimages.coles.com.au/productimages/2/2353056.jpg",
  "id": 2353056,
  "type": "product",
  "source": "coles.com.au",
  "sourceUrl": "https://www.coles.com.au/search/products?q=chocolate",
  "scrapedAt": "2026-10-09T08:55:32.350Z"
}
```

A half-price row's `price` looks like this:

```json
{ "now": 2.02, "was": 4.05, "saved": 2.03, "currency": "AUD", "onlineSpecial": false, "onSpecial": true, "specialType": "PERCENT_OFF", "promoLabel": "1/2 Price", "promoDescription": null, "savePercent": 50, "multiBuy": null, "comparable": "$8.42/ 1L", "unitPrice": 8.42, "unitMeasure": "1L", "unitPricePerKg": null, "unitPricePerL": 8.42, "unitPricePerEach": null, "unitPriceBasis": "L" }
```

With *Barcode, origin, serving size and aisle* on, each row also has `gtin`, `countryOfOrigin`, `nutrition` (`servingSize`, `servingsPerPackage`) and `aisle`. Change-tracking runs add `changeType`, `previousPrice` and `previousAvailability`.

#### Store row (store discovery)

```json
{ "type": "store", "source": "coles.com.au", "storeId": "COL:840", "name": "Coles Central Wynyard Express", "distanceKm": 0.717, "scrapedAt": "2026-10-09T08:55:32.350Z" }
```

#### Key fields

| Field | Meaning |
|---|---|
| `price.now`, `price.was`, `price.saved` | Current price, pre-special price, difference (AUD) |
| `price.onSpecial` | Any Coles promotion on the product |
| `price.specialType` | `PERCENT_OFF`, `MULTI_SAVE`, or null |
| `price.promoLabel`, `price.promoDescription` | Deal text as shown on Coles |
| `price.multiBuy` | `quantity`, `totalPrice`, `priceEach` for multibuy deals |
| `price.unitPricePerKg` / `PerL` / `PerEach` | Normalized unit price |
| `availability`, `availabilityType` | In stock, and where it is sold |
| `category`, `subCategory`, `categoryGroup` | Coles category path |
| `restrictions` | Purchase limits and age flags |

The dataset has three views: *Overview*, *Specials* (price, was, deal text, % off) and *All fields*.

### Pricing

Pay per event: $0.005 per run start plus $0.0008 per dataset row ($0.80 per 1,000 products). With *Only changed rows*, unchanged products are neither delivered nor billed. Free-plan runs are capped at 25 products, 3 runs per 24 hours, with 30 minutes between runs.

### Coming from another Coles scraper?

| Common complaint | This actor |
|---|---|
| Runs time out | One API call per page of results, no browser |
| Only a handful of results, or a cap of about 100 | Pages through to `maxItems`, including the whole half-price catalogue |
| Product-ID lookups return nothing but say "succeeded" | IDs go straight to Coles' product API; fetch failures with no rows end FAILED with a reason |

### Notes & limitations

- **Postcode lookup** is not supported. Use `storeLookup` with the postcode's coordinates.
- **Barcode search** is not supported: Coles' search does not match barcodes. Turn on *Include extra details* to get each product's `gtin` and match on your side.
- **`availableQuantity`** is null on most rows: Coles' product API does not expose stock counts. `availability` (in stock or not) is always filled.

### FAQ

**Do I need proxies?** No.

**How do I get every half-price product?** Set *Specials catalogue* to *Half price* and clear the keyword field, or paste `https://www.coles.com.au/specials/half-price` into the URL field.

**Can I get specials within one category?** Yes. Paste the `/browse/...` URL and pick a specials kind; Coles filters the category.

**Are prices store-specific?** Yes. Pass `store` as `COL:<storeNo>`, and use `storeLookup` to find store ids.

**How does change tracking work?** Each run compares with the last run of the same `stateName`, tags rows with `changeType` and the previous price, and with `onlyChanged` drops unchanged rows. Schedule the actor daily for a price watch.

**How fresh is the data?** Read live from Coles when the run starts.

### Support

Found a problem or need a field? Open an issue on the actor's Issues tab and include the run id.

### 🤖 For AI Agents & LLM Apps

- **Purpose:** live Coles Australia grocery prices, specials and stock.
- **Minimal input:** `{"searchTerms": ["milk"], "maxItems": 20}`
- **Specials catalogue:** `{"searchTerms": [], "specials": "halfPrice", "maxItems": 100}` (values: `all`, `halfPrice`, `multibuy`, `onlineOnly`)
- **Per store:** add `"store": "COL:<storeNo>"`; get ids with `{"storeLookup": {"latitude": -33.86, "longitude": 151.21}}`
- **Output:** one flat JSON row per product: `id, name, brand, size, price{now, was, saved, onSpecial, specialType, promoLabel, promoDescription, savePercent, multiBuy, unitPricePerKg, unitPricePerL, unitPricePerEach}, availability, category, subCategory, productUrl, imageUrl`
- **Billing:** per dataset row plus a small start fee; `maxItems` caps cost.
- **Behavior:** no matches (with no fetch errors) ends SUCCEEDED with 0 rows; fetch failures with no rows end FAILED with the reason in the status message.

### ⚠️ Disclaimer

This actor collects only publicly available information from `coles.com.au`. It is not affiliated with or endorsed by Coles Group. Use the data in line with applicable laws and the site's terms. You are responsible for how you use the data.

### SEO Keywords

Coles scraper, coles.com.au scraper, Coles API, Coles price scraper, Coles specials scraper, Coles half price specials, Coles multibuy, Coles catalogue scraper, Australian grocery prices API, supermarket price monitoring Australia, grocery price comparison Australia, Coles unit price, Coles stock availability, Coles price drop alerts, Coles barcode GTIN, Coles nutrition data, Coles per-store pricing, Coles store locator.

# Actor input Schema

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

Words to search, one per line, e.g. milk, coffee or tim tams.

## `skuList` (type: `array`):

Product numbers to fetch, one per line. You find them at the end of a Coles product link, e.g. 8150288 in /product/...-8150288.

## `startUrls` (type: `array`):

Search results (e.g. https://www.coles.com.au/search/products?q=coffee), category pages under /browse/, specials pages (/on-special, /specials/half-price) or single product pages. Each is read the way its page type needs.

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

Off by default. Pick a kind of special to keep only those products. With keywords or links, you get the specials among their results. With nothing else filled in, you get Coles' whole catalogue of that kind (half price alone is often more than 1,000 products), so clear the starting keyword for a full catalogue.

## `specialsOnly` (type: `boolean`):

On: keeps only products on special right now, whether a lower was-price, a multibuy deal or an online special. Works with every kind of input.

## `store` (type: `string`):

A Coles store as COL:<number>, e.g. COL:7674; prices and stock differ by store. Empty: Coles' online store. The store finder at the bottom of the form gives store numbers.

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

Off by default. On: each row also gets the barcode (GTIN), country of origin, serving size and servings per pack, and the aisle.

## `includeDescription` (type: `boolean`):

On (default): the product's full description where Coles shows one.

## `stateName` (type: `string`):

A name for one set of tracked products, e.g. weekly-coffee. Runs with the same name are compared with each other; use separate names for separate searches.

## `monitorMode` (type: `string`):

Off (default): plain product rows. On: each row also says what changed since the last run of this watchlist (NEW, PRICE_DROP, PRICE_RISE, BACK_IN_STOCK, OUT_OF_STOCK or UNCHANGED), with the earlier price and availability.

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

On: rows marked UNCHANGED are left out, and tracking switches on by itself. A watchlist's first run marks every product NEW.

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

The run stops after this many products, default 1,000; only those are billed. The free Apify plan allows 25 products a run and 3 runs a day.

## `maxRequestsPerCrawl` (type: `integer`):

Stops after this many pages of results. 0 (default): no limit.

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

1 to 30, default 10.

## `storeLookup` (type: `object`):

Enter a latitude and longitude to get the nearest Coles and Liquorland stores (number, name, distance) instead of products. Put a store number in the store field above on a later run.

## Actor input object example

```json
{
  "searchTerms": [
    "milk"
  ],
  "skuList": [],
  "startUrls": [],
  "specials": "none",
  "specialsOnly": false,
  "includeDetails": false,
  "includeDescription": true,
  "stateName": "default",
  "monitorMode": "snapshot",
  "onlyChanged": false,
  "maxItems": 1000,
  "maxRequestsPerCrawl": 0,
  "maxConcurrency": 10,
  "storeLookup": {}
}
```

# Actor output Schema

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

One row per Coles product in the default dataset.

# 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 = {
    "searchTerms": [
        "milk"
    ],
    "skuList": [],
    "startUrls": [],
    "stateName": "default",
    "monitorMode": "snapshot",
    "storeLookup": {}
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/coles-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 = {
    "searchTerms": ["milk"],
    "skuList": [],
    "startUrls": [],
    "stateName": "default",
    "monitorMode": "snapshot",
    "storeLookup": {},
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/coles-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 '{
  "searchTerms": [
    "milk"
  ],
  "skuList": [],
  "startUrls": [],
  "stateName": "default",
  "monitorMode": "snapshot",
  "storeLookup": {}
}' |
apify call memo23/coles-scraper --silent --output-dataset

```

## MCP server setup

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