# Woolworths NZ Scraper: Prices, Specials & Club Deals (`vant47/woolworths-nz-scraper`) Actor

Get Woolworths New Zealand prices for milk, bread and weekly specials. Collect store prices, regular and member offers, promotion dates, unit prices, stock, barcode, brand and size by postcode, category or product. About $0.003 per product, or $3 for 1,000. No login.

- **URL**: https://apify.com/vant47/woolworths-nz-scraper.md
- **Developed by:** [fortbrand](https://apify.com/vant47) (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 $2.40 / 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.
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 NZ Scraper: Prices, Specials & Club Deals

For retail price analysts, grocery brands and comparison sites monitoring Woolworths New Zealand. Collect verified-store prices, specials, member offers, unit prices, stock and current barcodes.

Actor fees are USD; product prices are NZD. About **$0.003 per product at one store**. Twenty product-store rows cost about **$0.06**, plus Apify's small start charge. Optional ingredients and nutrition are included in that product price. You do not need a Woolworths account, Everyday Rewards login or a separate data subscription.

### What you get

Each product response must confirm the selected pickup address and fulfilment store. Default-store substitutions are retried, then reported as free notes if the requested context cannot be recovered.

| Field | Meaning |
| --- | --- |
| `verdict` | A readable price, store and availability summary. |
| `retailer`, `country`, `currency` | Woolworths New Zealand, NZ and NZD. |
| `storeId`, `storeName` | The fulfilment store ID and store name actually reported with the price. |
| `location` | The requested four-digit postcode, preserving leading zeros; null for a store-ID or default-store request. |
| `productId`, `sku` | Woolworths numeric product stockcode. |
| `gtin` | Current product-service barcode for the selected selling unit. The original catalogue identifier is retained separately. |
| `title`, `brand`, `size` | Catalogue product name, brand and pack information. |
| `price` | Current ordinary, non-member price in NZD. A member-only offer is kept separately. |
| `regularPrice` | The source reference price, including the non-member price for a member offer. |
| `onSale` | A current special or member-price flag supplied by the source. |
| `promoText`, `promoType` | Available promotion labels and the kind of offer. |
| `promoStart`, `promoEnd` | Published promotion timestamps, or null. Dates are never guessed from a weekly promotion cycle. |
| `memberPrice`, `memberProgram` | Everyday Rewards member price and programme when a club-price offer is published. |
| `unitPrice`, `unitOfMeasure` | Ordinary unit price and its published measure, such as 1L or 100G. |
| `inStock` | The site's in-stock or out-of-stock signal. Null means it did not give a clear signal. |
| `stockLevel` | Null. The API's stock-level integers are availability codes, not physical shelf counts. |
| `rating`, `reviewCount` | Null when the source does not publish these in the product data. Customer review text is not collected. |
| `category` | Published category path or the category used to find the item. |
| `url`, `imageUrl` | Public product page and product image. |
| `scrapedAt` | UTC observation time, retained when a run resumes. |

Retailer-specific extras include `catalogueBarcode`, `catalogueBarcodeDiffers`, `canonicalVariantKey`, `canonicalBarcodes`, `categoryPaths`, `sellingUnit`, `stockcode`, `cupPrice`, `cupMeasure`, `cupListPrice`, `isNew`, `pickupAddressId`, `fulfilmentMethod`, `siteDisplayedPrice`, `memberUnitPrice`, `availabilityStatus`, `stockStatusCode`, `multibuy`, `bonusPoints`, `savingsAmount` and `savingsPercent`.

With `includeDetails`, rows can also contain ingredients, allergens, nutrition, origins, description, directions and health star rating. `detailsStatus` says whether those details were included, were not requested, or could not be read. An absent ingredient list does not establish that a product contains no ingredients or allergens.

### Choose a store deliberately

`locations` accepts NZ pickup-store postcodes, for example `0629`, `8051`, `9016`, `9371` and `2022`. These are exact matches against the retailer's public pickup directory. If several pickup stores have the same postcode, the first store by name is selected and its actual name appears in every row. An unknown postcode produces a free note; it is never replaced with Glenfield or another default store.

Use public **pickup-address IDs** in `storeIds`: Northlands `1225718`, Glenfield `1190273`. Reuse the output's `pickupAddressId` in this input. The separate `storeId` is the fulfilment ID the retailer reports after selection. For example, the verified Northlands context reported pickup address `1225718` and fulfilment store `9540` during validation.

Empty location lists use the site's explicit signed-out default, not a national average. Pickup and delivery prices can differ. Preserve `storeId`, `pickupAddressId` and `fulfilmentMethod`, even when store names look similar.

### Start in three steps

1. Enter a search term, paste a category or product link, or supply product stockcodes.
2. Choose postcodes or pickup-address IDs and set the run-wide `maxItems` cap.
3. Run the Actor, read the summary and export JSON, CSV or Excel from the dataset.

The prefill searches milk in `0629`, capped at 20 products. A 29 October cloud run delivered 20 products in 18 seconds. Validation passed 38 varied cloud inputs and 100-product pagination. Ten products were hand-checked across five selected stores.

### Input examples

Search at one store:

```json
{
  "searchTerms": ["milk"],
  "locations": ["0629"],
  "maxItemsPerSearch": 20,
  "maxItems": 20
}
```

Compare a small basket across five pickup areas:

```json
{
  "searchTerms": ["milk"],
  "locations": ["0629", "8051", "9016", "9371", "2022"],
  "maxItemsPerSearch": 2,
  "maxItems": 20
}
```

Browse a department or a precise shelf:

```json
{
  "startUrls": [
    "https://www.woolworths.co.nz/shop/browse/fridge-deli/milk/full-cream-milk"
  ],
  "storeIds": ["1225718"],
  "maxItemsPerSearch": 30,
  "maxItems": 30
}
```

Collect current coffee deals:

```json
{
  "searchTerms": ["coffee"],
  "locations": ["8051"],
  "dealsOnly": true,
  "maxItemsPerSearch": 100,
  "maxItems": 20
}
```

Fetch known product details:

```json
{
  "productIds": ["282764", "282768"],
  "locations": ["0629"],
  "includeDetails": true,
  "maxItems": 2
}
```

| Input | Default and behaviour |
| --- | --- |
| `searchTerms` | Optional list. Milk is used when no source input is supplied. |
| `startUrls` | Optional search, browse, specials or product links on woolworths.co.nz. |
| `productIds` | Optional numeric stockcodes. Current and older product URL formats are accepted too. |
| `locations` | Four-digit pickup-store postcodes. Empty means the site's default context. |
| `storeIds` | Optional public pickup-address IDs. Duplicate selections of one store are combined. |
| `maxItemsPerSearch` | 20 product candidates examined per source and store. Filters and duplicates can reduce delivered rows. |
| `maxItems` | 20 unique product-store rows across the entire run, not 20 per store. |
| `dealsOnly` | False. Keep specials, member, multibuy or points offers when true. |
| `includeDetails` | False. Adds slower product-detail requests at no extra product fee. |
| `proxyConfiguration` | Verified NZ residential route. Custom proxy URLs or an explicit direct configuration are honoured. |

A specials URL walks the current public departments and retains actual offer rows. A regular low shelf price alone is not treated as a discount. `maxItemsPerSearch` limits candidates examined, so a narrow search with few specials may return fewer deal rows than `maxItems`.

### Output example from a real run

This shortened record is from the 29 October 2026 prefill validation; prices are observations at that time:

```json
{
  "verdict": "$8.09 at Woolworths Glenfield; in stock.",
  "retailer": "Woolworths New Zealand",
  "country": "NZ",
  "currency": "NZD",
  "storeId": "9443",
  "storeName": "Woolworths Glenfield",
  "location": "0629",
  "productId": "6038845",
  "sku": "6038845",
  "gtin": "94153835",
  "title": "Anchor Lactose Free Milk Blue 2L",
  "price": 8.09,
  "regularPrice": 8.09,
  "memberPrice": null,
  "unitPrice": 4.05,
  "unitOfMeasure": "1L",
  "inStock": true,
  "stockLevel": null,
  "pickupAddressId": "1190273",
  "fulfilmentMethod": "Pickup"
}
```

Dataset views cover prices, offers and details. The free `DEALS` record contains offers only. `SUMMARY` reports counts, stores, notes, duplicates, coverage and spending-limit stops.

Free note rows have `recordType: "NOTE"`, a readable verdict and a `noteId`. Product rows have `productId`. Filter on `productId` when your downstream table should contain products only.

### Pricing and spending controls

| Job | Product charge before plan discounts |
| --- | --- |
| 20 products at one store | $0.06 |
| 100 product-store rows | $0.30 |
| 1,000 product-store rows | $3.00 |
| 20 products at five stores, if all 100 rows are requested and found | $0.30 |

The start charge is $0.00005 at the 512 MB default. Bronze product pricing is $0.00285, Silver $0.0027 and Gold $0.0024. Higher tiers use the Gold product rate. Apify platform and the configured Apify proxy usage are included in the Actor price; a separately supplied proxy may have its own provider charges.

One product at one store is one unit, deduplicated across all inputs. That product at two stores is two price observations and two units. Failed inputs, invalid links, empty results, filtered candidates, duplicates and summaries have no Product charge.

The Actor checks available spending room before saving another paid row. If there is no room for the next product, it stops cleanly and keeps the products already delivered. A restart recovers saved rows and acknowledged charges, including an interruption between saving the row and receiving its charge acknowledgement.

### Monitor prices every day

Save a task with a fixed search, shelf or basket and an explicit store list. In Apify's Schedules, create a daily schedule for that task. Each run creates a dated snapshot, so retain `scrapedAt` and the store identifiers when you append exports to your database or spreadsheet.

Keep the pickup address and fulfilment method constant. Compare ordinary and member prices separately, and compare unit prices only when their published measures match. A member offer ending does not establish an ordinary shelf-price increase.

Each scheduled run is a fresh snapshot; unchanged products are not suppressed. Append finished datasets through webhooks, Make, Zapier, n8n, Sheets or your ingestion job. Read `SUMMARY` before treating a scan as complete.

### API use

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("vant47/woolworths-nz-scraper").call(run_input={
    "searchTerms": ["milk"],
    "locations": ["0629"],
    "maxItems": 20
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row.get("productId"):
        print(row["storeName"], row["title"], row["price"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('vant47/woolworths-nz-scraper').call({
  searchTerms: ['coffee'], locations: ['8051'], maxItems: 20
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

HTTP:

```bash
curl -X POST \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"searchTerms":["milk"],"locations":["0629"],"maxItems":20}' \
  'https://api.apify.com/v2/acts/vant47~woolworths-nz-scraper/run-sync-get-dataset-items'
```

### Use with AI agents (MCP)

Connect an MCP client to `https://mcp.apify.com` and ask it to use `vant47/woolworths-nz-scraper`. A useful request names the retailer, postcode or pickup-address ID, product terms and maximum row count: “Get 20 Woolworths NZ milk prices at postcode 0629, including unit prices and member offers.”

Agents use the same input fields, dataset and `SUMMARY`. Set the run-wide cap for all intended product-store combinations. API aliases `query`, `queries`, `keywords`, `url` and `urls` are also accepted.

### Known limits and FAQ

**Are these national prices?** No. They are prices for the verified pickup context or the default context the site actually supplied. Neither is labelled a national average.

**Does a postcode choose a delivery address?** No. It selects an exact matching pickup-store postcode. No personal address, account or delivery checkout is used. A postcode without a published pickup-store match gives a free note.

**Which barcode is returned?** `gtin` comes from the current product service. `catalogueBarcode` preserves the older catalogue identifier, which can differ. Multiple selling units are matched explicitly; an ambiguous barcode stays null.

**Does `stockLevel: 3` mean three items?** No. Source stock-level values are status codes. They are retained as `stockStatusCode`; the family `stockLevel` quantity stays null. `inStock` follows the site's availability text.

**What if promotions have no dates?** `promoStart` and `promoEnd` stay null. Offers are checked against product details for published dates. Timestamps remain exactly as supplied, including any missing timezone offset.

**What happens when details fail?** A verified listing row can still be delivered at the ordinary Product price with `detailsStatus: "unavailable"`. No detail surcharge exists.

**What if the retailer refuses requests?** Requests are paced and retried on fresh sessions. Store selection is repeated and checked on recovery. Inputs still unavailable after retries produce free notes; other inputs can continue.

**Which connection works?** NZ residential HTTP is the production route. Direct and datacenter HTTP failed cloud checks. Headed Chrome is used for independent storefront validation. Custom proxies are optional.

**Can I resume a migrated or rebooted run?** Yes. Saved pages, product-store identities and acknowledged charges are recovered within that run. A new independent run is a fresh snapshot and charges its delivered products again.

**Can I export only deals?** Yes. Read the free `DEALS` JSON record, use `dealsOnly` for a filtered dataset, or inspect offer fields in the dataset's deals view.

**Does this collect shoppers or reviews?** No. It reads public grocery catalogue and anonymous store-context data. Customer profiles, account cookies, personal shopping lists and review text are outside the collection.

# Actor input Schema

## `searchTerms` (type: `array`):

Search products such as milk, coffee or bread. Leave empty when using category/product links or product IDs.

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

Optional woolworths.co.nz product, browse, specials or search links. Product links may use the current product-details or older productdetails form.

## `productIds` (type: `array`):

Optional numeric stockcodes, for example 282764. Current product names and canonical barcodes are checked for the same store.

## `locations` (type: `array`):

Four-digit NZ pickup-store postcodes, including leading zeros. Exact public directory matches; current site postal aliases are checked when needed. Examples: 0629, 8051, 9016, 9300 and 2022. Multiple stores use the first by name. Unknown postcodes give a free note; empty uses the site default.

## `storeIds` (type: `array`):

Optional retailer pickup-address IDs, for example 1225718 for Northlands or 1190273 for Glenfield. These differ from fulfilment store IDs returned in output. Unknown IDs give free notes.

## `maxItemsPerSearch` (type: `integer`):

Product candidates examined for each search/category at each selected store. Deals filtering and duplicates can reduce delivered rows.

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

Run-wide cap on unique product-store rows, across all inputs and stores. About $0.003 per row: 20 rows cost $0.06 plus the small start charge. Raise this to cover every requested store.

## `dealsOnly` (type: `boolean`):

Deliver current specials, member prices, multibuy or points offers. Regular low shelf prices alone are not counted as a discount.

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

Slower: fetch product details for ingredients, nutrition, allergens, origins and health star rating where published. Included in the product price. Failed enrichment is not an extra charge.

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

NZ residential is the verified route. Datacenter and direct connections failed feasibility. Custom proxies and direct mode are honored; a failed configured Apify datacenter connection can retry through NZ residential.

## Actor input object example

```json
{
  "searchTerms": [
    "milk"
  ],
  "locations": [
    "0629"
  ],
  "maxItemsPerSearch": 20,
  "maxItems": 20,
  "dealsOnly": false,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "NZ"
  }
}
```

# Actor output Schema

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

No description

## `offers` (type: `string`):

No description

## `deals` (type: `string`):

No description

## `summary` (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 = {
    "searchTerms": [
        "milk"
    ],
    "locations": [
        "0629"
    ],
    "maxItemsPerSearch": 20,
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "NZ"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("vant47/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 = {
    "searchTerms": ["milk"],
    "locations": ["0629"],
    "maxItemsPerSearch": 20,
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "NZ",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("vant47/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 '{
  "searchTerms": [
    "milk"
  ],
  "locations": [
    "0629"
  ],
  "maxItemsPerSearch": 20,
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "NZ"
  }
}' |
apify call vant47/woolworths-nz-scraper --silent --output-dataset

```

## MCP server setup

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