# Woolworths Price Scraper & Monitor (Australia) (`mouadapi/woolworths-price-monitor`) Actor

Woolworths scraper and price tracker for Australia: prices, specials, was-prices, unit prices and stock by product URL or code, or a catalog export, never charged for failed results, unpriced products or unchanged products left out. Unofficial.

- **URL**: https://apify.com/mouadapi/woolworths-price-monitor.md
- **Developed by:** [COMPASS DEV](https://apify.com/mouadapi) (community)
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 product with a prices

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 scraper and price tracker** for Australia that returns **Woolworths prices**, **specials**, was-prices,
unit prices and stock for any **Woolworths product** (by product URL or code, or a catalog export from the Woolworths
sitemap) as flat rows — and never charges for failed results, for products without a price, or for unchanged products
left out with `onlyChangedProducts`.

*Unofficial. Not affiliated with Woolworths Group.*

It also **tracks price changes between runs** (previous price, price change, new and changed flags) and can
return **specials only**. Results come as clean, flat rows that export well to CSV, Excel, JSON or Google Sheets.

**You pay only for products with a price. Failed and blocked products are free.**

### Quick start

1. Click **Start**. The form is already filled in with two products (a product URL and a product code both work):

   ```json
   {
     "products": [
       "https://www.woolworths.com.au/shop/productdetails/888140/woolworths-full-cream-milk",
       "83273"
     ]
   }
   ```

   The other fields keep their defaults (products mode, datacenter proxy).
2. In under a minute you get two rows with the current price, was-price, special flag and unit price (see
   [Output example](#output-example)). On the Free plan this run costs $0.003 plus Apify's Actor-start charge.
3. Replace them with your own products: the code is the number in the product page's address
   (`…/productdetails/888140/…` → `888140`). Export the results as CSV, Excel or JSON from the **Output** tab.

### Use cases

- **Weekly shopping-list price tracker.** Put the products you buy in a list with a `stateName`, schedule the run
  daily or weekly, and see each product's `priceChange` since the last run.
- **Specials finder.** Add `"onlySpecials": true` to your product list to see which of your products are on special
  today, with the was-price next to the current price.
- **Brand and retail price research.** Brands and analysts monitor the online shelf price, special and availability
  of a set of products at Woolworths, or export priced products from the catalog for price research.

### What it does

| Mode | Use it for | Input |
|---|---|---|
| **Products** (default) | Daily/weekly **price monitoring** of specific products | Product URLs or product codes |
| **Catalog** | **Catalog export**: priced products from the official Woolworths sitemap (~42,000 listed) | How many priced products (`maxItems`) |

For every product you get: name, brand, size, price, was-price, special flag, unit price (e.g. $1.65 per 1L),
availability, category, product URL and image URL. With a tracking name (`stateName`) you also get the
previous run's values and what changed (see [Track price changes](#track-price-changes)).

### Input examples

Monitor three products (URLs or codes both work):

```json
{
  "products": [
    "https://www.woolworths.com.au/shop/productdetails/888140/woolworths-full-cream-milk",
    "83273",
    "208064"
  ]
}
```

Export 500 priced products from the catalog:

```json
{ "mode": "catalog", "maxItems": 500 }
```

Use Australian residential IPs instead of the default datacenter proxy (adds a surcharge, see Pricing):

```json
{ "products": ["888140"], "proxy": "residentialAU" }
```

Daily price watch that returns only what changed (see [Track price changes](#track-price-changes)):

```json
{ "products": ["888140", "83273", "208064"], "stateName": "my-weekly-basket", "onlyChangedProducts": true }
```

The first 50 products on special from the catalog:

```json
{ "mode": "catalog", "maxItems": 50, "onlySpecials": true }
```

| Field | Default | Description |
|---|---|---|
| `mode` | `products` | `products` or `catalog` |
| `products` | — | Product URLs or codes (products mode). Duplicates are checked once. |
| `maxItems` | `100` | Catalog mode: how many **priced** products to deliver (max 50,000), in sitemap order |
| `includeUnpricedProducts` | `false` | Catalog mode: also output products without a price at the default online store (free) |
| `stateName` | — | Optional tracking name. Remembers each product's values between runs and fills the change fields. |
| `onlyChangedProducts` | `false` | Needs `stateName`. Output only products that changed since the last run, or are new. Others are not output and not charged. |
| `onlySpecials` | `false` | Output only products on special. Others are not output and not charged. In catalog mode, `maxItems` counts specials delivered. |
| `proxy` | `datacenter` | `datacenter` (cheapest, works for Woolworths) or `residentialAU` |
| `maxConcurrency` | `5` | Parallel sessions (1–10). Each makes at most 1 request per second. |

**Field names from other tools:** `productId`, `itemId`, `url`, `urls`, `startUrls`, `productIds` (→ `products`);
`maxResults`, `limit` (→ `maxItems`); the standard `proxyConfiguration` (→ `proxy`; the `RESIDENTIAL` group →
`residentialAU`).

### Output example

```json
{
  "status": "ok",
  "error": null,
  "attempts": 1,
  "input": "83273",
  "productCode": "83273",
  "name": "Sanitarium So Good Barista Oat Long Life Milk 1L",
  "brand": "Sanitarium",
  "size": "1L",
  "price": 3,
  "wasPrice": 4,
  "isOnSpecial": true,
  "unitPrice": 3,
  "unitPriceUnit": "1L",
  "currency": "AUD",
  "availabilityStatus": "available",
  "category": "Milk > Long Life Milk",
  "url": "https://www.woolworths.com.au/shop/productdetails/83273/sanitarium-so-good-barista-oat-long-life-milk",
  "imageUrl": "https://cdn0.woolworths.media/content/wowproductimages/large/083273.jpg",
  "store": "default online store",
  "previousPrice": null,
  "previousWasPrice": null,
  "previousIsOnSpecial": null,
  "previousAvailabilityStatus": null,
  "priceChange": null,
  "changedSinceLastRun": null,
  "firstSeen": null,
  "scrapedAt": "2026-09-28T08:53:01.329Z"
}
```

The change fields stay `null` unless you set a tracking name (`stateName`).

#### Status of each row

**Products mode:** every product you list gets exactly one row. **Catalog mode:** products without a price
are skipped, unless `includeUnpricedProducts` is `true`; failed products are always shown. **Filters:** with
`onlySpecials` or `onlyChangedProducts`, products the filter leaves out are not output and not charged;
failed products are still shown (free), so you always see errors.

| `status` | Meaning | Charged? |
|---|---|---|
| `ok` | Product found. See `availabilityStatus`. | Yes, only when the row has a price |
| `no_data` | Product discontinued or removed from the catalog | **No** |
| `failed` | We couldn't get an answer (see `error` and `attempts`), the input wasn't a Woolworths product, or the run stopped after a block | **No** |

`availabilityStatus` on `ok` rows:

| Value | Meaning | Price | Charged? |
|---|---|---|---|
| `available` | Sold and available online | Current price | Yes |
| `unavailable_online` | Sold, but currently unavailable online (e.g. out of stock) | Current shelf price is kept | Yes |
| `not_ranged` | Listed by Woolworths but not sold at the default online store | `null` (never 0) | **No** |

### Pricing

Pay per product **with a price**. Price depends on your Apify plan:

| Apify plan | Per product | Per 1,000 products |
|---|---|---|
| Free | $0.0015 | $1.50 |
| Bronze | $0.0010 | $1.00 |
| Silver | $0.0009 | $0.90 |
| Gold | $0.0008 | $0.80 |
| Platinum | $0.0007 | $0.70 |
| Diamond | $0.0006 | $0.60 |

- **Residential surcharge:** +$0.0020 per product with a price, only when you choose `"proxy": "residentialAU"`.
- Apify's standard Actor-start charge applies per run.
- **Never charged for failed results:** `failed` and `no_data` rows and products without a price are free.
  Products left out by `onlySpecials` or `onlyChangedProducts` are not output and not charged.
- **Your maximum charge is respected:** the Actor never charges past your run's spending limit. When the
  limit is reached it stops; products it could not pay for are not returned.

### Track price changes

Give your list a **tracking name** and run it on a schedule. Each run compares every product with the
previous run of the same tracking name:

1. Create a task with your products and a `stateName`, for example
   `{"products": ["888140", "83273"], "stateName": "my-weekly-basket"}`.
2. Add an Apify schedule (for example, every morning at 7:00 Sydney time).
3. Every row now includes `previousPrice`, `previousWasPrice`, `previousIsOnSpecial`,
   `previousAvailabilityStatus`, `priceChange` (current − previous, in AUD), `changedSinceLastRun` and
   `firstSeen`.
4. Want only the news? Add `"onlyChangedProducts": true`. Unchanged products are then not output and not
   charged, so a quiet day costs almost nothing.

What counts as a change: price, was-price, special flag or availability. A product you tracked that
disappears from Woolworths shows up once as `no_data` with `changedSinceLastRun: true` (free).

How it works: the last seen values are kept in a named key-value store in **your own Apify account**
(`woolworths-monitor-<tracking name>`). Use a different tracking name for each list. Only products actually
checked in a run are updated. If a run stops early (for example after a block), the other products keep their
last known values, so nothing is lost. On the first run every product has `firstSeen: true` and the
`previous…` fields are `null`.

### Use it from AI agents

- Works through **Apify's MCP server**, so AI assistants (Claude, ChatGPT and other MCP clients) can find and
  call it.
- Smallest input: `{"products": ["888140"]}` — every other field has a sensible default.
- One flat row per product, with an explicit `status` and `availabilityStatus`, easy for an agent to read.
- For a recurring watch, pass a `stateName` and `"onlyChangedProducts": true`: the agent then gets only what
  changed, with `priceChange` and the previous values in the same row.
- Small lists are fast: in our tests a 1-product run took about 3–5 seconds and a 5-product run about
  10 seconds (default settings, datacenter proxy).
- **x402 payments:** the Actor is pay-per-event only, with no usage fees, limited permissions and no Standby mode, so
  agents can pay per product with x402.

Copy-paste call (your Apify token in `APIFY_TOKEN`):

```bash
curl -s -X POST "https://api.apify.com/v2/acts/mouadapi~woolworths-price-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"products": ["888140"]}'
```

The same call from JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('mouadapi/woolworths-price-monitor').call({ products: ['888140'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Limits (by design)

- **Default online store only.** Prices and availability are those Woolworths shows for its default online
  store. Store or postcode selection is not supported, so regional prices may differ.
- **No keyword search or category browsing.** Woolworths asks crawlers not to crawl its search pages
  (robots.txt), and we respect that. Use product URLs/codes, or catalog mode.
- **Catalog mode follows sitemap order** (roughly alphabetical by product name), not category order. About a
  third of catalog products have no price at the default online store. They are skipped by default and
  never charged.
- **Polite speed:** at most 5 requests per second with the default 5 sessions. In our tests, 1,000 products
  took about 10 minutes at the default 256 MB memory, and about 6.5 minutes at 512 MB (Apify gives more CPU
  to runs with more memory). Raise the run's memory for large catalog exports.

### Known issues

No open bugs are known in the current version. Things that can surprise you:

- Woolworths can keep a removed product in its sitemap for a while. It comes back as `no_data` (free).
- Catalog mode with `onlySpecials` checks products in sitemap order until it has `maxItems` specials, so it checks
  many more products than it returns and takes longer. Products that are not on special are free.
- Each price is the one Woolworths showed at `scrapedAt`; Woolworths can change prices later the same day.

### Reliability

- If Woolworths blocks a request or resets a connection, the Actor **stops and tells you** how far it got
  (checked / total). It doesn't try to get around blocks and never switches proxies automatically.
  Unchecked products get free `failed` rows.
- Temporary errors (timeouts, HTTP 5xx) are retried with a short backoff.
- If Woolworths answers "too many requests" (HTTP 429), that session pauses for 1, then 2, then 4 minutes
  on the same connection, and if it's still refused after that, the run stops as it does for a block.
- A daily health check runs known products and alerts us if anything breaks.

### FAQ

**How much does it cost?** $1.50 per 1,000 products with a price on the Free plan, down to $0.60 on Diamond (see
[Pricing](#pricing)), plus Apify's small Actor-start charge per run. Checking 10 priced products on the Free plan
costs $0.015.

**Am I charged for failed products?** No. Only rows with a price are charged. `failed` and `no_data` rows, products
without a price and products left out by your filters are free.

**What are the limits?** Default online store only (no postcode or store selection), no keyword search or category
browsing, and 1 request per second per session (5 sessions by default). See [Limits](#limits-by-design).

**Is this allowed?** The Actor reads public product pages that Woolworths' robots.txt allows. It uses no
login and collects no personal data.

**How do I monitor prices every day?** Save your product list as a task with a `stateName` and add an Apify
schedule (for example, every morning). Each run's dataset is a dated price snapshot with the change since the
last run; add `onlyChangedProducts` to get only the changes. Use Apify integrations or webhooks to send
results to Google Sheets, Slack or your database. See [Track price changes](#track-price-changes).

**Why is a product `no_data`?** Woolworths has discontinued or removed it (it may still appear in the
sitemap). You are not charged.

**Why does a product have no price?** It is listed by Woolworths but not sold at the default online store
(`not_ranged`). You are not charged for it.

Also by the same author: [DNS Lookup & SSL Certificate Checker](https://apify.com/mouadapi/dns-ssl-checker) — bulk
DNS records, SPF/DMARC and SSL certificate expiry for many domains.

# Actor input Schema

## `mode` (type: `string`):

"products": check the products you list below (price monitoring). "catalog": export products from the official Woolworths sitemap, up to Max items.

## `products` (type: `array`):

Woolworths product URLs or product codes, one per line. Examples: 888140, or https://www.woolworths.com.au/shop/productdetails/888140/woolworths-full-cream-milk. Used in "products" mode. Duplicates are checked once.

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

Catalog mode only: maximum number of products to export from the sitemap (in sitemap order). Default 100. You are charged only for products returned with data.

## `includeUnpricedProducts` (type: `boolean`):

Catalog mode only. By default, products without a price at the default online store (not sold there, or removed) are skipped. Turn this on to output them too, free of charge.

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

Optional. Give your watch list a name (e.g. "my-weekly-basket") to track changes between runs. The Actor keeps the last seen price, was-price, special flag and availability of every product in a named key-value store in your account ("woolworths-monitor-<name>"), and fills previousPrice, priceChange, changedSinceLastRun and firstSeen in every row. Use the same name on every run of the same list.

## `onlyChangedProducts` (type: `boolean`):

Needs a tracking name. Output only products whose price, was-price, special flag or availability changed since the last run, or that are new. Unchanged products are not output and not charged.

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

Output only products that are on special (isOnSpecial = true). Other products are not output and not charged. In catalog mode, Max items counts special products delivered.

## `proxy` (type: `string`):

"datacenter" (default) works for Woolworths and is the cheapest. "residentialAU" uses Australian residential IPs and adds a residential surcharge per product returned. We never switch proxies automatically.

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

Number of parallel browser-like sessions. Each session makes at most 1 request per second, so 5 sessions ≈ 5 requests/second in total.

## Actor input object example

```json
{
  "mode": "products",
  "products": [
    "https://www.woolworths.com.au/shop/productdetails/888140/woolworths-full-cream-milk",
    "83273"
  ],
  "maxItems": 100,
  "includeUnpricedProducts": false,
  "onlyChangedProducts": false,
  "onlySpecials": false,
  "proxy": "datacenter",
  "maxConcurrency": 5
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset with one row per product

## `runReport` (type: `string`):

Summary of the run (counts, charged and free rows, stop reason)

# 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 = {
    "products": [
        "https://www.woolworths.com.au/shop/productdetails/888140/woolworths-full-cream-milk",
        "83273"
    ],
    "maxItems": 100,
    "proxy": "datacenter"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mouadapi/woolworths-price-monitor").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 = {
    "products": [
        "https://www.woolworths.com.au/shop/productdetails/888140/woolworths-full-cream-milk",
        "83273",
    ],
    "maxItems": 100,
    "proxy": "datacenter",
}

# Run the Actor and wait for it to finish
run = client.actor("mouadapi/woolworths-price-monitor").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 '{
  "products": [
    "https://www.woolworths.com.au/shop/productdetails/888140/woolworths-full-cream-milk",
    "83273"
  ],
  "maxItems": 100,
  "proxy": "datacenter"
}' |
apify call mouadapi/woolworths-price-monitor --silent --output-dataset

```

## MCP server setup

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

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/ThJwSoSipYciYZhnd/builds/L2vTmwMXhsANVF9BL/openapi.json
