# Coles Australia Scraper - Prices, Specials, Nutrition & Stock (`rel8ble/coles-scraper`) Actor

Coles supermarket product data: price, was price, unit price, half-price and multibuy specials, stock, category, barcode, ingredients, allergens and nutrition. Search, categories, specials or product URLs. HTTP-only, fast and cheap.

- **URL**: https://apify.com/rel8ble/coles-scraper.md
- **Developed by:** [Giovanni Rich](https://apify.com/rel8ble) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

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 Scraper - Coles Australia Prices API & Specials Scraper

**Coles scraper** and unofficial **Coles Australia prices API**: scrape coles.com.au product prices, was prices, unit prices, half-price and multibuy specials, stock and categories, plus optional barcode (GTIN), ingredients, allergens and nutrition. Search by keyword, scrape whole categories or the weekly Coles specials catalogue, and export Australian grocery prices to JSON, CSV or Excel.

Coles is Australia's second-largest supermarket, and it sits behind Imperva bot protection, which is why scrapers for it often fail. This one reads the same JSON data the Coles website loads, over plain HTTP with **no headless browser**. It uses Australian residential proxies and rotates to a fresh session whenever Coles pushes back. In our test runs every request succeeded, and a full half-price catalogue (1,549 products) finished in under a minute.

### How to use

1. Enter what to scrape: keywords (`milk`, `tim tam`), categories (`bakery`, or `all` for the whole catalogue), a specials list (`halfprice`, `multibuy`...) or any coles.com.au URL. Optionally add a store ID and turn on product details.
2. Click **Start**. The Coles specials scraper saves one row per product as it goes.
3. Download the results as JSON, CSV or Excel, or call the actor through the Apify API and schedule it daily or weekly.

### What you get

- **Prices**: current price, was price, dollar and percent saving, unit price (`$3.06/ 100g`) with its number and unit split out, and a flag for products sold by weight.
- **Specials**: special type (`half_price`, `price_drop`, `multibuy`, `percent_off`, `special`), offer text (`save $2.50`, `Buy 2 for $9.40`), multibuy quantity and total price, online-only flag, and the promo purchase limit.
- **Stock**: in stock or not, availability type (in store and online), and the quantity Coles reports as available.
- **Category**: department > category > aisle, with IDs, plus Coles' internal merchandise hierarchy.
- **Product details** (with `includeDetails`): barcode/GTIN, long description, ingredients, allergens, dietary info, storage and usage instructions, dimensions, country of origin with the Australian content %, the full nutrition panel (per serving and per 100 g/ml), and all product images.
- **Store-specific data**: give a store ID to get that store's prices, stock and shelf location.

### Use cases

- Grocery price monitoring and price-history databases (schedule it daily or weekly)
- Weekly specials trackers, half-price alerts and deal sites
- Competitor pricing for FMCG brands, retailers and category managers
- Product and nutrition databases, keyed by barcode
- Market research: range, pack sizes and price per unit by category
- Australian grocery price comparison (pair it with a Woolworths scraper)
- Feeding live Coles prices to AI agents and shopping assistants via the Apify MCP server

### Input example

Use any mix of the four ways in. Each keyword, category, specials list or URL is scraped separately, up to `maxItems` products each.

| Field | Default | Description |
|---|---|---|
| `searchQueries` | - | Keywords as typed into the Coles search box: `"milk"`, `"tim tam"`. A barcode works too. |
| `categories` | - | Category slugs or browse URLs: `"dairy-eggs-fridge"`, `"pantry/breakfast"`, `"https://www.coles.com.au/browse/bakery"`. Use `"all"` for the whole catalogue (every department, deduplicated). |
| `specials` | `none` | Weekly specials: `all`, `halfprice`, `multibuy` or `onlineonly`. |
| `startUrls` | - | Any coles.com.au search, category, specials or product URL. Filters in the URL are kept. |
| `maxItems` | 100 | Products per source. `0` = everything Coles lists. |
| `includeDetails` | false | Adds barcode, ingredients, allergens, nutrition, origin and images. One extra request per product. |
| `storeId` | Coles default | A store number like `7799`, or a store page URL such as `https://www.coles.com.au/find-stores/coles/vic/ardeer-7799`. |
| `includeSponsored` | false | Include the paid product ads Coles puts at the top of search and specials pages. They're flagged `isSponsored`. |
| `proxyConfiguration` | Residential, AU | Leave as is unless you have a reason to change it. |

Example: every half-price special, plus the bakery department and two keyword searches with full product details:

```json
{
    "specials": "halfprice",
    "categories": ["bakery"],
    "searchQueries": ["chocolate", "shampoo"],
    "maxItems": 0,
    "includeDetails": true
}
```

### Output example

One dataset item per product. This one is from a real run on 24 Sep 2026, search "chocolate" with `includeDetails: true` (long description shortened):

```json
{
    "productId": "2351720",
    "name": "Dairy Milk Hazelnut Chocolate Block",
    "brand": "Cadbury",
    "fullName": "CADBURY FAMILY BLOCK CHOCOLATE:HAZELNUT:.:180 GRAM",
    "size": "180g",
    "url": "https://www.coles.com.au/product/cadbury-dairy-milk-hazelnut-chocolate-block-180g-2351720",
    "imageUrl": "https://cdn.productimages.coles.com.au/productimages/2/2351720.jpg",
    "price": 5.5,
    "wasPrice": 8,
    "saveAmount": 2.5,
    "discountPercent": 31,
    "unitPrice": 3.06,
    "unitPriceQuantity": 100,
    "unitPriceUnit": "g",
    "unitPriceText": "$3.06/ 100g",
    "isWeighted": false,
    "currency": "AUD",
    "isOnSpecial": true,
    "promotionTag": "special",
    "specialType": "price_drop",
    "offerDescription": "save $2.50",
    "multiBuy": null,
    "onlineOnlySpecial": false,
    "available": true,
    "availabilityType": "InStoreAndOnline",
    "availableQuantity": 877,
    "retailLimit": 20,
    "promotionalLimit": 12,
    "ageRestricted": false,
    "department": "Chips, Chocolates & Snacks",
    "category": "Chocolates",
    "aisle": "Chocolate Blocks",
    "categoryIds": { "department": "8916201", "category": "657635263", "aisle": "657635638" },
    "merchandiseHierarchy": { "tradeProfitCentre": "GROCERY", "categoryGroup": "IMPULSE", "category": "CONFECTIONERY", "subCategory": "BLOCK CHOCOLATE", "className": "EVERYDAY" },
    "storeAisle": null,
    "variationsCount": 27,
    "isSponsored": false,
    "source": "search",
    "sourceQuery": "chocolate",
    "page": 1,
    "positionOnPage": 10,
    "storeId": null,
    "scrapedAt": "2026-09-24T05:29:53.686Z",
    "barcode": "9300617064923",
    "longDescription": "There's a moment - right when your teeth break through smooth milk chocolate and hit that first whole hazelnut - ...",
    "ingredients": "Full Cream Milk, Sugar, Hazelnuts, Cocoa Butter, Cocoa Mass, Milk Solids, Emulsifiers (Soy Lecithin, 476), Flavours.",
    "allergens": "Contains Hazelnut, Milk, Soy\nMay Contain Peanut, Wheat",
    "storage": "Please store in cool, dry conditions.",
    "dimensions": "178.00 x 86.00 x 12.00 mm",
    "countryOfOrigin": "Australia",
    "countryOfOriginStatement": "Made in Australia from Imported and Local Ingredients",
    "nutrition": {
        "servingsPerPackage": 7.2,
        "servingSize": "25g",
        "perServing": { "Energy": "585 kJ", "Protein": "2.3 g", "Fat - Total": "9.1 g", "Fat - Saturated": "4.0 g", "Carbohydrate": "11.9 g", "Sugars - Total": "11.6 g", "Sodium": "17 mg" },
        "per100": { "Energy": "2340 kJ", "Protein": "9.0 g", "Fat - Total": "36.2 g", "Fat - Saturated": "16.1 g", "Carbohydrate": "47.7 g", "Sugars - Total": "46.4 g", "Sodium": "67 mg" }
    },
    "images": [
        "https://shop.coles.com.au/wcsstore/Coles-CAS/images/2/3/5/2351720.jpg",
        "https://shop.coles.com.au/wcsstore/Coles-CAS/images/2/3/5/2351720_L.jpg"
    ]
}
```

A half-price row from the specials list, with no details:

```json
{ "productId": "2993706", "fullName": "COCA-COLA ZERO SUGAR SOFT DRINK BOTTLE 1.25L", "price": 2.25, "wasPrice": 4.5, "saveAmount": 2.25, "discountPercent": 50, "unitPriceText": "$1.80/ 1L", "specialType": "half_price", "offerDescription": "save $2.25", "available": true, "availableQuantity": 4824, "promotionalLimit": 12, "department": "Drinks", "category": "Soft Drinks", "aisle": "Soft Drink Bottles", "source": "specials", "specialsFilter": "halfprice" }
```

The dataset comes with three ready-made views: **Overview**, **Specials** and **Barcode & nutrition**. A `RUN_SUMMARY` record in the key-value store lists, for each source, how many products were saved, how many Coles has in total, and why scraping stopped.

#### Field fill rates (real test runs)

| Field | Fill rate |
|---|---|
| productId, name, brand, size, url, image, price, category path, stock flag, limits | 100% |
| unitPrice / unitPriceText | 93-100% (a few items, such as gift cards, have no unit price) |
| wasPrice, saveAmount, specialType | 100% of products that are on special (78% of a specials run that included the full bakery range) |
| barcode | 92% with `includeDetails` (fresh produce and in-store bakery items have no barcode) |
| ingredients / long description / images / dimensions | 98-100% with `includeDetails` |
| nutrition | 95% of food products with `includeDetails` (non-food such as shampoo has none) |
| allergens | 99% of food products with `includeDetails` (non-food rarely declares any) |
| storeAisle | only with a `storeId` and only where Coles has shelf data |

Test runs: 2,100 products (full half-price list + full bakery department) in 53 s with 45 requests, 0 failed. 200 products with full details in 103 s, 265 requests, 0 failed.

### Pricing

**Pay per result: $1.50 per 1,000 results.** You pay per product saved to the dataset, with product details (barcode, ingredients, nutrition) included at no extra charge.

- 1,000 results = $1.50
- 10,000 results = $15.00

You're never charged for failed requests, blocked retries or duplicates. If you set a maximum cost per run, the scraper stops cleanly when it reaches it. Apify's free plan includes $5 of monthly platform credit, enough to try it out.

### Integrations

- **Make, Zapier and n8n**: trigger runs and pass Coles prices into any workflow.
- **Google Sheets**: send each run's dataset straight into a spreadsheet.
- **Apify API**: start runs and fetch results over REST, or with the JavaScript and Python API clients.
- **Webhooks**: get notified when a run finishes, then pull the new data.
- **MCP for AI agents**: the Apify MCP server (https://mcp.apify.com) lets Claude, ChatGPT, Cursor and other agents call this actor and read Coles prices directly.
- **Schedules**: run it every Wednesday morning (AEST) to capture each week's specials.

### Limits

- **Coles' own totals are approximate.** A category that says 550 products can return 549 or 551, because Coles re-ranks items between pages. Duplicates are removed, so every product appears once per source.
- **Prices are the online store's prices** unless you set `storeId`. In-store prices can differ by store. Set `storeId` to get a specific store's prices.
- **Sponsored products** are skipped by default. They're paid ads that also show up in the normal results.
- **Product details double the request count**, so leave `includeDetails` off when you only need prices.
- **Customer reviews** are not included in this version.

### FAQ

**Does it need a Coles account or a browser?**
No. It reads public product data over HTTPS, the same data the Coles website loads for any visitor.

**How do I find a store ID?**
Open the store on [coles.com.au/find-stores](https://www.coles.com.au/find-stores). The number at the end of the page URL is the store ID (`.../ardeer-7799` → `7799`). You can also paste the whole URL into `storeId`.

**How do I scrape the whole catalogue?**
Set `categories` to `["all"]` and `maxItems` to `0`. That's around 35,000 products across every department, and a product listed in two departments is saved only once.

**How often do specials change?**
Coles' weekly specials start on Wednesday. Schedule the actor for Wednesday morning (AEST) to capture each week's catalogue.

**Is it legal to scrape Coles?**
This actor collects only publicly available product and price information, not personal data. You're responsible for using the data in line with Coles' terms of service and privacy laws such as GDPR and the Australian Privacy Act. This isn't legal advice; if you're unsure, check with a lawyer.

**How does it avoid blocks?**
It sends normal browser-like requests through Australian residential proxies (the default). Each proxy session is reused for at most 25 requests and keeps its own cookies. When Coles returns an Imperva interstitial, a 401/403/429 or an empty page, that session is retired and the request is retried on a new session with a new IP, up to 8 retries by default (`maxRequestRetries`). Other errors are retried with an increasing back-off.

**What are the limits?**
`maxItems` caps each source (default 100, `0` = everything). Coles' own listing totals are approximate, the online store's prices are used unless you set `storeId`, and customer reviews aren't included. See the Limits section above. If you run it with no input at all, it does a demo search for "milk" (up to `maxItems` products) instead of failing.

### How it works (for developers)

coles.com.au is a Next.js site. Every search, category and product page has a matching `/_next/data/<buildId>/...json` route that returns the page's data as JSON (about 100 KB for 48 products). The scraper calls these routes directly. It caches the site's build ID between runs, and when Coles deploys a new build it picks up the new ID from an HTML page. If a product link is slightly off, it follows Coles' redirect to the canonical product path. Imperva interstitials are detected and retried on a new residential session with a new IP.

```bash
npm install
npm test                                   # parser tests on saved fixtures
APIFY_LOCAL_STORAGE_DIR=./storage node src/main.js   # input in storage/key_value_stores/default/INPUT.json
```

# Actor input Schema

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

Keywords typed into the Coles search box, one per line, e.g. "milk", "tim tam", "shampoo". A barcode (EAN/GTIN like 9300617064923) also works. Each keyword is scraped separately, up to "Max products per source".

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

Coles category slugs or browse URLs, e.g. "dairy-eggs-fridge", "pantry/breakfast", "meat-seafood" or "https://www.coles.com.au/browse/bakery". Use "all" for the whole online catalogue (every department, about 35,000 products, deduplicated). One department is roughly 1,000-4,000 products.

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

Scrape the Coles weekly specials catalogue (new specials start each Wednesday). "All specials" is every product on special, usually 6,000-8,000; or pick half price, multibuy or online-only specials.

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

Any coles.com.au search, category (/browse/...), specials (/on-special), brand or product (/product/...) URL. Filters in the URL are kept. Example: https://www.coles.com.au/on-special?filter\_Special=halfprice

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

Stop each keyword, category, specials list or URL after this many unique products. 0 = everything Coles lists. Example: 100 with two keywords returns up to 200 products.

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

Opens each product page to add the barcode (GTIN), ingredients, allergens, dietary info, nutrition panel, country of origin and all images. One extra request per product, so runs take about twice as long. Leave off if you only need prices.

## `storeId` (type: `string`):

Optional Coles store number for that store's prices, stock and shelf location, e.g. 7799, or paste a store page URL like https://www.coles.com.au/find-stores/coles/vic/ardeer-7799. Leave empty for Coles' default online store.

## `includeSponsored` (type: `boolean`):

Coles puts paid product ads at the top of search and specials pages. Off = organic results only. When on, sponsored rows are flagged isSponsored: true.

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

How many requests run in parallel (1-25). The default of 5 is fast and gentle; raise it for very large catalogue runs.

## `maxRequestRetries` (type: `integer`):

How many times a failed page is retried (0-20). Blocked requests are retried on a fresh proxy session with a new IP.

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

Coles sits behind Imperva bot protection. Australian residential proxies (the default) get through reliably; datacenter proxies are often blocked. Blocked requests are retried on a fresh session automatically. Leave as is unless you have a reason to change it.

## Actor input object example

```json
{
  "searchQueries": [
    "milk",
    "coffee"
  ],
  "specials": "none",
  "maxItems": 20,
  "includeDetails": false,
  "includeSponsored": false,
  "maxConcurrency": 5,
  "maxRequestRetries": 8,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AU"
  }
}
```

# Actor output Schema

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

All products found, with prices, specials, stock and category (plus barcode and nutrition when details are on).

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

Per-source counts, Coles totals and stop reasons.

# 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 = {
    "searchQueries": [
        "milk",
        "coffee"
    ],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "AU"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("rel8ble/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 = {
    "searchQueries": [
        "milk",
        "coffee",
    ],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "AU",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("rel8ble/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 '{
  "searchQueries": [
    "milk",
    "coffee"
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AU"
  }
}' |
apify call rel8ble/coles-scraper --silent --output-dataset

```

## MCP server setup

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