# Private Property Listings Extractor (`kawsar/private-property-listings-extractor`) Actor

Private Property scraper that pulls South African homes for sale and to rent from privateproperty.co.za, with price, suburb, bedrooms, agent, photos and GPS, so you can track prices and new listings without copying them by hand.

- **URL**: https://apify.com/kawsar/private-property-listings-extractor.md
- **Developed by:** [Kawsar](https://apify.com/kawsar) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.99 / 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?

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

## Private Property Listings Extractor: Scrape privateproperty.co.za Homes for Sale and Rent

Private Property Listings Extractor pulls property listings from privateproperty.co.za, one of South Africa's biggest real estate portals. Paste a search URL for any town, suburb, or province and you get a clean dataset of houses, apartments, and townhouses with price, address, bedrooms, bathrooms, floor size, agent, photos, and GPS coordinates.

It reads search result pages only, so runs are fast and cheap. Sale and rental searches both work, and every value comes straight from what the site shows. Nothing is guessed or padded.

### Who uses it

- Estate agents tracking what competitors list in their patch
- Investors comparing asking prices and rental yields across suburbs
- Property data teams building price indexes or valuation models
- Developers feeding listings into a CRM, map, or alert bot
- Researchers studying the South African housing market

### What you get per listing

Fields are only written when the site actually shows them, so you won't see columns full of `null`.

| Field | Example | Notes |
| --- | --- | --- |
| `listingId` | `T5598246` | Listing number (`T` for sale, `RR` for rent) |
| `internalId` | `12031231` | Numeric id used by the site |
| `url` | `https://www.privateproperty.co.za/...` | Public listing page |
| `listingType` | `For Sale` | Or `To Rent` |
| `propertyTitle` | `2 Bedroom Apartment` | |
| `headline` | `2 Bedroom Apartment in Umhlanga Rocks` | |
| `price` | `3500000` | Rand, as a number |
| `priceText` | `R 3 500 000` | As displayed, also covers `On Auction` |
| `currency` | `ZAR` | When a price is shown |
| `pricePeriod` | `per month` | Rentals only |
| `suburb`, `locality`, `region` | `Umhlanga Rocks`, `KwaZulu Natal` | |
| `streetAddress` | `701 Villa Pax, 2 Ocean Way` | When the agent publishes it |
| `latitude`, `longitude` | `-29.7297`, `31.0859` | When published |
| `bedrooms`, `bathrooms`, `parkingSpaces`, `garages` | `2`, `2`, `2`, `1` | |
| `floorSize`, `landSize` | `190 m²` | Kept with the unit (m² or ha) |
| `shortDescription` | `Choose from flexible lease options...` | Teaser text on standard cards |
| `agentName`, `agentImageUrl` | `Jarryd Jeffery` | |
| `agencyName`, `agencyLogoUrl` | `SA Property Sales` | |
| `imageUrl`, `images` | | High-resolution photo links from the card |
| `badges`, `isPromoted` | `["New"]`, `true` | |
| `pageNumber`, `totalResults` | `2`, `19836` | Where the row came from |
| `sourceUrl` | | Results page URL |
| `scrapedAt` | `2026-09-27T09:22:50Z` | UTC |

### How to use it

1. Go to privateproperty.co.za and run a search. Pick the area, sale or rent, price range, bedrooms, whatever you need.
2. Copy the URL from the address bar.
3. Paste it into **Start URLs**. Add as many searches as you like.
4. Set **Max items per URL**. The default is 20, which is one results page.
5. Click **Start**, then download the dataset as JSON, CSV, Excel, or XML.

The filters you set on the site are kept in the URL, so the extractor returns the same listings you saw. It moves through the result pages on its own until it hits your limit or runs out of listings.

#### Supported URLs

| Type | Example |
| --- | --- |
| For sale search | `https://www.privateproperty.co.za/for-sale/kwazulu-natal/durban/16` |
| To rent search | `https://www.privateproperty.co.za/to-rent/western-cape/cape-town/55` |
| Province search | `https://www.privateproperty.co.za/for-sale/gauteng/3` |
| Starting from a later page | `https://www.privateproperty.co.za/for-sale/kwazulu-natal/durban/16?page=3` |

Single listing links (ending in something like `/T5598246`) are skipped. Use a search results URL instead.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `startUrls` | array | | Search results URLs |
| `maxItemsPerUrl` | integer | `20` | Listings to save for each URL (max 1000) |
| `requestTimeoutSecs` | integer | `30` | Per-request timeout |

#### Example input

```json
{
  "startUrls": [
    "https://www.privateproperty.co.za/for-sale/kwazulu-natal/durban/16",
    "https://www.privateproperty.co.za/to-rent/western-cape/cape-town/55"
  ],
  "maxItemsPerUrl": 20
}
```

This returns up to 40 rows: 20 from Durban sales and 20 from Cape Town rentals.

#### Example output

```json
{
  "listingId": "RR4766759",
  "internalId": "12057792",
  "url": "https://www.privateproperty.co.za/to-rent/western-cape/cape-town/atlantic-seaboard/foreshore/no-1-harbour-arch/11-christiaan-barnard-street/RR4766759",
  "listingType": "To Rent",
  "propertyTitle": "Studio Apartment",
  "headline": "Studio Apartment in Foreshore",
  "price": 13500,
  "priceText": "R 13 500 per month",
  "currency": "ZAR",
  "pricePeriod": "per month",
  "suburb": "Foreshore",
  "streetAddress": "No 1 Harbour Arch, 11 Christiaan Barnard Street",
  "bedrooms": 0.5,
  "bathrooms": 1,
  "parkingSpaces": 1,
  "floorSize": "35 m²",
  "agentName": "Kyle Illman",
  "imageUrl": "https://images.pp.co.za/listing/12057792/q9VCJuAbwQqOKOJL3WJqv0/1600/1066/contain/jpegorpng",
  "images": [
    "https://images.pp.co.za/listing/12057792/q9VCJuAbwQqOKOJL3WJqv0/1600/1066/contain/jpegorpng",
    "https://images.pp.co.za/listing/12057792/HG2oou0CMehxIcTzdMCK93/1600/1066/contain/jpegorpng"
  ],
  "badges": ["Available now"],
  "isPromoted": true,
  "locality": "Foreshore, Atlantic Seaboard",
  "region": "Western Cape",
  "latitude": -33.9213705616998,
  "longitude": 18.43379259109497,
  "pageNumber": 1,
  "totalResults": 3326,
  "sourceUrl": "https://www.privateproperty.co.za/to-rent/western-cape/cape-town/55",
  "scrapedAt": "2026-09-27T09:22:50.170872+00:00"
}
```

Studios show `0.5` bedrooms because that is how the site records them.

### Speed and cost

One request covers about 20 listings. The default of 20 per URL costs a single request per search, and 100 per URL is about 5. Runs with a handful of searches usually finish in under a minute.

### Tips

- Narrow the search on the site first (price, bedrooms, property type). It is cheaper than scraping everything and filtering later.
- Run the same search on a schedule to catch new listings and price drops. `listingId` stays the same between runs, so you can use it to compare them.
- Promoted listings show up at the top of each page. Within one URL, each listing is saved once.

### Integrations

Send results to Google Sheets, Slack, Airtable, a webhook, or your own database using Apify integrations, or call the actor from the Apify API with Python, JavaScript, or plain HTTP.

### FAQ

**Can I scrape a whole province?**
Yes. Use a province URL such as `/for-sale/gauteng/3` and raise `maxItemsPerUrl`. Big provinces have tens of thousands of listings, so split them into cities if you need more than 1000.

**Why is the agent name missing on some rows?**
Some cards don't name an agent. You still get the agency logo, and on standard cards the agency name too.

**Why don't all listings have coordinates?**
Agents choose whether to show the exact location. Rows without it still have suburb, locality, and province.

**Does it work for commercial property?**
Yes. Commercial search URLs use the same layout.

# Actor input Schema

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

Private Property search results URLs (for sale or to rent, any city, suburb, or province). Open privateproperty.co.za, set your area and filters, then paste the address bar URL here. Example: https://www.privateproperty.co.za/for-sale/kwazulu-natal/durban/16

## `maxItemsPerUrl` (type: `integer`):

Maximum number of listings to save for each start URL. Each results page holds about 20 listings.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout in seconds.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.privateproperty.co.za/for-sale/kwazulu-natal/durban/16",
    "https://www.privateproperty.co.za/to-rent/western-cape/cape-town/55"
  ],
  "maxItemsPerUrl": 20,
  "requestTimeoutSecs": 30
}
```

# Actor output Schema

## `properties` (type: `string`):

Private Property listings extractor for privateproperty.co.za. Pull South African homes for sale or to rent with price, address, bedrooms, agent, photos, and GPS coordinates.

# 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": [
        "https://www.privateproperty.co.za/for-sale/kwazulu-natal/durban/16"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kawsar/private-property-listings-extractor").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": ["https://www.privateproperty.co.za/for-sale/kwazulu-natal/durban/16"] }

# Run the Actor and wait for it to finish
run = client.actor("kawsar/private-property-listings-extractor").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": [
    "https://www.privateproperty.co.za/for-sale/kwazulu-natal/durban/16"
  ]
}' |
apify call kawsar/private-property-listings-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kawsar/private-property-listings-extractor"
        }
    }
}
```

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/lTGEvMCwOsNpB7lVg/builds/rPp9cmKZr4JL9aivu/openapi.json
