# Dot Property + Lamudi PH Scraper | Philippines Property (`abotapi/dotproperty-com-ph-scraper`) Actor

Scrape Philippines property listings from dotproperty.com.ph and lamudi.com.ph with exact map-pin coordinates, price, beds, baths, areas, facilities, full photo galleries, and agent contact details. Covers houses, condos, apartments, townhouses, villas, land, offices and commercial units, for.

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

## Pricing

from $1.00 / 1,000 listing 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/platform/actors/running/actors-in-store#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

## Dot Property + Lamudi Philippines Scraper

Scrape Philippines property listings from **dotproperty.com.ph** and **lamudi.com.ph** (the country's two largest property portals) in one actor, with the **exact map-pin latitude/longitude** for every listing, price, beds, baths, usable and land areas, facilities, full photo galleries, and agent contact details. Houses, condos, apartments, townhouses, villas, land, offices, for rent or for sale. Every row carries a `source` field so you can tell the two portals apart.

### Key Features

| Feature | Description |
|---------|-------------|
| **Two portals, one dataset** | Dot Property and Lamudi in the same run; every row carries a `source` field |
| **Exact map-pin coordinates** | `latitude` / `longitude` for every listing: the exact pin the portal shows on its map, not a vague "Makati" label |
| **Complete listing data** | Price with currency and rent period, property type, bedrooms, bathrooms, usable + land area, floor, facilities, full description, complete photo gallery |
| **Agent contact details** | Agent/publisher name, **phone number**, profile URL, listing count, verified status |
| **Nearby amenities** | Points of interest with coordinates around Lamudi listings |
| **Three ways to start** | Search builder (location + rent/sale + property types + price/beds/area filters), pasted search URLs, or direct listing URLs, all auto-classified |
| **Incremental monitoring** | Run on a schedule, receive only what changed (NEW / UPDATED / REAPPEARED / EXPIRED) |
| **Resume support** | Continue one interrupted run from a pasted run/dataset ID |
| **Fast** | Lightweight and fast; covers every listing in the results |

### Quick Start

Search for condos for sale in Makati, Metro Manila:

```json
{
  "mode": "search",
  "sites": ["dotproperty", "lamudi"],
  "operation": "sale",
  "propertyTypes": ["condos"],
  "locations": ["Metro Manila"],
  "maxItems": 20
}
```

### How to Use

#### Step 1: Search by location

```json
{
  "mode": "search",
  "sites": ["dotproperty", "lamudi"],
  "operation": "rent",
  "propertyTypes": ["houses"],
  "locations": ["Metro Manila", "Cebu"],
  "maxItems": 100
}
```

Locations are slugified automatically: `"Metro Manila"`, `"Makati"`, `"Cebu"`, or `"metro-manila/makati"` all work. A city name is matched against the portal's own location list first, so `"Makati"` is expanded to its region (`metro-manila/makati`) before the first page is requested, so you never pay for a wrong-shaped search URL. One search is built per (portal × property type × location).

#### Step 2: Filter by price, beds and area

```json
{
  "mode": "search",
  "sites": ["dotproperty"],
  "operation": "sale",
  "propertyTypes": ["condos", "townhouses"],
  "locations": ["Metro Manila"],
  "minPrice": 5000000,
  "maxPrice": 15000000,
  "minBedrooms": 2,
  "minSqm": 60,
  "maxItems": 100
}
```

#### Step 3: Scrape specific URLs

```json
{
  "mode": "url",
  "urls": [
    "https://www.dotproperty.com.ph/properties-for-sale/metro-manila/makati",
    "https://www.lamudi.com.ph/rent/metro-manila/makati/house/",
    "https://www.dotproperty.com.ph/ads/1-bedroom-condo-for-sale-in-the-linear-san-antonio-metro-manila_5971c811f880-1371-3792-81d0-a8181089"
  ],
  "maxItems": 100
}
```

Search URLs and direct listing URLs from both portals can be mixed in the same array; they are auto-classified. For search URLs the scraper continues through all available results pages (starting from any `?page=N` you provide); a direct listing URL is collected once.

### Input Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `mode` | string | `search` | `search` (build URLs from filters) or `url` (use pasted URLs) |
| `sites` | array | `["dotproperty","lamudi"]` | Portals to search |
| `operation` | string | `sale` | `sale` or `rent` |
| `propertyTypes` | array | `["properties"]` | `properties`, `houses`, `condos`, `apartments`, `townhouses`, `villas`, `land`, `offices` |
| `locations` | array | `[]` | Location names or slugs (search mode) |
| `minPrice` | integer | - | Minimum price in PHP |
| `maxPrice` | integer | - | Maximum price in PHP |
| `minBedrooms` | integer | - | Minimum bedrooms |
| `minSqm` | integer | - | Minimum usable floor area (sqm) |
| `sort` | string | `newest` | Dot Property sort: `newest`, `min_price`, `max_price`, `relevance` |
| `urls` | array | `[]` | dotproperty.com.ph / lamudi.com.ph URLs (url mode) |
| `maxItems` | integer | `20` | **The run's volume limit.** Hard cap on listings collected |
| `maxPages` | integer | - | Optional per-search page cap. Leave empty for no page limit; the run stops at Max listings |
| `fetchDetails` | boolean | `true` | Enrich results with exact coordinates, facilities, full gallery, description, and agent contacts. Adds a per-listing detail-enrichment charge |
| `resumeFromRunId` | string | - | Resume one interrupted run (skips already-collected listings) |
| `incrementalMode` | boolean | `false` | Recurring monitoring: remembers the previous run of this search and returns only what changed |
| `stateKey` | string | - | Optional manual key to control which runs share incremental state |
| `emitUnchanged` | boolean | `false` | Incremental mode: also return listings with no detected change |
| `emitExpired` | boolean | `false` | Incremental mode: also return listings no longer found (after a complete scan) |
| `proxy` | object | - | Proxy configuration. Works on every Apify plan. If a run gets blocked, select RESIDENTIAL proxies with a PH country pin |
| `mcpConnectors` | array | `[]` | MCP connectors to also deliver results into (Notion, Linear, Airtable, Apify). Leave empty to skip; see [Send results into your apps](#send-results-into-your-apps-mcp-connectors) |
| `notionParentPageUrl` | string | - | Notion connector only: URL or id of the page under which item pages are created. Required to enable the Notion export; ignored by other connectors |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset |

### Resume an interrupted run

Paste a previous run ID or dataset ID into `resumeFromRunId` to continue that ONE crawl; listings it already collected are skipped instead of re-scraped. This is a one-off continuation, unrelated to Incremental mode below.

### Incremental mode (recurring monitoring)

Turn on `incrementalMode` to schedule this actor against the same search repeatedly and get only what changed. The actor remembers the previous run of this search itself (a saved state, keyed on your filters or on `stateKey`) and tags every listing returned with a `changeType`:

| `changeType` | Meaning |
|---|---|
| `NEW` | First time this listing has ever been tracked under this state key |
| `UPDATED` | Tracked before; at least one comparable field changed (see `changedFields`) |
| `UNCHANGED` | Tracked before, nothing changed; suppressed (not returned) unless `emitUnchanged` is ON |
| `REAPPEARED` | Was previously marked EXPIRED, now back |
| `EXPIRED` | Was tracked and present before, no longer found; only emitted when `emitExpired` is ON, and only after a run that scanned its search to a genuine natural end (no `maxItems` cap hit, no Resume) |

Additional fields in incremental mode: `changedFields` (which field names differ from the last tracked scrape), `firstSeenAt`, `lastSeenAt` (UTC timestamps).

`stateKey` is optional: leave it blank to auto-derive the tracked scope from your search filters (portals, operation, property types, locations, price/bedroom/area filters, sort, URLs, and whether detail pages are enabled). Two different filter setups never share a baseline unless you set the same `stateKey` on both.

`emitUnchanged` and `emitExpired` default OFF. Turning either on returns (and bills, at the standard per-result rate) extra rows beyond what actually changed.

### Output

Each listing is returned as a JSON object. Field availability varies slightly by portal (`source` distinguishes them):

| Field group | Fields |
|---|---|
| Identity | `id`, `listing_id`, `source`, `url`, `title`, `operation`, `property_type`, `listing_id_text` |
| Price | `price`, `currency`, `price_period`, `price_per_sqm` |
| Location | `latitude`, `longitude`, `location_source`, `region`, `city`, `area`, `address`, `breadcrumbs` |
| Specs | `bedrooms`, `bathrooms`, `usable_area_sqm`, `land_area_sqm`, `floor`, `car_parks`, `contract_duration` |
| Facilities | `facilities[]` (swimming pool, gym, security, …) |
| Nearby | `nearby_pois[]` (points of interest with coordinates, Lamudi) |
| Content | `description`, `images[]`, `image_count`, `og_image` |
| Agent | `agent_name`, `agent_phone`, `agent_url`, `agent_listing_count`, `agent_verified` |
| Dates | `posted_ago` (relative posting age) |
| Incremental | `changeType`, `changedFields`, `firstSeenAt`, `lastSeenAt` |

### Connection & reliability

Both portals are scraped over your Apify proxy connection and work on every Apify plan. If a run gets blocked, switch to **RESIDENTIAL** proxies with a **PH** country pin for the most consistent results.

If a portal stops serving detail pages part-way through a run, the actor stops asking for them instead of retrying every remaining listing. You still get every listing with its search-result fields (price, beds, baths, area, URL), and the listings that were not enriched are **not** charged the detail-enrichment fee.

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

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape; the Apify dataset is never changed.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field.
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

# Actor input Schema

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

Search mode = build search URLs from the filters below. URL mode = paste one or more dotproperty.com.ph or lamudi.com.ph URLs (search or listing pages); they are auto-classified and pagination advances forward from any ?page=N you provide.

## `sites` (type: `array`):

Which Philippines property portals to search. Both by default — every row carries a source field so you can tell them apart.

## `operation` (type: `string`):

Whether to search rental listings or listings for sale.

## `propertyTypes` (type: `array`):

Which property types to search. 'properties' means all types. (Townhouses and villas exist only on Dot Property; offices map to Lamudi's commercial category; land maps to land on both.)

## `locations` (type: `array`):

Location names or slugs — e.g. 'Metro Manila', 'Makati', 'Cebu', 'metro-manila/makati'. Case and spaces don't matter; they are slugified automatically. One search is built per (portal x property type x location).

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

Minimum price in Philippine Pesos. Leave empty for no minimum.

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

Maximum price in Philippine Pesos. Leave empty for no maximum.

## `minBedrooms` (type: `integer`):

Minimum number of bedrooms (0=studio).

## `minSqm` (type: `integer`):

Minimum usable floor area in square meters.

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

Sort order for Dot Property search results. Lamudi uses its default relevance ordering.

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

One or more dotproperty.com.ph or lamudi.com.ph URLs. Paste search pages (e.g. /properties-for-sale/metro-manila/makati or /rent/metro-manila/makati/house/) or direct listing pages (/ads/{slug}\_{id} or /property/{id}). Search URLs continue through all available results pages; a listing URL is collected once. In URL mode the search-mode fields above are ignored.

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

Hard cap on the number of listings collected across all searches and URLs. This is the run's volume limit; leave Max pages below unlimited (empty).

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

Optional page cap per search URL. Leave empty for no page limit — the run stops at Max listings. Both portals show roughly 25-30 listings per page.

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

When ON, results are enriched with exact map coordinates (lat/lng), facilities, the full photo gallery, description, and agent contact details. Adds a per-listing detail-enrichment charge. Default ON — the exact map-pin coordinates only exist in the full listing data.

## `proxy` (type: `object`):

Works on every Apify plan. If a run gets blocked, select RESIDENTIAL proxies and a PH country pin for the most locally-consistent results.

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

Paste a previous run ID or dataset ID to continue that ONE interrupted crawl - listings it already collected are skipped, not re-scraped. Unrelated to Incremental mode below, which tracks a recurring search across scheduled runs.

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

When ON, this actor remembers the last run of this SAME search (via its own saved state, keyed on your filters or on State key below) and classifies every listing as NEW / UPDATED / REAPPEARED / EXPIRED. Unrelated to Resume from run/dataset ID above, which continues one specific interrupted run. Default OFF - normal runs return every matching listing every time.

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

Optional. Set this to a name of your choice to manually control which runs share incremental state (e.g. to deliberately merge two different searches into one monitoring campaign). Leave blank to auto-derive the key from your search filters - two different filter setups then never share a baseline.

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

When ON, listings with no detected change are still returned (tagged changeType=UNCHANGED) instead of being suppressed. Off by default so a recurring run returns only what actually changed. Turning this ON returns - and bills, at the standard per-result rate - every listing scanned, not just the changed ones.

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

When ON, listings tracked by a previous incremental run that no longer appear are returned once, tagged changeType=EXPIRED. Only fires after a run that scans its search to a genuine natural end (no Max listings cap hit, no Resume) - a partial run leaves previous state untouched instead of guessing. Turning this ON returns - and bills, at the standard per-result rate - these extra rows.

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

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `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.

## Actor input object example

```json
{
  "mode": "search",
  "sites": [
    "dotproperty",
    "lamudi"
  ],
  "operation": "sale",
  "propertyTypes": [
    "properties"
  ],
  "locations": [
    "Metro Manila"
  ],
  "sort": "newest",
  "urls": [
    "https://www.dotproperty.com.ph/properties-for-sale/metro-manila/makati",
    "https://www.lamudi.com.ph/rent/metro-manila/makati/house/"
  ],
  "maxItems": 20,
  "fetchDetails": true,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [],
    "apifyProxyCountry": "PH"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

Every listing collected by this run, with price, size, location and change type.

## `map` (type: `string`):

Listings with their exact map coordinates.

## `agents` (type: `string`):

The seller or agent behind each listing.

## `detailed` (type: `string`):

Every scraped field, including description, facilities, photos and nearby places.

# 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",
    "sites": [
        "dotproperty",
        "lamudi"
    ],
    "operation": "sale",
    "propertyTypes": [
        "properties"
    ],
    "locations": [
        "Metro Manila"
    ],
    "sort": "newest",
    "urls": [
        "https://www.dotproperty.com.ph/properties-for-sale/metro-manila/makati",
        "https://www.lamudi.com.ph/rent/metro-manila/makati/house/"
    ],
    "maxItems": 20,
    "fetchDetails": true,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [],
        "apifyProxyCountry": "PH"
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "maxNotifyListings": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/dotproperty-com-ph-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",
    "sites": [
        "dotproperty",
        "lamudi",
    ],
    "operation": "sale",
    "propertyTypes": ["properties"],
    "locations": ["Metro Manila"],
    "sort": "newest",
    "urls": [
        "https://www.dotproperty.com.ph/properties-for-sale/metro-manila/makati",
        "https://www.lamudi.com.ph/rent/metro-manila/makati/house/",
    ],
    "maxItems": 20,
    "fetchDetails": True,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": [],
        "apifyProxyCountry": "PH",
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "maxNotifyListings": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/dotproperty-com-ph-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",
  "sites": [
    "dotproperty",
    "lamudi"
  ],
  "operation": "sale",
  "propertyTypes": [
    "properties"
  ],
  "locations": [
    "Metro Manila"
  ],
  "sort": "newest",
  "urls": [
    "https://www.dotproperty.com.ph/properties-for-sale/metro-manila/makati",
    "https://www.lamudi.com.ph/rent/metro-manila/makati/house/"
  ],
  "maxItems": 20,
  "fetchDetails": true,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [],
    "apifyProxyCountry": "PH"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}' |
apify call abotapi/dotproperty-com-ph-scraper --silent --output-dataset

```

## MCP server setup

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