# Woolworths Australia Scraper - Prices, Specials and Barcodes (`s-r/woolworths-scraper`) Actor

Scrape the Woolworths Australia grocery catalogue: product name, brand, AUD price, was-price, unit price, specials, barcode/GTIN, pack size, stock and image. Search by keyword, look products up by barcode, browse a department or read full detail by stockcode.

- **URL**: https://apify.com/s-r/woolworths-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** E-commerce, Business
- **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

## Woolworths Australia Scraper

Pull the Woolworths Australia grocery catalogue as clean rows: **product name,
brand, AUD price, was-price, unit price, specials, barcode, pack size, stock
status and image**. Search by keyword, look products up **by barcode or GTIN**,
browse a whole department, or read the full record including ingredients,
nutrition panel and Health Star Rating.

```json
{
  "operation": "search",
  "queries": ["tim tam", "vegemite"],
  "specials_only": true,
  "sort_by": "unit_price_asc",
  "max_results": 200
}
```

### Four things it does

| Operation | What you give it | What you get |
|---|---|---|
| **Search by keyword** | Search terms | The catalogue rows for each term, tagged with the term that found them |
| **Look up by barcode** | EAN-13, UPC-A or GTIN codes | The Woolworths product for each code, or an explicit "not stocked" |
| **Product detail** | Woolworths stockcodes | The full record: ingredients, nutrition, allergens, Health Star Rating, storage |
| **Browse a category** | A department name such as `fruit-veg` | Everything Woolworths lists in it |

### Barcode lookup, and the trap it avoids

This is the part worth reading before you use anything else for it.

Woolworths takes a barcode as a search term. For a barcode it stocks, you get
exactly one product:

- `9310072000282` → Arnott's Tim Tam Original Chocolate Biscuits 200g, $6.00

For a barcode it does **not** stock, it does not tell you. It quietly falls back
to listing the catalogue, over a million rows headed by "Cavendish Bananas
each". Both answers are an HTTP 200 carrying real products. Anything that reads
the first row hands you a banana for a German chocolate GTIN and gives you no
reason to doubt it.

So every lookup here is settled by **comparing the code you asked for against
the barcode on the rows that came back**. A code with no exact match comes back
as `found: false` with `not_found_reason`, never as a substitute product. Short
codes are left-padded, so a 12-digit US UPC matches the 13-digit GTIN
Woolworths actually stores.

The same care applies to stockcodes. A stockcode that never existed returns a
404, but a **withdrawn** one returns HTTP 200 with a blank name and a price of
zero. That comes back as `found: false` too, rather than as a real product that
costs nothing.

### What comes back

| Field | What it is |
|---|---|
| `stockcode` | Woolworths' own product id. Stable, use it to join runs |
| `name`, `brand`, `package_size`, `unit` | The product |
| `barcode`, `gtin_format` | Its barcode, normally a 13-digit GTIN |
| `price`, `currency` | Current price in AUD |
| `was_price`, `savings_amount`, `is_on_special`, `is_half_price` | The promotion |
| `unit_price`, `unit_measure`, `unit_price_label` | Price per 100g, per litre and so on |
| `is_available`, `is_in_stock`, `supply_limit` | Whether you can actually buy it |
| `is_sponsored` | True when Woolworths served the row as a paid placement |
| `image`, `url`, `description` | |
| `query`, `position` | Which term found it, and where it ranked |

With **Add ingredients and nutrition** turned on, each row also gets
`ingredients`, `nutrition`, `country_of_origin`, `health_star_rating`,
`allergen_statement`, `allergens_may_be_present`, `contains_gluten`,
`contains_nuts`, `department`, `aisle`, `storage_instructions` and
`product_warnings`. That costs one extra request per product.

A note on **`price`**: Woolworths sends `0` both for "this product has no price
right now" and for a withdrawn record. A zero in a price column quietly wins
every cheapest-supplier comparison built on top of it, so those come back
`null`.

A note on **`was_price`**: Woolworths fills it with the current price when
nothing is on promotion. Passed through, that invites you to compute a 0%
saving on a product that was never discounted, so it is dropped unless it is
actually higher than the price.

### The filters, and what we saw them do

- **On special only** — Woolworths applies it. On "chocolate", 36 of 44 rows
  came back on special with it on, against 14 of 44 with it off.
- **Sort order** — Woolworths applies it, and it visibly holds: the same search
  opened at $0.89 sorted price-ascending and at $1,298.99 price-descending.
  Unit-price sorting is the interesting one for grocery, because it is the only
  honest way to compare a 165g pack against a 200g one.

### Volume

A catalogue page carries about 36 products, and paging is real: pages 1, 2, 5
and 10 of the same search all returned different products, out of 2,206
available for "milk". `max_results` is the total across all your terms and
`max_pages` caps how deep each term goes.

### Not included

There is **no store locator**. Woolworths' store endpoint answered with an
error on every shape tried, so there is no per-store pricing or stock here.
Prices are the national online ones.

### Free plan

Free Apify accounts get 10 results per run. Paid plans are uncapped. On a free
run the Actor stops after one page instead of paging through a catalogue it is
not allowed to deliver.

# Actor input Schema

## `operation` (type: `string`):

Search the catalogue by keyword, look products up by barcode or GTIN, read full detail for known stockcodes, or browse a whole department.

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

What to search the Woolworths catalogue for, one term per line. Used when the operation is 'Search by keyword'. Every term is run separately and each row records which term found it.

## `barcodes` (type: `array`):

EAN-13, UPC-A or GTIN codes to look up, one per line. Short codes are left-padded with zeros so a 12-digit UPC matches its 13-digit GTIN. A code Woolworths does not stock comes back as a row with found set to false, never as a substitute product.

## `stockcodes` (type: `array`):

Woolworths' own product ids, one per line, for the 'Product detail by stockcode' operation. A stockcode that no longer resolves comes back with found set to false rather than as a nameless product priced at zero.

## `category` (type: `string`):

A Woolworths department to browse, by its web name, for example fruit-veg, bakery, meat-seafood-deli or freezer. A name that does not match returns the list of ones that do.

## `specials_only` (type: `boolean`):

Returns only products currently on promotion. Woolworths applies this itself: with it on, 36 of 44 rows for 'chocolate' were on special, against 14 of 44 with it off.

## `sort_by` (type: `string`):

How Woolworths orders the results. Price and unit-price orders are applied by Woolworths and visibly hold: price ascending opened at $0.89 and descending at $1,298.99 on the same search.

## `with_details` (type: `boolean`):

Opens each product's own record for ingredients, nutrition panel and country of origin. One extra request per product, so runs are much slower and cost more.

## `max_results` (type: `integer`):

How many products to return in total. A catalogue page carries about 36.

## `max_pages` (type: `integer`):

Stops each search term or category after this many pages even if the product target has not been reached.

## `retries` (type: `integer`):

How many times to re-ask for a page that did not come back. Woolworths is steady, so the default is enough for almost every run.

## Actor input object example

```json
{
  "operation": "search",
  "queries": [
    "tim tam"
  ],
  "barcodes": [
    "9310072000282"
  ],
  "stockcodes": [
    "36066"
  ],
  "category": "fruit-veg",
  "specials_only": false,
  "sort_by": "relevance",
  "with_details": false,
  "max_results": 100,
  "max_pages": 20,
  "retries": 3
}
```

# Actor output Schema

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

One row per Woolworths product.

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

Counts, price and barcode coverage, how many lookups resolved and how many did not.

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

Failures with a code and a redacted message.

# 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 = {
    "operation": "search",
    "queries": [
        "tim tam"
    ],
    "barcodes": [
        "9310072000282"
    ],
    "stockcodes": [
        "36066"
    ],
    "specials_only": false,
    "sort_by": "relevance",
    "with_details": false,
    "max_results": 100,
    "max_pages": 20,
    "retries": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/woolworths-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 = {
    "operation": "search",
    "queries": ["tim tam"],
    "barcodes": ["9310072000282"],
    "stockcodes": ["36066"],
    "specials_only": False,
    "sort_by": "relevance",
    "with_details": False,
    "max_results": 100,
    "max_pages": 20,
    "retries": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/woolworths-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 '{
  "operation": "search",
  "queries": [
    "tim tam"
  ],
  "barcodes": [
    "9310072000282"
  ],
  "stockcodes": [
    "36066"
  ],
  "specials_only": false,
  "sort_by": "relevance",
  "with_details": false,
  "max_results": 100,
  "max_pages": 20,
  "retries": 3
}' |
apify call s-r/woolworths-scraper --silent --output-dataset

```

## MCP server setup

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