# Property24 Real Estate Scraper (South Africa) (`scrapyx/property24-properties-scraper`) Actor

Scrapes property listings from Property24 — South Africa's #1 property portal. Returns price, location, bedrooms, bathrooms, parking, floor size, erf size, rates and levies, agent contact, GPS coordinates and full descriptions for any search you paste in.

- **URL**: https://apify.com/scrapyx/property24-properties-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Categories:** Real estate, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.84 / 1,000 results

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

## Property24 Real Estate Scraper (South Africa)

Scrapes property listings from **[Property24](https://www.property24.com)** —
South Africa's #1 property portal.

Public data only. No login, no cookies, no browser. **No bot challenge of any
kind** — 5 of 5 TLS profiles returned clean 200s on search, detail, and even
a bogus area path (which answers a clean 404, not a challenge).

### The one thing you need to know before using this

**You paste a search URL — you don't build one.** Property24 search URLs end
in an opaque numeric area id
(`/for-sale/cape-town/western-cape/432`) that Property24 assigns per
suburb/city. A wrong guess does **not** error — it can silently resolve to a
completely different place with a healthy HTTP 200 (a guessed id for "durban"
landed on Himeville, ~150 km away, with no error of any kind). So this actor
takes full search-result URLs, not place names: browse to any search on
property24.com (city, suburb, price range, bedrooms — whatever filters you
want) and paste the resulting URL in.

### What you get

Three record types share one dataset, told apart by `recordType`.

#### `PROPERTY` — one row per listing

List rows carry price, currency, suburb, street address, bedrooms,
bathrooms, parking spaces, floor size, a description excerpt and the main
image — parsed from Property24's own Schema.org microdata on the search
page.

With **Fetch full property details** on (the default), each row also gets:
`propertyDetails` (Property24's Schema.org `RealEstateListing` JSON-LD,
verbatim — full description, exact GPS coordinates, listing agent and
agency), plus `erfSize`, `ratesAndTaxes`, `levies` and `listingDate` — four
fields that exist **only** as plain HTML on the detail page, in neither the
list tile nor the JSON-LD block.

#### `SEARCH_SUMMARY` — one row per search URL

How many pages were fetched and how many properties came back.

#### `ERROR` — one row per input that could not be processed

Every input maps to at least one row, so nothing disappears silently.

### Input

| Field | What it does |
| --- | --- |
| **Search URLs** | full `property24.com/for-sale/...` URLs, copied from the site — one crawl each |
| **Max properties per search URL** | `0` = unlimited (pagination here is honest — see below) |
| **Fetch full property details** | JSON-LD + erf size/rates/levies/date via one extra request per property |
| **Min interval between requests** | the speed control — 0.4s default |

#### Example

```json
{
  "searchUrls": [
    "https://www.property24.com/for-sale/cape-town/western-cape/432",
    "https://www.property24.com/for-sale/johannesburg/gauteng/9"
  ],
  "maxItems": 200,
  "includePropertyDetails": true
}
```

### Notes on reliability

- **No WAF.** All surfaces answered 200 on `chrome124`, `chrome120`,
  `safari17_0`, `chrome99_android` and `firefox133` — `chrome99_android` was
  checked specifically for the mobile-document trap seen on other targets in
  this portfolio and does **not** trigger it here (identical markup).
- **Pagination is honest — unusually so for this portfolio.** Page 1 and
  page 2 share zero listing ids (genuinely different results), and past the
  real last page Property24 answers HTTP 200 with a normal document but zero
  tiles. It does **not** clamp back to page 1 the way some other portals in
  this portfolio do, so `maxItems: 0` (unlimited) safely walks every page a
  search has.
- **Speed is controlled by request rate, not concurrency**, same design as
  the other actors in this portfolio — `maxConcurrency` absorbs latency
  variance, `minRequestInterval` is the actual throttle.
- **Residential proxy pinned to South Africa** is the cloud default.

### Known limits

- **For-sale only.** A `to-let` (rental) path was tried at the same area id
  and returned a clean 404 — rentals appear to use a separate, equally
  opaque id space that was not mapped in this recon pass.
- **No standalone geocoding of area ids.** There is no public directory of
  every suburb's numeric id, so this actor cannot accept "Sandton" and find
  its id itself — see "the one thing you need to know" above.

# Actor input Schema

## `searchUrls` (type: `array`):

Full Property24 'for-sale' search-result URLs — one crawl per entry, each with its own SEARCH\_SUMMARY row. Browse to any city/suburb/price/bedroom search on property24.com and paste the resulting URL here; the trailing numeric area id cannot be guessed from a place name (a wrong guess can silently resolve to an unrelated place with a healthy HTTP 200, so always copy a real URL rather than hand-building one).

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

Stop paginating a search URL after this many properties. Set to 0 for unlimited. Property24's pagination is honest here (rare in this portfolio) — it ends with a genuinely empty page rather than clamping back to page 1, so an unlimited crawl safely walks every page that search has.

## `includePropertyDetails` (type: `boolean`):

Fetch each property's detail page via one extra request: full description, exact GPS coordinates, agent name and agency, erf size, rates and taxes, levies and listing date. Turn this off for a fast list-only crawl — list rows already carry price, location, address, bedrooms, bathrooms, parking, floor size and a description excerpt.

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

How many requests may be in flight at once. This absorbs latency variance; it is NOT the throttle — the crawl rate is set by 'Minimum interval between requests' below.

## `minRequestInterval` (type: `number`):

The actual speed control: the shortest gap between two requests to Property24, across all workers. Property24 serves no bot challenge at all, which is worth preserving — an unthrottled crawl is how a WAF-free site stops being WAF-free. Values below 0.05s are clamped.

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

Property24 runs no bot challenge at all (5/5 TLS profiles clean on search and detail), but Residential pinned to South Africa is the cloud default — container egress is a different posture than a home connection.

## Actor input object example

```json
{
  "searchUrls": [
    "https://www.property24.com/for-sale/cape-town/western-cape/432",
    "https://www.property24.com/for-sale/johannesburg/gauteng/9"
  ],
  "maxItems": 100,
  "includePropertyDetails": true,
  "maxConcurrency": 5,
  "minRequestInterval": 0.4,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "ZA"
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "searchUrls": [
        "https://www.property24.com/for-sale/cape-town/western-cape/432",
        "https://www.property24.com/for-sale/johannesburg/gauteng/9"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/property24-properties-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 = { "searchUrls": [
        "https://www.property24.com/for-sale/cape-town/western-cape/432",
        "https://www.property24.com/for-sale/johannesburg/gauteng/9",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/property24-properties-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 '{
  "searchUrls": [
    "https://www.property24.com/for-sale/cape-town/western-cape/432",
    "https://www.property24.com/for-sale/johannesburg/gauteng/9"
  ]
}' |
apify call scrapyx/property24-properties-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/property24-properties-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/WEvhlCy6zIEuXXabb/builds/9gIlrbCJASrEWeJkD/openapi.json
