# PropertyGuru SG & MY Listings Scraper (`khadinakbar/propertyguru-all-in-one-scraper`) Actor

Scrape public PropertyGuru Singapore and Malaysia sale/rent listings from search or listing URLs. Returns price, type, beds/baths when enriched, area, tenure, GPS, amenities, and agent. MCP-ready.

- **URL**: https://apify.com/khadinakbar/propertyguru-all-in-one-scraper.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** Real estate, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 listing scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## PropertyGuru SG & MY Listings Scraper

This Apify Actor helps proptech teams, agencies, investors, and AI agents collect **public residential listings** from PropertyGuru Singapore and PropertyGuru Malaysia. Paste a search URL, a listing URL, or build a search with market, sale/rent, location, and filters. Each dataset row is one listing with price, currency, property type, images, and optional enriched beds, baths, floor area, tenure, GPS, amenities, and agent fields. The Actor runs over HTTP (JSON-LD), works through Apify API/MCP, and uses Pay per event plus platform usage.

### Best fit and connected workflows

Use this Actor when your workflow is centered on SG or MY residential PropertyGuru inventory:

- Singapore condo/HDB/landed for-sale comps from search pages or listing URLs
- Singapore for-rent discovery with price and property type from search cards
- Malaysia residential sale or rent pulls from PropertyGuru Malaysia
- Detail enrichment when you need beds, baths, floor area, tenure, GPS, amenities, and agent
- MCP/API batch jobs that need honest COMPLETE / VALID\_EMPTY / INVALID\_INPUT outcomes

### Connect the workflow

- For UK BUY/RENT/SOLD and agent branches, start with [Rightmove All-in-One Scraper](https://apify.com/khadinakbar/rightmove-all-in-one-scraper) when your market is the UK, then return here for SEA PropertyGuru listings.
- For US property cards, continue with [Zillow All-in-One Scraper](https://apify.com/khadinakbar/zillow-all-in-one-scraper) after you finish SG/MY comps when you also need Zillow listings.

PropertyGuru SG & MY Listings Scraper is designed as a focused standalone workflow for SEA residential public listings (CommercialGuru, DDProperty Thailand, and login-gated CRM data sit outside this contract).

### Practical scenario

Priya is pricing a 3-bed condo brief in Singapore. She keeps the prefill SG condo search, sets `maxResults` to `20`, and leaves `enrichDetails` off for a fast discovery pass. The dataset returns `listingId`, `listingUrl`, `title`, `price`, `currency`, `propertyType`, and `imageUrls`. She shortlists five URLs, then re-runs those listing URLs with `enrichDetails: true` to add bedrooms, bathrooms, floor area, tenure, GPS, and the listing agent before sending the pack to her client.

### Input

Use the **Input** tab. Supply `startUrls` and/or build a search with `market` + `listingType` (and optional filters).

| Field | Purpose |
|-------|---------|
| `market` | `SG` or `MY` when building a search URL |
| `listingType` | `sale` or `rent` for built searches |
| `startUrls` | PropertyGuru search or listing URLs (SG or MY) |
| `searchLocation` | Optional free-text location for built searches |
| `propertyType` | `all`, `condo`, `hdb`, or `landed` |
| `minPrice` / `maxPrice` / `bedrooms` | Optional filters for built searches |
| `maxResults` | Hard cap on validated listings returned and billed |
| `maxPages` | Search pagination cap (`?page=N`) |
| `enrichDetails` | Fetch each listing detail page for richer fields |
| `proxyConfiguration` | Apify proxy; Unblocker or Residential SG/MY recommended |

Valid focused JSON example:

```json
{
  "market": "SG",
  "listingType": "sale",
  "startUrls": ["https://www.propertyguru.com.sg/property-for-sale?market=residential&property_type=N"],
  "maxResults": 5,
  "maxPages": 1,
  "enrichDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["UNBLOCKER"]
  }
}
```

### Output

One dataset row per listing. Download JSON, CSV, HTML, or Excel from the run dataset.

| Field | Meaning |
|-------|---------|
| `listingId` / `listingUrl` | Stable ID and canonical PropertyGuru URL |
| `market` / `listingType` / `propertyType` | SG or MY, sale/rent, condo/HDB/landed |
| `title` / `price` / `currency` / `pricePerArea` | Asking price and derived PSF when area is known |
| `bedrooms` / `bathrooms` / `floorArea` / `tenure` | Present when detail enrichment runs |
| `locality` / `region` / `latitude` / `longitude` | Location + GPS when enriched |
| `agentName` / `agencyName` | Public agent fields when enriched |
| `amenities` / `imageUrls` | Public amenities and photo URLs |
| `sourceUrl` / `sourceFetchedAt` | Provenance of the fetch |

Illustrative search-card JSON:

```json
{
  "listingId": "500237065",
  "listingUrl": "https://www.propertyguru.com.sg/listing/for-sale-the-sail-marina-bay-500237065",
  "market": "SG",
  "title": "The Sail @ Marina Bay",
  "listingType": "sale",
  "propertyType": "Condominium",
  "price": 2750000,
  "currency": "SGD",
  "imageUrls": ["https://sg1-cdn.pgimgs.com/listing/500237065/UPHO.161141806.V550/example.jpg"],
  "enriched": false,
  "sourceUrl": "https://www.propertyguru.com.sg/property-for-sale?market=residential",
  "sourceFetchedAt": "2026-09-22T09:51:01.377Z"
}
```

### How it works

The Actor classifies each start URL as search or detail, then parses PropertyGuru schema.org JSON-LD (`ItemList` on search pages; `Apartment` / `Offer` / `RealEstateAgent` on detail pages). Prefer `?market=residential` style search URLs. Malaysia listing paths use `/property-listing/...` (and project cards may use `#listingId`). When Residential returns HTTP 403 or an empty parse, the Actor retries once with Apify Unblocker. Every run writes `OUTPUT` and `RUN_SUMMARY` with an honest outcome.

### Pricing

This Actor uses **Apify Pay per event** plus standard **platform usage** (compute, proxy, storage). Charged events:

- Actor start — `$0.00005`
- Listing scraped — `$0.005` (primary; one per validated listing row)
- Listing detail enriched — `$0.008` (when `enrichDetails` merges a detail page)

Open the live **Pricing** tab in Apify Console for current event prices. Example: five search listings charge the start event once and `listing-scraped` five times (about `$0.025` in listing events), plus usage.

### Use with AI agents (MCP)

Exact Actor identity: `khadinakbar/propertyguru-all-in-one-scraper`

Tool description: scrapes public PropertyGuru SG/MY residential search or listing URLs into structured listing rows with optional detail enrichment for beds, baths, area, tenure, GPS, and agent.

> Scrape PropertyGuru Singapore condo listings for sale, return listingUrl, price, currency, propertyType, and when enrichDetails is true also bedrooms, bathrooms, floorArea, tenure, and agentName. Cap at 10 rows. Summarize the dataset and note the RUN\_SUMMARY outcome.

Output interpretation for agents:

- `market` is `SG` or `MY`; `listingType` is `sale` or `rent`
- `sourceUrl` and `sourceFetchedAt` provide provenance
- The dataset is the primary result store; `OUTPUT` / `RUN_SUMMARY` hold terminal outcome and charge counts
- `maxResults` and `maxPages` bound collection and cost
- Cost guidance: one validated listing → one `listing-scraped` event; enrichment adds `listing-enriched` — see the live Pricing tab plus platform usage

### Apify API example

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('khadinakbar/propertyguru-all-in-one-scraper').call({
  market: 'MY',
  listingType: 'sale',
  maxResults: 10,
  maxPages: 1,
  enrichDetails: false,
  proxyConfiguration: {
    useApifyProxy: true,
    apifyProxyGroups: ['UNBLOCKER'],
  },
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Best results

Use Unblocker or Residential pinned to SG or MY. Start with `maxResults: 5` for a quality sample, then raise caps. Enable `enrichDetails` when you need beds, baths, GPS, and agent. Prefer search URLs that include `market=residential`. Segment large jobs by district, property type, or price band. Empty matches finish as `VALID_EMPTY`, non-PropertyGuru hosts finish as `INVALID_INPUT`, and sustained upstream blocks finish as `UPSTREAM_FAILED`.

### Design note

In my testing, PropertyGuru public pages already expose schema.org JSON-LD, so Cheerio over HTTP was enough once Cloudflare cleared — I found no need for a full browser session. I learned Malaysia uses `/property-listing/` paths and project cards often put the real ID in the URL hash, which is why listing-id extraction prefers the fragment before trailing path digits. I also found Residential can still return 403, so the Actor retries once with Unblocker after a primary empty pass.

### Responsible use

Use public listing data for research, comps, and your own market analysis. Respect PropertyGuru terms of service and applicable law. This Actor is for public residential listing pages only. PropertyGuru is a trademark of its owner. This independent Actor is not affiliated with, associated with, or endorsed by PropertyGuru.

# Changelog

This Actor's version history is a separate document: https://apify.com/khadinakbar/propertyguru-all-in-one-scraper/changelog.md

# Actor input Schema

## `market` (type: `string`):

Choose Singapore (propertyguru.com.sg) or Malaysia (propertyguru.com.my). Ignored when startUrls already include a full PropertyGuru host.

## `listingType` (type: `string`):

Sale or rent search when building a URL from filters. Ignored for pasted startUrls.

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

Optional PropertyGuru search or listing URLs (SG or MY). Example search: https://www.propertyguru.com.sg/property-for-sale?market=residential. Leave empty to build a search from Market / Listing type / filters.

## `searchLocation` (type: `string`):

Optional free-text location (e.g. Orchard, Tampines, Petaling Jaya) when building a search URL.

## `propertyType` (type: `string`):

Optional property-type filter for built searches.

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

Minimum price in local currency (SGD or MYR).

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

Maximum price in local currency (SGD or MYR).

## `bedrooms` (type: `integer`):

Exact bedroom filter (0 = studio).

## `maxResults` (type: `integer`):

Hard cap on validated listings returned and billed. Prefill 5 for a fast quality sample.

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

Pagination cap per search URL (about 20 cards per page).

## `enrichDetails` (type: `boolean`):

Fetch each listing detail page for beds, baths, floor area, tenure, GPS, amenities, and agent. Adds the listing-enriched event.

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

Apify Unblocker is the most reliable route (Cloudflare). Residential SG/MY usually works; if Residential returns 403 the Actor retries once with Unblocker automatically.

## Actor input object example

```json
{
  "market": "SG",
  "listingType": "sale",
  "startUrls": [
    "https://www.propertyguru.com.sg/property-for-sale?market=residential&property_type=N"
  ],
  "propertyType": "condo",
  "maxResults": 5,
  "maxPages": 1,
  "enrichDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Validated PropertyGuru listings in the default dataset.

## `output` (type: `string`):

Compact terminal outcome, item counts, and warnings.

## `runSummary` (type: `string`):

Detailed terminal diagnostics and billable-event counts.

# 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 = {
    "market": "SG",
    "listingType": "sale",
    "startUrls": [
        "https://www.propertyguru.com.sg/property-for-sale?market=residential&property_type=N"
    ],
    "propertyType": "condo",
    "maxResults": 5,
    "maxPages": 1,
    "enrichDetails": false,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("khadinakbar/propertyguru-all-in-one-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 = {
    "market": "SG",
    "listingType": "sale",
    "startUrls": ["https://www.propertyguru.com.sg/property-for-sale?market=residential&property_type=N"],
    "propertyType": "condo",
    "maxResults": 5,
    "maxPages": 1,
    "enrichDetails": False,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("khadinakbar/propertyguru-all-in-one-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 '{
  "market": "SG",
  "listingType": "sale",
  "startUrls": [
    "https://www.propertyguru.com.sg/property-for-sale?market=residential&property_type=N"
  ],
  "propertyType": "condo",
  "maxResults": 5,
  "maxPages": 1,
  "enrichDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call khadinakbar/propertyguru-all-in-one-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,khadinakbar/propertyguru-all-in-one-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/acuXHQP4eTUkzggXW/builds/y2kWOVxUZadzRHoax/openapi.json
