# Woolworths NZ Scraper: Grocery Prices & Specials (`hexagone-data/woolworths-nz-scraper`) Actor

Scrape woolworths.co.nz (ex-Countdown) departments and specials: price, member (Everyday Rewards) vs non-member price, price per 100g/kg, EAN barcode, brand, size, availability. Pay per result.

- **URL**: https://apify.com/hexagone-data/woolworths-nz-scraper.md
- **Developed by:** [Hexagone Data](https://apify.com/hexagone-data) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 1,000 products

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 NZ Scraper — New Zealand Grocery Prices, EANs & Specials

Collect product information from **woolworths.co.nz** (Woolworths New Zealand, former Countdown), the country's biggest supermarket chain. Paste department URLs for full listings, the specials page for promotions, or product URLs for product-page records. Every record carries the price context the site applied, so you know what the numbers mean.

Use dated exports to track an assortment, match products by barcode (EAN) when available, or monitor specials and club prices over time.

### Try it with 20 products

1. Paste a department URL into **Start URLs**.
2. Set **Maximum number of products** to `20`.
3. Start the Actor and open its dataset.
4. Read **Run summary** before exporting JSON, CSV or Excel: a successful process can still contain incomplete URLs or check warnings.

```json
{
  "startUrls": [{"url": "https://www.woolworths.co.nz/shop/browse/fruit-veg"}],
  "maxItems": 20
}
```

The form's product-count prefill is 20, while the schema default is 500. Set `maxItems` explicitly in API calls; `0` removes the product-count limit. Departments hold thousands of products (Pantry: ~5 100 at the time of writing): start small.

### What to paste

| Input | Data | Notes |
|---|---|---|
| Department `/shop/browse/fruit-veg` | Every product of the department, paginated to the end of the list (48 products per page) | Includes all sub-categories. Sub-category URLs themselves are not supported yet. |
| Specials `/shop/specials` | Current specials across the store | Includes out-of-stock items when the site lists them. |
| Product `/shop/productdetails?stockcode=123456` | One product record | Richer category path; no barcode (the product API does not publish one). |

You can mix all three. Explicit product URLs are processed first; a product-page record takes precedence over its duplicate listing record. Products are deduplicated within the run.

Search pages (`/shop/search…`) are disallowed by the site's robots.txt and rejected with a reason; so are checkout, account and shopping-list pages.

### Prices are the site's default-store prices

The Actor browses like a signed-out visitor, and the site then applies its **default store and home delivery** to every price. The store the site applied is written in each record's `priceContext` (`store: "Glenfield"`, `method: "Courier"` at the time of writing). These are **not** the prices of a store you chose, and they are not a national average: compare exports only with that context in mind.

#### About `isClubPrice` — member and non-member prices

When `isClubPrice` is true, the site shows **two prices**: "Member price" and "Non-member price". The record keeps both:

- `price` = the **member price**, what Everyday Rewards (former Onecard) members pay;
- `originalPrice` = the **non-member price**, what everyone else pays.

For example the site showing "Member price $5.00" and "Non-member price $5.39" gives `price: 5.00`, `originalPrice: 5.39`, `isClubPrice: true`. Never treat a club price as the price for everyone: without the loyalty card, the number to use is `originalPrice`. `isSpecial: true` marks regular weekly specials, open to everyone.

### Output

| Field | Meaning |
|---|---|
| `sku` | Woolworths stockcode (numeric product code). |
| `barcode` | Usually EAN-13; shorter numeric codes exist for items sold by weight. Null on product-page records. |
| `name` | Product name as filed in the site's catalogue (internal label, lowercase). The product page often shows a longer marketing title (brand, flavour, pack count) that this field does not carry — see Known limits. |
| `url`, `image` | Public product page and image. |
| `brand`, `variety`, `unit`, `size`, `packageType` | Site classification; `unit` says how it is sold (`Each` or `Kg`), `size` is the pack size shown next to the name. |
| `price`, `priceCurrency` | Displayed price in NZD — the member price when `isClubPrice` is true. |
| `originalPrice`, `savePrice` | Reference price (crossed-out for specials, non-member price for club prices) and announced saving, else null. |
| `isSpecial`, `isClubPrice` | Weekly special / member-price flags (see the member price section). |
| `unitPrice`, `unitPriceMeasure` | Price per measure exactly as the site shows it (e.g. `2.00` + `"100g"` for "$2.00 / 100g"); null when the site shows none. |
| `averagePricePerSingleUnit` | Site-computed average price of a single piece (e.g. one loose pear) — a different number from `unitPrice`. |
| `availability` | Site availability: `in_stock`, `low_stock` or `out_of_stock` — the online shop's stock for the default context, not your local store. |
| `department` | Category path, e.g. `Pantry > Snacks & Sweets > Chips`. |
| `priceContext` | Store and delivery method the site applied (see above). |
| `position`, `page`, `sourceUrl`, `scrapedAt` | Collection provenance. |

A field the site does not publish is `null`, never an empty string.

`priceContext` and `unitPrice`-related fields aside, records are flat. Account for `priceContext` (an object) when transforming JSON or choosing spreadsheet columns. Retain `scrapedAt` with your exports: this Actor returns observations, not a maintained history of price changes.

#### Real output example

The following product record was collected on **22 October 2026**. It demonstrates the format, not today's price or stock.

```json
{
  "sku": "6033995",
  "barcode": null,
  "name": "bluebird originals mix potato chips multipack ready salted, s&v, chicken",
  "url": "https://www.woolworths.co.nz/shop/productdetails?stockcode=6033995",
  "brand": "bluebird originals mix",
  "variety": "ready salted, s&v, chicken",
  "unit": "Each",
  "size": "360g",
  "packageType": "20 pack",
  "price": 7.95,
  "priceCurrency": "NZD",
  "originalPrice": 8.4,
  "savePrice": 0.45,
  "isSpecial": true,
  "isClubPrice": true,
  "unitPrice": 2.21,
  "unitPriceMeasure": "100g",
  "averagePricePerSingleUnit": null,
  "availability": "in_stock",
  "image": "https://assets.woolworths.com.au/images/2010/6033995.jpg?impolicy=wowcdxwbjbx&w=900&h=900",
  "department": "Pantry > Snacks & Sweets > Chips",
  "priceContext": {"store": "Glenfield", "method": "Courier"},
  "position": null,
  "page": null,
  "sourceUrl": "https://www.woolworths.co.nz/shop/productdetails?stockcode=6033995",
  "scrapedAt": "2026-10-22T06:18:00+00:00"
}
```

This record is a club price: $7.95 is the Everyday Rewards member price, $8.40 the non-member price, and $2.21 per 100g is what the site shows next to it.

### Run summary

The **OUTPUT** record (and its readable **SUMMARY** page) tells, for every pasted URL, how many products were delivered and why a URL delivered nothing (`no_products`, `not_found`, `blocked_by_site`, …), plus the `anomalies` list — empty when the automatic checks found nothing unusual. An invalid URL never blocks the others; a URL that really fails is reported, and the run fails only if nothing at all could be collected.

### Known limits

- **Default store only.** No store or postcode can be selected; see the price-context section above.
- **Sub-category URLs** (`/shop/browse/<department>/<aisle>…`) are rejected for now: paste the department URL, which contains all its sub-categories.
- **Catalogue name vs on-screen title.** `name` is the site's internal catalogue label (lowercase); the product page frequently shows a longer marketing title that cannot be rebuilt from the API fields (e.g. catalogue `shapes light & crispy crackers honey soy` shown as "Arnott's Shapes Light & Crispy Crackers Honey Soy Chicken 120g"). Match products on `sku` or `barcode`, not on the exact wording of `name`.
- **Member vs non-member prices.** On club prices, `price` is the member price and `originalPrice` the non-member price: see `isClubPrice`.
- The site lists a few products twice across pages of the same listing; duplicates are deduplicated by `sku` within a run.

### Proxy

Not needed by default. Only enable the proxy configuration if your runs get blocked; the Actor then retries through it, and as a last resort through Apify datacenter then New-Zealand residential proxies.

# Actor input Schema

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

woolworths.co.nz pages to scrape. Accepted: department pages (https://www.woolworths.co.nz/shop/browse/fruit-veg), the specials page (https://www.woolworths.co.nz/shop/specials) and product pages (https://www.woolworths.co.nz/shop/productdetails?stockcode=123456). Search pages are disallowed by the site's robots.txt and rejected with a reason. Sub-category URLs are not supported yet: paste the department page, it contains all its sub-categories.

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

Stops after this many unique products across all start URLs. 0 = no limit. A department holds thousands of products: start small.

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

Not needed by default. Only enable it if your runs get blocked.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.woolworths.co.nz/shop/browse/fruit-veg"
    }
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `records` (type: `string`):

All product records, one per product and source URL, with every field.

## `prices` (type: `string`):

Price view: product, current price, crossed-out price, saving, special and club flags.

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

Readable per-URL summary: status, products delivered, reason for any URL that returned nothing.

## `summaryJson` (type: `string`):

The same summary as JSON (OUTPUT record), for programmatic use.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.woolworths.co.nz/shop/browse/fruit-veg"
        }
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("hexagone-data/woolworths-nz-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 = {
    "startUrls": [{ "url": "https://www.woolworths.co.nz/shop/browse/fruit-veg" }],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("hexagone-data/woolworths-nz-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 '{
  "startUrls": [
    {
      "url": "https://www.woolworths.co.nz/shop/browse/fruit-veg"
    }
  ],
  "maxItems": 20
}' |
apify call hexagone-data/woolworths-nz-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hexagone-data/woolworths-nz-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/YzZ37Tgx1HgsxQifv/builds/xf5RlNx8RgvLCAUvT/openapi.json
