# Woolworths Australia Scraper: Products, Prices & Specials (`atalaia/woolworths-au`) Actor

Scrape Woolworths Australia (woolworths.com.au) products by search term, category or specials page (half price, multibuy) or product URL: price, was-price, special flag, unit price, availability, barcode, and optional product details (ingredients, nutrition, images). Pay per product.

- **URL**: https://apify.com/atalaia/woolworths-au.md
- **Developed by:** [Atalaia](https://apify.com/atalaia) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

### What does Woolworths Australia Scraper do?

**Woolworths Australia Scraper** extracts products, prices and specials from [Woolworths](https://www.woolworths.com.au) (woolworths.com.au), Australia's largest supermarket. Give it **search terms**, **category or specials pages** (half price, multibuy, "prices dropped"...) or **product URLs**, and get one clean dataset item per product: current price, was-price, whether it is really on special, promotion text ("2 for $5.00"), unit price, availability, barcode, category and, optionally, full product details (ingredients, nutrition, allergens, images).

- ✅ Search, aisles, specials pages and product pages, with automatic **pagination** up to 2,000 products per search or category
- ✅ A reliable **`isOnSpecial`** flag: only real discounts (special price or multibuy), never "everyday low price" badges
- ✅ **Unit price** as a number plus the raw text ("$2.25 / 1L"), so you can compare products of different sizes
- ✅ Optional **product details**: description, ingredients, nutrition panel, allergens, country of origin, up to 10 images
- ✅ Works on Apify's standard datacenter proxy, no login and no account needed
- ✅ **You only pay for products returned.** Failed inputs are reported as error items and never charged

### Why scrape Woolworths?

- **Price monitoring:** track the price of your basket or your brand every day and get alerted when it changes.
- **Specials tracking:** pull the full half-price and multibuy catalogue each week.
- **Basket comparison:** compare Woolworths with other supermarkets product by product, using barcodes and unit prices.
- **Market research:** see which brands are on promotion, how deep the discounts are and how ranges change.

Running on the Apify platform gives you an **API**, **scheduling** (for example every morning), webhooks, and integrations with Google Sheets, Make, Zapier, n8n and more.

### What data does it extract?

| Field | Type | Description |
|---|---|---|
| `productId` | string | Woolworths stockcode |
| `name`, `brand`, `size` | string | Product name (with size), brand and package size |
| `url`, `image` | string | Product page and main image |
| `price` | number | Current price in AUD (`null` when the product is unavailable) |
| `wasPrice` | number | Previous price when the product is on special |
| `isOnSpecial` | boolean | `true` only for a real discount: special price or multibuy offer |
| `isHalfPrice` | boolean | Woolworths' half-price flag |
| `promotionType`, `promotionText` | string | Raw promotion label (`Special`, `Multibuy`, `LowerShelfPrice`...) and its text (`Save $0.55`, `2 for $5.00`) |
| `unitPrice`, `unitPriceText` | number, string | Comparable unit price and its raw text (`$2.25 / 1L`) |
| `isAvailable` | boolean | Available to buy online |
| `category` | string | Department > category > subcategory |
| `barcode` | string | EAN/GTIN barcode |
| `position` | integer | Rank in the search or category listing |
| `isSponsored` | boolean | Promoted placement in the listing |
| `source`, `query` / `categoryUrl` / `productUrl` | string | Which input returned the item |
| `storeContext` | string | Store the price is for: `default`, or `postcode 3000 / store 3813 ...` with **Postcode** |
| `description`, `ingredients`, `nutrition`, `allergens`, `allergensMayBePresent`, `countryOfOrigin`, `images` | | Product details (with **Include product details** or **Product URLs**) |
| `scrapedAt` | string | ISO 8601 timestamp |

### How to scrape Woolworths

1. Click **Try for free** and open the **Input** tab.
2. Add **Search terms** (`milk`, `tim tam`...), **Category or specials URLs** copied from woolworths.com.au, and/or **Product URLs or IDs**.
3. Set **Max items per search or category** (default 100, up to 2,000).
4. Optionally turn on **Include product details** or **Only specials**.
5. Click **Start**, then download the results as JSON, CSV, Excel or HTML, or read them through the API.

### How much does it cost?

This Actor uses **pay-per-event** pricing. Platform usage (compute and proxy) is included.

| Event | Price | When |
|---|---|---|
| `apify-actor-start` | **US$ 0.004** | Once per run (default 512 MB memory) |
| `product` | **US$ 0.0006** | Per product returned from a search or category listing |
| `product-detail` | **US$ 0.0005** | Per product page read: each product from **Product URLs**, or each listed product when **Include product details** is on |

Examples:

- 1,000 products from searches or specials pages: **US$ 0.604** (start + 1,000 × 0.0006).
- The same 1,000 products with details: **US$ 1.104** (adds 1,000 × 0.0005).
- 50 product URLs: **US$ 0.029** (start + 50 × 0.0005).

Error items (invalid input, unknown category, removed product, blocked request) are **never charged**. You can cap the spend of a run with **Maximum cost per run**: the Actor stops cleanly and marks the inputs it didn't reach as `not_processed` (free).

### Input

See the **Input** tab for every option. Example:

```json
{
    "searchQueries": ["milk", "tim tam", "a2 milk 2l"],
    "categoryUrls": [
        "https://www.woolworths.com.au/shop/browse/specials/half-price",
        "https://www.woolworths.com.au/shop/browse/fruit-veg/fruit"
    ],
    "productUrls": ["https://www.woolworths.com.au/shop/productdetails/44528/norco-full-cream-milk", "888137"],
    "maxItemsPerQuery": 200,
    "includeDetails": false,
    "onlySpecials": false,
    "postcode": "3000"
}
```

- **Postcode** (optional): prices, specials and availability for the Woolworths store nearest to that postcode (for example `2000` Sydney, `3000` Melbourne, `6000` Perth) instead of the default online store. `storeContext` says which store was used, e.g. `postcode 3000 / store 3813 Melbourne Square (Southbank)`. Woolworths only gives a store's own prices on the product page, so with a postcode every listed product is read with its details and charged as `product` + `product-detail` (like **Include product details**). Store prices really differ: in our test, all 10 products checked had a different price or availability between Sydney (2000), Melbourne (3000) and Perth (6000), mostly fresh produce, meat, eggs, dairy and liquor. A postcode Woolworths doesn't know stops the run with an `invalid_postcode` error item (free).

- **Category URLs** can be any `/shop/browse/...` page: an aisle (`/shop/browse/pantry`), a sub-aisle, or a specials page (`/shop/browse/specials/half-price`, `/shop/browse/specials/buy-more-save-more`...). A group page with no products of its own, such as `/shop/browse/specials`, is expanded into its sub-pages. Search-result URLs (`/shop/search/products?searchTerm=...`) work too.

- **Only specials** keeps products with `isOnSpecial = true`. For searches it also asks Woolworths for its "specials" filter, so you get more specials per page.

- Products are **de-duplicated** by `productId` within each search or category.

### Output

Example item from a search (`includeDetails` off):

```json
{
    "store": "woolworths",
    "source": "search",
    "query": "milk",
    "position": 3,
    "productId": "44528",
    "name": "Norco Full Cream Milk 2L",
    "brand": "Norco",
    "size": "2L",
    "url": "https://www.woolworths.com.au/shop/productdetails/44528/norco-full-cream-milk",
    "image": "https://cdn1.woolworths.media/content/wowproductimages/large/044528.jpg",
    "price": 4.5,
    "wasPrice": 5.05,
    "isOnSpecial": true,
    "isHalfPrice": false,
    "promotionType": "Special",
    "promotionText": "Save $0.55",
    "unitPrice": 2.25,
    "unitPriceText": "$2.25 / 1L",
    "isAvailable": true,
    "isSponsored": false,
    "category": "Dairy, Eggs & Fridge > Milk > Full Cream Milk",
    "barcode": "9322042000048",
    "storeContext": "default",
    "scrapedAt": "2026-09-29T12:00:00.000Z"
}
```

With **Include product details** (and for every **Product URL**) the item also has:

```json
{
    "description": "Our full cream milk is still brought to you by a co-operative of passionate Norco dairy farmers...",
    "ingredients": "Pasteurised homogenised Milk",
    "nutrition": {
        "Energy": { "perServing": "678.0kJ", "per100": "271.0kJ" },
        "Protein": { "perServing": "8.2g", "per100": "3.3g" },
        "servingSize": "250.0 ML",
        "servingsPerPack": "8.0"
    },
    "allergens": "Egg Free,Fish Free,Gluten Free,Soy Free,Wheat Free",
    "allergensMayBePresent": null,
    "countryOfOrigin": "Australian Milk",
    "images": ["https://cdn0.woolworths.media/content/wowproductimages/large/044528.jpg", "https://cdn0.woolworths.media/content/wowproductimages/large/044528_2.jpg"]
}
```

An input that failed (never charged):

```json
{
    "store": "woolworths",
    "source": "product",
    "productUrl": "999999999",
    "productId": "999999999",
    "error": "not_found",
    "errorMessage": "Woolworths has no product with this ID (removed or never existed).",
    "scrapedAt": "2026-09-29T12:00:00.000Z"
}
```

Error codes: `invalid_input`, `category_not_found`, `not_found`, `blocked` (Woolworths refused every retry), `http_<status>`, `not_processed` (maximum cost per run reached). The dataset has three views: **Products**, **Product details** and **Errors (not charged)**. Run statistics are saved in the `RUN_STATS` key-value record.

### Tips

- Batch many searches and categories in one run: the start fee is charged once per run.
- For a weekly specials feed, schedule the half-price and buy-more-save-more pages with **Max items** at 2,000.
- Use `barcode` to match products across supermarkets, and `unitPrice` to compare different pack sizes.
- A search that returns nothing (for example a misspelling) is not an error: it simply yields no items.
- The run fails only when **every** input failed; otherwise failures are listed as error items.

### Limits

- **Without a postcode, prices are for Woolworths' default online store** (`storeContext: "default"`), as seen by an anonymous visitor. With **Postcode**, they are for the nearest store Woolworths lists for that postcode (its store finder for Pick up / Direct to boot), not for a delivery address.
- With **Postcode**, a product the chosen store doesn't sell comes back with `price: null` and `isAvailable: false`. If its product page can't be read, the item keeps the default-store values, with `storeContext: "default"` and a `detailError`.
- An **unavailable** product may come without a price (`price: null`, `isAvailable: false`). That is what the site shows, not an error.
- Up to **2,000 products per search or category** (Woolworths lists 36 per page).
- Member-only prices and personalised offers ("My specials", Everyday Rewards boosts) need an account and are not collected. The Actor never logs in.
- Some fields exist only for some products: `unitPrice` is missing for items sold "each" without a comparable measure, and details such as nutrition or country of origin depend on what Woolworths publishes.

### FAQ and support

**Is it legal to scrape Woolworths?** This Actor only reads publicly available product and price information that any visitor can see, without logging in. It does not collect personal data. You should still check that your use complies with the site's terms and the laws that apply to you, and consult a lawyer if you are unsure.

**Can I use it from code or an AI agent?** Yes: see the **API** tab for ready-made calls (HTTP, JavaScript, Python, CLI), or use it through the Apify MCP server.

Found a problem or need a feature? Open an issue in the **Issues** tab.

# Actor input Schema

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

Text searches, exactly as you would type them on woolworths.com.au (for example milk, tim tam, a2 milk 2l). Each term returns up to "Max items per search or category" products.

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

Listing pages from woolworths.com.au: aisles (https://www.woolworths.com.au/shop/browse/fruit-veg), specials pages (https://www.woolworths.com.au/shop/browse/specials/half-price) or search-result URLs. All pages are followed up to the item cap.

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

Product pages (https://www.woolworths.com.au/shop/productdetails/44528/...) or bare numeric product IDs (stockcodes). Each one returns the full product with details.

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

Maximum number of products returned for each search term or category URL (36 products per page are read until this cap or the end of the listing).

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

Also open each listed product's page to add description, ingredients, nutrition, allergens, country of origin and up to 10 images. Charged as an extra product-detail event per product and makes runs slower.

## `onlySpecials` (type: `boolean`):

Keep only products that are really discounted: on special (with a was-price) or on a multibuy offer such as "2 for $5". Badges like "Lower shelf price" or "Everyday low price" alone don't count.

## `postcode` (type: `string`):

4-digit Australian postcode (for example 3000). Prices, specials and availability are then read for the nearest Woolworths store to that postcode instead of the default online store. This opens each product's page (charged as product details, like "Include product details") because Woolworths only gives store prices there. Leave empty for the default online store. An unknown postcode stops the run with an error.

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

Apify datacenter proxy is used by default and is enough for Woolworths.

## Actor input object example

```json
{
  "searchQueries": [
    "milk",
    "tim tam",
    "olive oil"
  ],
  "categoryUrls": [
    "https://www.woolworths.com.au/shop/browse/specials/half-price",
    "https://www.woolworths.com.au/shop/browse/fruit-veg/fruit"
  ],
  "productUrls": [
    "https://www.woolworths.com.au/shop/productdetails/44528/norco-full-cream-milk",
    "888137"
  ],
  "maxItemsPerQuery": 100,
  "includeDetails": false,
  "onlySpecials": false,
  "postcode": "3000",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

## `details` (type: `string`):

No description

## `errors` (type: `string`):

No description

## `runStats` (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 = {
    "searchQueries": [
        "milk",
        "tim tam"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("atalaia/woolworths-au").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",
        "tim tam",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("atalaia/woolworths-au").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",
    "tim tam"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call atalaia/woolworths-au --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,atalaia/woolworths-au"
        }
    }
}
```

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/AccjnfnS6CU1x4mbV/builds/SXEeOx38mD06YyC68/openapi.json
