# Willhaben.at Scraper - Listings, Prices, Sellers & 40+ Fields (`abotapi/takealot-com-scraper`) Actor

From $1/1K. Scrape Willhaben.at listings into clean JSON with 40+ structured fields, including prices, GPS coordinates, photos, dates, seller details and category-specific attributes. Search with filters or paste any Willhaben URL.

- **URL**: https://apify.com/abotapi/takealot-com-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 1,000 product records

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Takealot Scraper - South Africa Products & Deals

Scrape [Takealot](https://www.takealot.com), South Africa's largest online
marketplace: keyword search, a Daily Deals monitor, prices with was-price
savings, regional stock, ratings and real written reviews. Read through the
storefront's own JSON interface, so output is structured data, not scraped
HTML.

### What you get

Each product record carries: `title`, `brand`, `price` and `priceMax` (offer
price range in ZAR), `prettyPrice`, `listingPrice` (the was-price when the
product is on promotion), `savingPercent`, `starRating`, `reviewCount`,
`ratingDistribution` (1-5 star counts), `inStock`, `stockStatus`,
`distributionCentres` (which warehouses stock it), `isPreorder`,
`addToCartAvailable`, `isDeal`, badges, images and shipping message. With
**Fetch product details** on, each record also carries the spec sheet
(`specs`), rich description, `boxContents`, `warranty`, `categoriesPath`,
variant options, loyalty prices (eBucks / Discovery Miles) and offer detail.

About the offer list: Takealot merges its marketplace sellers into one buybox,
so the interface exposes an offer PRICE RANGE (`price` / `priceMax` plus the
buybox `offerDetail` when the store populates it) rather than a per-seller
listing table. That is a property of the store, not of this actor.

#### Example record (illustrative; these values are made up)

```json
{
  "kind": "product",
  "recordId": "PLID00000000",
  "url": "https://www.takealot.com/sample-gadget-name/PLID00000000",
  "title": "Sample Gadget 100 (Fictional Model)",
  "brand": "Example Brand",
  "price": 1299,
  "priceMax": 1499,
  "prettyPrice": "From R 1,299",
  "listingPrice": 1699,
  "savingPercent": 23.0,
  "currency": "ZAR",
  "starRating": 4.4,
  "reviewCount": 89,
  "inStock": true,
  "stockStatus": "In stock",
  "distributionCentres": ["CPT", "JHB"],
  "isDeal": true
}
```

### Modes

- **Search mode**: one or more keywords, each walked forward through every
  result page. Combine with the filters below.
- **URL mode**: paste any takealot.com links. Product links become single
  detailed records; search, filter, department and Daily Deals pages become
  forward walks. The Daily Deals page (`https://www.takealot.com/deals`) is
  the deals monitor entry point.

### Filters (search mode)

- **Brand**: exact brand spelling as the site uses it (for example `Apple`).
- **Min / Max price (ZAR)**: price window.
- **Minimum star rating**: 1 to 4 stars and up.
- **In stock at**: Cape Town, Durban or Johannesburg.
- **Deals filter**: Featured Deals (the Daily Deals surface), Bundle Deals or
  TakealotMORE Deals. Works with or without a keyword: leave the keywords
  empty and pick Daily Deals to monitor the whole deals surface on a
  schedule. Every row of a deals run is flagged `isDeal` and carries the
  was-price so you can see the drop.

**Sort order**: Relevance, Price high-to-low, Price low-to-high, Top Rated,
Newest Arrivals.

### Daily Deals price-drop monitor (the differentiator)

1. Turn on **Incremental changes for scheduled runs** and pick the **Daily
   Deals** filter (or paste the deals page in URL mode).
2. Schedule the actor to run daily.
3. The first run returns every deal as NEW. Each later run returns only what
   changed: a price drop or a stock change reads as UPDATED with the exact
   `changedFields`, a deal that vanished (and, if you turn on Emit expired,
   comes back) is tracked as EXPIRED / REAPPEARED.

### Limit

**Max items** is the only cap that stops a run early; it defaults to **20**
(set it to 0 for no limit, and the run stops when results run out instead).
**Max pages per keyword** defaults open. Raise Max items for large pulls.

### Reliability and proxy

Takealot's storefront front door refuses plain datacenter connections most of
the time, so the actor keeps a dedicated high-reliability connection pool of
its own as its primary path; your proxy selection under **Proxy
configuration** is honoured alongside it. Leaving the defaults on is the
recommended shape. Datacenter-only selections will fail more often; if your
plan includes residential proxy, naming it improves reliability further.

### Resume and recurring updates

- **Resume from a previous run**: paste a run or dataset ID to continue a
  large pull without collecting the same products again.
- **Incremental mode** (above): state is kept per keyword/link/filter setup;
  name a campaign with **State key** to keep it stable across edits.
- `Emit unchanged` and `Emit expired` are off by default; turning them on
  returns, and bills, extra rows.

### Send results into your apps (MCP connectors)

Results can be piped into the apps you already use via **MCP connectors**:
add `mcpConnectors`, and for Notion also `notionParentPageUrl`, with
`maxNotifyListings` capping rows per connector. Export never changes the
dataset output and fails safe when no connector is reachable.

### Output

Every record also carries the incremental contract fields (`changeType`,
`changedFields`, `firstSeenAt`, `lastSeenAt`) so scheduled runs are
machine-diffable.

### Limitations

- Prices are in South African rand (ZAR).
- Written reviews are fetched per product when **Fetch written reviews** is
  on (with Fetch product details); counts are capped per product.
- The storefront merges sellers into one buybox (see the offer note above).
- A keyword or link that genuinely has no results returns an empty dataset
  with exit 0; a store-side connection refusal fails the run loudly instead
  of faking an empty search.

# Actor input Schema

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

Search looks up products by keyword. URL mode reads specific takealot.com pages you paste (a product page, a search, the Daily Deals page).

## `queries` (type: `array`):

Search mode: one or more keywords, for example 'laptop', 'air fryer', 'nappy bag'. Each keyword is its own walk; results are tagged with the keyword they came from.

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

How results are ordered: Relevance, Price Descending, Price Ascending, Rating Descending (Top Rated) or ReleaseDate Descending (Newest Arrivals).

## `urls` (type: `array`):

URL mode: takealot.com pages to read, one per line. Product pages are read as single records; search, filter, department and Daily Deals pages are walked forward.

## `brand` (type: `string`):

Search mode: only products of this brand, for example Apple, Samsung or HP. Use the exact brand spelling the site uses.

## `minPrice` (type: `integer`):

Search mode: only products priced at or above this many rand.

## `maxPrice` (type: `integer`):

Search mode: only products priced at or below this many rand.

## `minRating` (type: `string`):

Search mode: only products rated this many stars or better.

## `inStockRegion` (type: `string`):

Search mode: only products in stock at the selected regional warehouse.

## `dealType` (type: `string`):

Search mode: only deal products. Featured Deals is the Daily Deals surface the homepage promotes; run it on a schedule to monitor price drops (each run reports what changed in incremental mode). Works with or without a keyword.

## `fetchDetails` (type: `boolean`):

Read each product's own page to add the spec sheet, rich description, category chain, variant options, loyalty prices and offer detail. Adds a per-product surcharge (see the section caption below).

## `fetchReviews` (type: `boolean`):

With Fetch product details on, also read each product's written customer reviews (needs Fetch product details). Adds no extra surcharge beyond the detail one.

## `maxReviewsPerProduct` (type: `integer`):

Cap on written reviews collected per product when Fetch written reviews is on.

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

Stop after this many product records (0 = no limit; the run then stops when results run out). This is the only limit that stops a run early by default.

## `maxPages` (type: `integer`):

Upper bound on pages walked per keyword or link. Defaults wide open so Max items is the only cap that stops a run early.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large pull without returning products already collected there.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns every matching product as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED products (price moves and stock changes read as UPDATED). Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). State is kept separately for each keyword, link and filter setup; use State key to name or deliberately share a monitoring campaign.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or deliberately share state across differently configured runs. Leave empty to let the actor derive a key automatically from the search, link and filter settings.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return products that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return products that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write or digest. Leave empty to skip; never changes the dataset output.

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

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

Apify proxy settings for this run. The actor keeps a dedicated high-reliability connection pool of its own as the primary path and uses your selection alongside it, so the defaults just work. Datacenter-only connections are refused by the store's front door more often than residential ones.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "laptop"
  ],
  "sort": "Relevance",
  "urls": [
    "https://www.takealot.com/deals?start=1"
  ],
  "fetchDetails": true,
  "fetchReviews": false,
  "maxReviewsPerProduct": 5,
  "maxItems": 20,
  "maxPages": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (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 = {
    "mode": "search",
    "queries": [
        "laptop"
    ],
    "sort": "Relevance",
    "urls": [
        "https://www.takealot.com/deals?start=1"
    ],
    "fetchDetails": true,
    "fetchReviews": false,
    "maxReviewsPerProduct": 5,
    "maxItems": 20,
    "maxPages": 0,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "maxNotifyListings": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/takealot-com-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 = {
    "mode": "search",
    "queries": ["laptop"],
    "sort": "Relevance",
    "urls": ["https://www.takealot.com/deals?start=1"],
    "fetchDetails": True,
    "fetchReviews": False,
    "maxReviewsPerProduct": 5,
    "maxItems": 20,
    "maxPages": 0,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "maxNotifyListings": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/takealot-com-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 '{
  "mode": "search",
  "queries": [
    "laptop"
  ],
  "sort": "Relevance",
  "urls": [
    "https://www.takealot.com/deals?start=1"
  ],
  "fetchDetails": true,
  "fetchReviews": false,
  "maxReviewsPerProduct": 5,
  "maxItems": 20,
  "maxPages": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call abotapi/takealot-com-scraper --silent --output-dataset

```

## MCP server setup

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