# Woolworths SA Scraper: Prices, Specials & Promotions (`zaiq/woolworths-sa-scraper`) Actor

Scrape Woolworths South Africa (woolworths.co.za) products by search term, category or URL: prices in all three Woolworths price zones, specials, promotions, MyDifference (WRewards) deals, was-prices, ratings, barcodes and unit prices. Food, home, beauty and clothing.

- **URL**: https://apify.com/zaiq/woolworths-sa-scraper.md
- **Developed by:** [Zaiq](https://apify.com/zaiq) (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 $4.00 / 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 SA Scraper: Prices, Specials & Promotions

Get product prices, specials, promotions and MyDifference (WRewards) member deals from Woolworths South Africa
(woolworths.co.za) as structured data: food, home, beauty and clothing. Search by keyword, open a category or give
product links. Every product comes with its price in each of Woolworths' three price zones. Requests go through South
African home internet connections.

### What it does

- Searches the catalogue like the site's search box, reads category pages (current `/browse/...` links and older
  `/cat/...` links) and single products.
- Returns, for every product: price, was-price, the price in all three Woolworths price zones, every promotion the
  site shows ("Now R329.99 Save R50", "Buy any 2 save 20%", "Buy any 4 for 3"), the MyDifference member price where
  there is one, barcode (for products whose Woolworths number is their EAN), brand and range, pack size, unit price per
  kg, litre or item, rating and review count, category path, image and product link.
- Specials only: returns just the products in the site's "On Promotion" filter.
- Food only: leaves clothing, beauty and home products out of searches.
- Weighed products (meat, poultry, some cheese) are marked `soldByWeight` with the site's price per kg.

### Who uses it

- **Price comparison and basket-tracking sites** following grocery prices and specials across South African chains.
- **Food brands and suppliers** checking how their products are priced and promoted at Woolworths.
- **Retail and economic analysts** comparing Woolworths prices by price zone and over time, with unit prices.
- **Shoppers** watching MyDifference deals and specials on a list of products.

### Example

Two searches on 4 October 2026 ("coffee beans" and "ayrshire milk", 8 products each), through South African
residential connections, in 9 seconds:

| Product | Zone 10 | Zone 30 | Zone 60 | Promotion for everyone | MyDifference |
|---|---|---|---|---|---|
| Fresh Full Cream Ayrshire Milk 2 L | R45.99 | R47.99 | R39.99 | none | none |
| Fresh Full Cream Ayrshire Milk 1 L | R29.99 | R31.99 | R25.99 | none | none |
| WCafe Italian Blend Coffee Beans 1 kg | R379.99 | R379.99 | R379.99 | Now R329.99 | Now R304.99 |

The same 2 L of milk is priced R8 apart between zones, and the coffee beans cost R75 less for MyDifference members.

Other test runs the same day:

- 20 everyday search terms, up to 60 products each: 1,081 products (some searches have fewer) in 17 seconds, no failed
  or blocked requests.
- 350 products of one search ("chocolate", 1,379 on the site) and three category links (including an old `/cat/...`
  link): 156 products.
- Specials only for coffee and chocolate: 80 products; "yoghurt" and "coffee beans": 13 MyDifference member deals.

### Input

```json
{
  "searchTerms": ["milk", "coffee"],
  "startUrls": [{ "url": "https://www.woolworths.co.za/browse/food-south-africa/milk-dairy-eggs" }],
  "maxItemsPerSource": 100,
  "specialsOnly": false,
  "priceZone": "10",
  "foodOnly": false
}
```

| Field | What it does |
|---|---|
| `searchTerms` | Words to search for. |
| `startUrls` | Category (`/browse/...` or older `/cat/...`), search (`/browse?searchterm=milk`) or product (any `.../prod/.../_/A-20026875` link), or a bare product number (`20026875`). |
| `maxItemsPerSource` | Products per search term or URL (default 100). A product link gives one. |
| `maxItems` | Products in total for the run; 0 means no total limit. |
| `specialsOnly` | Only products on promotion. |
| `priceZone` | Which zone fills `price`, `wasPrice` and `unitPrice`: `10` (default), `30` or `60`. All three are always in `regionalPrices`. |
| `foodOnly` | Leave clothing, beauty and home out of searches. |
| `sort` | `relevance` (default), `price_low`, `price_high`, `bestsellers`, `rating` or `newest`. |
| `proxyConfiguration`, `maxConcurrency` | Advanced: South African residential connections by default; requests at once (default 6). |

### Output

One row per product. This row is from the example above (image link shortened):

```json
{
  "retailer": "Woolworths",
  "productId": "6009189862465",
  "sku": null,
  "barcode": "6009189862465",
  "name": "WCafe Italian Blend Coffee Beans 1 kg",
  "brand": "Woolies Brands",
  "size": "1 kg",
  "sizeValue": 1.0,
  "sizeUnit": "kg",
  "packCount": 1,
  "soldByWeight": null,
  "price": 379.99,
  "currency": "ZAR",
  "wasPrice": null,
  "promoPrice": 329.99,
  "onSpecial": true,
  "savings": null,
  "unitPrice": 379.99,
  "unitPriceUnit": "kg",
  "loyaltyPrice": 304.99,
  "loyaltyProgram": "Woolworths MyDifference (WRewards)",
  "loyaltyValidFrom": null,
  "loyaltyValidUntil": null,
  "promotions": [
    { "text": "Mydifference Now R304.99 Save R75 Wcafe coffee Beans", "type": null, "memberOnly": true, "promoUnitPrice": 304.99, "minQuantity": 1, "promotionId": null },
    { "text": "Now R329.99 Save R50 Wcafe coffee Beans", "type": null, "memberOnly": false, "promoUnitPrice": 329.99, "minQuantity": 1, "promotionId": null }
  ],
  "inStock": null,
  "stockStatus": null,
  "stockQuantity": null,
  "category": "Food > Promotions > MyDifference Favourites",
  "categories": ["Food", "Promotions", "MyDifference Favourites"],
  "imageUrl": "https://assets.woolworthsstatic.co.za/WCafe-Italian-Blend-Coffee-Beans-1-kg-6009189862465.jpg?V=Qii5&o=...",
  "url": "https://www.woolworths.co.za/prod/Food/Promotions/MyDifference-Favourites/WCafe-Italian-Blend-Coffee-Beans-1-kg/_/A-6009189862465",
  "rating": 5.0,
  "reviewCount": 83,
  "seller": "Woolworths",
  "deliveryEstimate": null,
  "store": null,
  "position": 1,
  "sourceType": "search",
  "sourceValue": "coffee beans",
  "scrapedAt": "2026-10-04T16:12:37Z",
  "priceZone": "10",
  "regionalPrices": { "zone10": 379.99, "zone30": 379.99, "zone60": 379.99 },
  "regionalWasPrices": null,
  "range": null,
  "productType": "Food",
  "badges": ["SAVE", "WREWARDS"],
  "memberDeal": true
}
```

- `price` is the price in the chosen zone; `promoPrice` is the best single-item promotion open to every shopper;
  `loyaltyPrice` is the best single-item MyDifference price. Multi-buys stay in `promotions` with `minQuantity` and the
  price per item they work out to (`promoUnitPrice`). `memberDeal` is true when any deal is for members.
- `barcode` is filled when the product's Woolworths number is a valid EAN-13 (most branded products); Woolworths' own
  eight-digit numbers are not barcodes and are left out.
- All prices are in rand, as the site shows them, VAT included.

### Use it from code, automations and AI agents

- **API**: run it and get the results in one call: `POST https://api.apify.com/v2/acts/zaiq~woolworths-sa-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN` with the input as JSON. The API tab on this page has ready-made Python, JavaScript and cURL examples.
- **Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("zaiq/woolworths-sa-scraper").call(run_input={'searchTerms': ['milk', 'coffee'], 'startUrls': [{'url': 'https://www.woolworths.co.za/browse/food-south-africa/milk-dairy-eggs'}], 'maxItemsPerSource': 20})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

- **No code**: connect it to Google Sheets, Zapier, Make or n8n from the Integrations tab, schedule runs in Apify Console, and get a webhook when a run finishes.
- **AI agents (MCP)**: add it as a tool to Claude, Cursor or any MCP client through Apify's MCP server: `https://mcp.apify.com?tools=zaiq/woolworths-sa-scraper`.

### Pricing

$4.00 per 1,000 products ($0.004 per product row). Apify's platform usage is included.

Each product row is one charged event. Failed, blocked and empty pages, retries and duplicates are free. Your spending
limit is respected: the run stops cleanly before going over it.

### Limits

- **Price zones:** Woolworths prices products by zone (10, 30 and 60) and decides a shopper's zone from the delivery
  address on its own servers; the mapping of zones to areas is not published, so rows give all three zone prices
  rather than a city. Without an address the site shows zone 10 in search and category listings and zone 30 on product
  pages.
- Stock levels are not in Woolworths' listings, so `inStock` is empty. Promotion end dates are not published either.
- Personalised offers inside a MyDifference account are not included; nothing here signs in.
- Use the data in line with the retailer's terms and South African law. This Actor is not affiliated with or endorsed
  by Woolworths Holdings.

### FAQ

**Which price should I use?** For a like-for-like series, keep one `priceZone`. To see the spread, read
`regionalPrices`; the milk in the example differs by R8 between zones.

**Does it include clothing and home products?** Yes, searches include every department unless Food only is on.
Clothing rows also carry `colour`.

**How current are the prices?** Each run reads the site live; `scrapedAt` is the time of reading.

# Actor input Schema

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

Words to search for, as you would type them on the site (for example milk, coffee, nappies). A barcode (EAN-13) also works.

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

Woolworths pages: a category (https://www.woolworths.co.za/browse/food-south-africa/milk-dairy-eggs, older /cat/... links work too), a search (https://www.woolworths.co.za/browse?searchterm=milk) or a product (any .../prod/.../\_/A-20026875 link). A bare product number (20026875) works too.

## `maxItemsPerSource` (type: `integer`):

Stop each search term or category after this many products. A product URL always gives one.

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

Stop the whole run after this many products. 0 means no total limit (each search term or URL still stops at its own maximum, and your spending limit always applies).

## `specialsOnly` (type: `boolean`):

Return only products the site marks as on promotion (a lower price, a member price or a multi-buy deal).

## `sort` (type: `string`):

The order the site lists products in. With a product limit, this decides which products you get.

## `priceZone` (type: `string`):

Woolworths prices products by price zone. Every row has all three zones in regionalPrices; this picks the one used for price, wasPrice and unit price. The site shows zone 10 in search and category listings and zone 30 on product pages until a shopper sets a delivery address.

## `foodOnly` (type: `boolean`):

Skip clothing, beauty and home products in searches.

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

By default every request goes through South African home (residential) internet connections, as a shopper in South Africa would browse.

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

How many requests run at the same time. Kept low to stay polite to the site.

## Actor input object example

```json
{
  "searchTerms": [
    "milk",
    "coffee"
  ],
  "startUrls": [
    {
      "url": "https://www.woolworths.co.za/browse/food-south-africa/milk-dairy-eggs"
    }
  ],
  "maxItemsPerSource": 20,
  "maxItems": 0,
  "specialsOnly": false,
  "sort": "relevance",
  "priceZone": "10",
  "foodOnly": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "ZA"
  },
  "maxConcurrency": 6
}
```

# Actor output Schema

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

Dataset with one row per product.

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

Products per search term or URL, results the site reported, errors, requests and approximate traffic.

# 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",
        "coffee"
    ],
    "startUrls": [
        {
            "url": "https://www.woolworths.co.za/browse/food-south-africa/milk-dairy-eggs"
        }
    ],
    "maxItemsPerSource": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "ZA"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("zaiq/woolworths-sa-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",
        "coffee",
    ],
    "startUrls": [{ "url": "https://www.woolworths.co.za/browse/food-south-africa/milk-dairy-eggs" }],
    "maxItemsPerSource": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "ZA",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("zaiq/woolworths-sa-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",
    "coffee"
  ],
  "startUrls": [
    {
      "url": "https://www.woolworths.co.za/browse/food-south-africa/milk-dairy-eggs"
    }
  ],
  "maxItemsPerSource": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "ZA"
  }
}' |
apify call zaiq/woolworths-sa-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zaiq/woolworths-sa-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/2f7ZnLqkPnstaqekZ/builds/aSqDRE6CsdnUooInZ/openapi.json
