# Woolworths Price Tracker & Scraper - Specials & Unit Prices (`neverempty/woolworths-price-tracker`) Actor

Woolworths price tracker and scraper: get every product's price, was-price, savings, special tag, unit price, stock and Stockcode from any woolworths.com.au category URL. Monitor mode returns only price drops, new specials and new products since the last run. No API key or proxy needed.

- **URL**: https://apify.com/neverempty/woolworths-price-tracker.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (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 $1.10 / 1,000 product returneds

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

## Woolworths Price Tracker & Scraper - Specials & Unit Prices

**Woolworths price tracker and scraper for woolworths.com.au:** paste any Woolworths Australia category URL and get every product in it as a flat row with the price, the was-price, the savings, the special tag (Half Price, Special, % Off), the unit price, stock status, Stockcode and barcode. Turn on monitor mode and each later run returns **only the price drops, new specials and newly listed products** since the last run, with the previous price next to the new one, so you can run it on a schedule as a Woolworths specials and price-drop alert.

It works like a Woolworths product API without a key: you give it category pages (for example `https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk`), and it returns clean JSON, CSV or Excel from Apify.

### What you get for each product

- **Price, was-price and savings** exactly as Woolworths lists them, in AUD. The was-price is only filled when the product is cheaper than its usual price right now.
- **Specials**: `isOnSpecial`, `isHalfPrice` and Woolworths' own tag (`Half Price`, `Special`, `33% Off`), plus price-drop notes such as `Was $3.50 - 01/09/2026`.
- **Unit price** (`$2.30 / 1L`) as text and as a number with its unit, for comparing pack sizes.
- **Stock and availability**, pack size, brand, **Stockcode**, **barcode (GTIN)**, image URL and the product page URL.
- **Which category it came from** (path, name and Woolworths' category ID) and its A to Z position in that category.

### Monitor mode: only what changed

Turn on `onlyChanges` and schedule the Actor (daily, or every Wednesday when the weekly specials change):

- **First run**: records every product's price and special status as the starting point. Free, no product rows.
- **Later runs**: return only products whose **price dropped**, that **went on special**, or that are **newly listed** in the category. Each row has `changeTypes`, `previousPrice`, `previousIsOnSpecial` and `previousCheckedAt`.
- **Price increases** (for example when a special ends) are remembered quietly, or returned too if you turn on `includePriceIncreases`.
- A category with nothing to report costs one small `category-checked` event instead of a full product list.

Use `watchName` to keep separate memories for different schedules, and `onlySpecials` to get only products that are on special.

### Why this one

- **Complete category lists.** Products are read in Woolworths' A to Z order, 36 per page, which has no sponsored products mixed in and the same order on every run, so paging does not skip or repeat products. On 2026-10-12 the Milk category returned 282 of 282 products, twice, in the same order.
- **Prices checked against the product pages.** Six products were compared with their own product pages (structured data price, was-price and unit price): all six matched.
- **No wrong prices sold.** Out-of-stock products that Woolworths shows without a price come back as free rows (`no-price`), never with a guessed price. A category address that is not in Woolworths' own category list is not requested at all and comes back as a free row that lists the categories that do exist there.
- **One product, one charge per run.** A product that sits in two of your categories (for example Full Cream Milk and Milk Specials) is returned and charged once.

### Input

```json
{
  "categoryUrls": [
    "https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk/full-cream-milk",
    "https://www.woolworths.com.au/shop/browse/specials/half-price/dairy-eggs-fridge"
  ],
  "maxProductsPerCategory": 1000,
  "onlySpecials": false,
  "onlyChanges": false
}
```

| Field | What it does |
|---|---|
| `categoryUrls` | Woolworths category pages from the address bar, up to 100 per run. Top categories (`/shop/browse/pantry`), sub-categories (`/shop/browse/dairy-eggs-fridge/milk`) and specials pages (`/shop/browse/specials/half-price`) all work. Query parameters such as `?pageNumber=2` are ignored; the whole category is read. |
| `maxProductsPerCategory` | Products to read per category, A to Z (default 1,000, up to 20,000). |
| `onlySpecials` | Return only products on special. |
| `onlyChanges` | Monitor mode (see above). |
| `includePriceIncreases` | Monitor mode: also return price increases. |
| `watchName` | Monitor mode: a name for a separate memory. |
| `resetMonitoringState` | Monitor mode: forget this watch's memory and start again. |
| `requestTimeoutSecs` | Seconds to wait for one page of 36 products before asking again (default 30). |

### Output example

```json
{
  "status": "ok",
  "categoryPath": "/dairy-eggs-fridge/milk/full-cream-milk",
  "categoryName": "Full Cream Milk",
  "categoryId": "1_A8A8E7C",
  "rank": 9,
  "stockcode": "404980",
  "name": "Farmhouse Gold Full Cream Milk 750mL",
  "brand": "Farmhouse Gold",
  "packageSize": "750mL",
  "price": 3.4,
  "currency": "AUD",
  "wasPrice": 3.9,
  "savingsAmount": 0.5,
  "isOnSpecial": true,
  "isHalfPrice": false,
  "specialLabel": "Special",
  "unitPrice": 4.53,
  "unitPriceMeasure": "1L",
  "unitPriceText": "$4.53 / 1L",
  "inStock": true,
  "barcode": "9310036047308",
  "productUrl": "https://www.woolworths.com.au/shop/productdetails/404980/farmhouse-gold-full-cream-milk-milk"
}
```

| Column | Meaning |
|---|---|
| `price` | The current shelf price online, AUD. |
| `wasPrice`, `savingsAmount` | The usual price and the saving, only when the current price is lower. |
| `isOnSpecial`, `isHalfPrice`, `specialLabel` | Woolworths' special flags, and the tag on the product tile (for example `Half Price`, `33% Off`, `Lower shelf price`; a few tiles carry a non-price tag such as `Australian Grown`). |
| `priceNote` | Woolworths' price-drop note when it shows one (for example `Was $3.50 - 01/09/2026`). |
| `unitPrice`, `unitPriceMeasure`, `unitPriceText` | Price per unit (per 1L, per 1KG, per 100G...). |
| `inStock`, `isAvailable` | Stock status as Woolworths reports it online. |
| `isMarketplace`, `marketplaceSeller` | Everyday Market products sold by other sellers, and the seller's name. |
| `changeType`, `changeTypes`, `previousPrice`, `previousIsOnSpecial`, `previousCheckedAt` | Monitor mode only: `price-drop`, `new-special`, `new-product`, `price-increase`, and the values at the last check. |
| `status`, `note` | `ok` for a product row; other values are free rows that say why (below). |

#### Free rows (never charged)

| `status` | Meaning |
|---|---|
| `no-price` | Woolworths shows the product without a price right now (usually out of stock). |
| `category-not-found` | The address is not a category in Woolworths' category list. The note lists the categories that exist at that level. |
| `no-products` | Woolworths lists no products in that category right now. |
| `bad-input` | Not a Woolworths category URL (for example a search page or another shop). |
| `unreadable`, `blocked` | Woolworths did not answer with a usable list after 3 tries, or refused the request. Try again later. |
| `baseline-recorded`, `no-change`, `budget-reached` | Monitor mode and spending-limit notes. |

### Pricing

Pay per event: one `product-returned` event per product row with a price. In monitor mode, one `category-checked` event for a category with nothing new. The first monitor run, out-of-stock products and every note row are free. If you set a maximum charge for a run, the Actor stops before going over it and tells you so; in monitor mode, changes that did not fit are returned on the next run.

### Good to know

- Prices are Woolworths' online prices. In-store prices and prices at a specific store can differ.
- Search pages are not read: Woolworths' robots.txt disallows `/shop/search`, and this Actor follows robots.txt. Use category or specials pages.
- Products are returned in A to Z order. Woolworths' "Relevance" order mixes in sponsored products and changes between visits, so it is not used.
- Nutrition panels, ingredients and reviews are not included (they are on the product pages, not in the category list).
- Monitor mode compares the products it reads; with `maxProductsPerCategory` below the category size, the rest of the category is not watched.

### Use cases

- Weekly Woolworths specials lists for a deal site, newsletter or app.
- Price-drop alerts on the groceries you buy, delivered by Apify's schedule and integrations (email, Slack, Google Sheets).
- Price and unit-price comparisons between Woolworths and other Australian supermarkets.
- Tracking new products and range changes in a category.

### FAQ

**Is there a Woolworths API for prices?**
Woolworths does not offer a public price API. This Actor gives you the same data as JSON from category pages, with no key or login.

**How do I get only this week's Woolworths specials?**
Use a specials page such as `https://www.woolworths.com.au/shop/browse/specials/half-price`, or any category with `onlySpecials` on.

**How do I get alerted when a Woolworths price drops?**
Turn on `onlyChanges`, schedule the Actor daily, and connect the dataset to email or Slack. Only drops, new specials and new products come through.

**Can I track a whole department?**
Yes. Paste the department URL (for example `/shop/browse/pantry`) and raise `maxProductsPerCategory` above the department size.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

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

Woolworths Australia category pages, one per line (up to 100 per run), copied from the address bar, like https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk. Sub-categories and specials pages (for example /shop/browse/specials/half-price) work too. If you leave this empty, one example category (Full Cream Milk) is read.

## `maxProductsPerCategory` (type: `integer`):

How many products to read from each category, in A to Z order (1 to 20,000). Empty means 1,000. In monitor mode only this many products of each category are compared, so set it above the category size to watch the whole category.

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

Return only products that Woolworths marks as on special (a sale price, Half Price and similar). Off means every product with a price.

## `onlyChanges` (type: `boolean`):

Remembers the price and special status of every product in each category. The first run records the starting point (free, no product rows). Later runs with the same categories return only products whose price dropped, that went on special, or that are newly listed, with the previous price. A category with nothing to report costs one small category-checked event.

## `includePriceIncreases` (type: `boolean`):

Monitor mode only: also return products whose price went up (for example when a special ends). Off means price increases are remembered quietly and not returned.

## `watchName` (type: `string`):

Keeps a separate memory per name, so two schedules with different category lists do not mix. Letters, digits, dot, dash and underscore, up to 40 characters. Empty means the watch called default.

## `resetMonitoringState` (type: `boolean`):

Monitor mode only: forget the remembered prices of this watch name first, so this run records a new starting point.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one page of 36 products before asking again (5 to 60).

## Actor input object example

```json
{
  "categoryUrls": [
    "https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk/full-cream-milk"
  ],
  "onlySpecials": false,
  "onlyChanges": false,
  "includePriceIncreases": false,
  "resetMonitoringState": false,
  "requestTimeoutSecs": 30
}
```

# Actor output Schema

## `results` (type: `string`):

One row per Woolworths product: Stockcode, name, brand, pack size, price, was-price, savings, special tag, unit price, stock, barcode, image and product URL, with the category it came from. In monitor mode only price drops, new specials and new products, with the previous price. A category that cannot be read comes back as a free row that says why.

# 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 = {
    "categoryUrls": [
        "https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk/full-cream-milk"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/woolworths-price-tracker").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 = { "categoryUrls": ["https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk/full-cream-milk"] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/woolworths-price-tracker").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 '{
  "categoryUrls": [
    "https://www.woolworths.com.au/shop/browse/dairy-eggs-fridge/milk/full-cream-milk"
  ]
}' |
apify call neverempty/woolworths-price-tracker --silent --output-dataset

```

## MCP server setup

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

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/WCXRjIOgfuO5mpndc/builds/A92SsfOHLUWgBnGwT/openapi.json
