DDproperty Thailand Scraper - Properties, Prices & Agents
Pricing
from $1.00 / 1,000 results
DDproperty Thailand Scraper - Properties, Prices & Agents
Scrape DDproperty.com Thailand listings at scale. Extract prices, property features, images, agent contacts, GPS coordinates, nearby BTS/MRT transit and more. Built for Thai real estate analytics, market research and investment analysis.
Pricing
from $1.00 / 1,000 results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
1
Bookmarked
42
Total users
9
Monthly active users
11 days ago
Last modified
Categories
Share
DDproperty Scraper: Thailand Property Listings, Prices & Transit Data
DDproperty is Thailand's largest real estate listing site, and this scraper turns it into structured data. Get for sale and for rent listings across every Thai region with price, bedrooms, bathrooms, floor area, tenure, agent and developer details, exact coordinates, and nearest BTS or MRT station. Build filtered searches by region, property type, price and bedrooms, or paste your own DDproperty search URLs, then export to JSON, CSV or Excel, or pull results straight into your app through the API.
Why This Scraper?
- Two ways to search. Build a search from filters (region, property type, price, bedrooms) or paste one or more DDproperty search URLs when you need a more refined query than the filters allow.
- Detail-page enrichment on demand. Turn on detail pages to add exact latitude and longitude, a full description, facilities, floor plans and project attributes to every listing, not just what the search results page shows.
- Agent and developer data. Every listing carries the agent name and ID, the agency ID, and the developer name (or the full legal name on new-launch project rows) when the listing belongs to a project.
- Nationwide region coverage. Search by region, district or area code, from Bangkok down to a single sub-district, or leave it open for every listing on a listing type.
- Built for schedules. Incremental mode remembers a recurring search itself and returns only new, updated and reappeared listings on later runs (plus expired ones if you turn on Emit expired), with unchanged ones suppressed by default.
- Resume without re-billing. Point Resume at a previous run or dataset ID and the actor skips every listing it already collected there.
- Nearest transit. Distance and station ID for the closest BTS or MRT stop whenever the listing shows one, useful for Bangkok's transit-driven property market.
Use Cases
- Property investment research: compare prices per square meter, tenure and developer across regions to spot value.
- Rental market monitoring: track rent listings in a district over time with incremental mode to catch new units as they list.
- Real estate lead generation: collect agent names, agent IDs and agency IDs from listings matching your target criteria.
- Proptech and map products: feed exact coordinates and nearest-transit data into a map or search product.
- Market research and reporting: build a dataset of listing counts, price bands and property types by region for analysis.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
id | 12345678 |
title | "Condo for Sale in Watthana, Bangkok" |
address | "Sukhumvit Soi 24, Khlong Tan, Watthana, Bangkok" |
listing_type | "sale" (or "rent") |
property_type | "CONDO" |
price / price_formatted | 8500000 / "฿8,500,000" |
price_per_sqm | "฿141,667 / sqm" |
bedrooms / bathrooms / floor_area | 2 / 2 / 60 |
tenure | "Freehold" (or "Leasehold") |
developer / is_developer_listing | "Sample Developer Co." / true |
agent_name / agent_id / agency_id | "Agent Name" / 445566 / 778899 |
region / region_code / district / area | "Bangkok" / "TH10" / "Watthana" / "Khlong Tan" |
latitude / longitude | 13.7305 / 100.5697 (when detail pages are on) |
nearby_mrt / nearby_station_id | "7 min (560 m) from E5 Asok BTS" / "E5" |
badges | ["New Project: 2026", "Freehold"] |
posted_date / posted_unix / images / image_count | "07 Jul 2026" / 1783382400 (the last (re-)post time) / ["https://.../image1.jpg"] / 1 |
url / status | full DDproperty link to the listing / "ACT" |
description / facilities | full listing description / ["Swimming Pool", "Fitness Centre"] (when detail pages are on) |
type / sourceSite / currency | "listing" / "ddproperty.com" / "THB" |
furnishing / floor_level / project_developer | "Fully Furnished" / "High Floor" / "Sample Developer Co., Ltd." (listing detail pages; developer holds the lister name, exactly like search rows, and project_developer the developer of the project the listing belongs to) |
completion_year / total_units / project_name | 2008 / 219 / "Sample Residence" (listing and project detail pages) |
updated_date / expiry_date / first_posted_at | "2026-10-14 18:27:00" / "2026-11-15 18:27:00" / "2026-01-28 18:28:02" (listing detail pages; first_posted_at is when the listing was first posted, while posted_date and posted_unix hold the last (re-)post time, exactly like search rows) |
unit_types / brochure_url / developer_full_name | [{"name": "1 Bedroom", "min_price": "฿ 8,900,000", ...}] / PDF link / "Sample Developer Co., Ltd." (project detail pages; developer_full_name is the full legal developer name, also on new-launch project rows. On project detail rows developer holds the short name shown on search cards, while new-launch rows carry only developer_full_name) |
source_page | "listing_detail", "project_detail", "agent_profile" or "agent_directory" (single-page links only) |
Agent rows (type = agent) | agent_id, agent_name, agency_name, agency_id, job_title, is_agent_verified, years_on_site, active_sale_listings, active_rent_listings (directory and search rows), profile_sale_listings, profile_rent_listings (profile rows), website, facebook_url, instagram_url, about, services, operating_areas, agent_photo, agent_phone (directory cards only), directory_section |
changeType / changedFields | incremental mode only: NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED, and the fields that changed |
The dataset has seven views: overview, map, agents, detailed, project pages, agent rows and changes (incremental rows). Detail-page fields are empty when Fetch detail pages is off or a detail page could not be read. Listing rows (type = listing) come from listing pages, listing detail pages and project detail pages; agent profile and agent directory pages produce agent rows, identified by agent_id and tracked separately in incremental mode (an agent id never collides with a listing id). In incremental mode the new fields do not cause a one-time UPDATED wave on listings stored before they existed, but a field that appears later on a listing stored by this version does flag UPDATED. Agent directory pages show a rotating set, so they never trigger EXPIRED rows.
Fields that never trigger UPDATED (incremental mode): recency, a relative posted_date such as "2 days ago", and the query string of image URLs are left out of change detection but still appear in your output. You can add more with ignoreFieldsForChanges.
How to Use
- Pick a Search mode:
search(build a URL from the filters below) orurl(paste DDproperty search URLs directly). - In search mode, set listing type, property type, region or free text location, price and bedroom filters. In URL mode, paste your DDproperty search links. Supported URL shapes: region sale and rent, national, paginated (
?page=N, scraping continues forward from N), filtered and sorted, price-landing, district, near-station, property-type, commercial and new-launch search pages. The prefilled list holds one example link per supported shape, but all links share the one Max properties budget (default 20) and are walked in the order listed: with the defaults the first link (a Bangkok sale search, about 20 cards per page) fills the whole budget and the links after it are not reached. Raise Max properties, or remove links, to reach more shapes; to scrape only your own links, replace the list. Single-page links are also supported. A listing detail page (/en/property/<slug>-<id>), a project detail page (/en/property/project/<slug>-<id>, with aunit_typesprice ladder) and an agent profile page (/en/agent/<slug>-<id>) return one record each. The agent directory page (/en/agent-directory) returns one agent row per featured card (about 30 to 35, a rotating set), and the agent search page (/en/agent-directory/search) returns up to 20 agent rows of about 1,400 because the site ignores page and filter parameters. Every row counts against Max properties and is billed. Agent pages produceagentrows; the profile page masks the phone number, so none is returned there. Bare ddproperty.com links are rewritten to www, and links under /th/ are rejected because the site refuses them. In search mode the region field also accepts a plain name such as Bangkok. - Set Max properties to control run size and cost, and toggle Fetch detail pages for coordinates, description and facilities.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.
Search for condos for sale in Bangkok:
{"mode": "search","listing_type": "sale","property_type": "CONDO","region": "TH10","max_properties": 20}
Rent search by free text location with price and bedroom filters:
{"mode": "search","listing_type": "rent","search": "Sukhumvit","min_price": 15000,"max_price": 50000,"bedrooms": 1,"max_properties": 50}
Paste DDproperty search URLs:
{"mode": "url","urls": ["https://www.ddproperty.com/en/property-for-sale/in-bangkok-th10","https://www.ddproperty.com/en/property-for-rent/in-phuket-th83"],"max_properties": 50}
Recurring monitoring of a Phuket sale search:
{"mode": "search","listing_type": "sale","region": "TH83","incrementalMode": true,"max_properties": 0}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/ddproperty-scraper").call(run_input={"mode": "search", "listing_type": "sale", "region": "TH10"})for listing in client.dataset(run["defaultDatasetId"]).iterate_items():print(listing["title"], listing["price_formatted"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/ddproperty-scraper').call({ mode: 'search', listing_type: 'sale', region: 'TH10' });const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
How max_properties shapes a run
The actor collects listings page by page in the order the site returns them, pushing each page as it goes, and stops once max_properties is reached or the search runs out of pages. Set it to 0 to collect every listing the search returns. Fetching detail pages for coordinates, description and facilities adds one extra request per listing, so a run with Fetch detail pages on takes longer than one with it off; Detail page concurrency controls how many of those detail fetches run in parallel (all of them share one connection, so values above 3 risk being rate-limited).
Resume and recurring updates
- Resume (
resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor skips every listing id it already collected there, so you don't pay twice. - Incremental mode (
incrementalMode) is for recurring runs over the same search. Each listing is classifiedNEW,UPDATED(withchangedFields),UNCHANGED(suppressed and not billed unlessemitUnchangedis on),REAPPEARED, orEXPIRED(only after a run that scanned its search to a genuine natural end, nomax_propertiescap hit and no Resume, and only withemitExpiredon).stateKeynames or shares the stored state; leave it blank and the actor derives the tracked scope from your search filters, so two different searches never share a baseline by accident. With incremental mode off, output is exactly as before. Tracking the same listing both through a search link and through its detail link under onestateKeycounts the first switch as an update, because the detail page carries extra fields. - Ignore fields when detecting changes (
ignoreFieldsForChanges, incremental mode only) lists field names (at any depth, or a dotted path likeagent.rank) that stay in your output but never make a listingUPDATEDor show up inchangedFields. Always excluded from change detection, still returned in the output:recency, a relativeposted_datetext such as "2 days ago", and the query string of image URLs (only the image path is compared). Everything else, includingbadges, price and status, counts as a change.
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.
What gets written to the connector: a condensed, human-readable summary of each listing, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto 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.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | (prefill: search) | search (build a URL from the filters below) or url (paste DDproperty search URLs). |
listing_type | string | sale | sale or rent. Applies in search mode only. |
property_type | string | (none) | CONDO, BUNG, SEMI, VIL, TOWN, LAND, APT, RET, OFF, WAR, BIZ, SHOP. Search mode only. |
search | string | (none) | Free text location (e.g. "Sukhumvit"). Takes priority over region if both are set. Search mode only. |
region | string | (none) | Region, district or area code (e.g. TH10 = Bangkok, TH83 = Phuket, TH1033 = Watthana). Search mode only. |
min_price | integer | (none) | Minimum price in Thai Baht. Search mode only. |
max_price | integer | (none) | Maximum price in Thai Baht. Search mode only. |
bedrooms | integer | (none) | Number of bedrooms, 0 = Studio. Search mode only. |
sort | string | date | date, price, or psf. Search mode only. |
sort_order | string | desc | asc or desc. Search mode only. |
urls | array | (default: one example URL per supported shape; they share the Max properties budget, so the first fills the default 20) | One or more DDproperty search URLs. URL mode only; ignores the search-mode filters above. |
max_properties | integer | 20 | Stop after this many listings (0 = no limit). Caps both modes; the schema maximum is 100000. |
enable_detail_pages | boolean | true | Fetch each listing's detail page for latitude/longitude, description, facilities and project info. |
detail_concurrency | integer | 1 | Parallel detail-page fetches (1 to 5). |
session_ttl_seconds | integer | 300 | How long the actor reuses one browsing session before refreshing it. |
resumeFromRunId | string | (none) | Continue one interrupted run: paste its run or dataset ID. |
incrementalMode | boolean | false | Return only new and changed listings on recurring runs of the same search. |
stateKey | string | (none) | Name or share an incremental-mode state across runs. Leave blank to auto-derive it from your search filters. |
emitUnchanged | boolean | false | Also return (and bill) unchanged listings in incremental mode. |
emitExpired | boolean | false | Also return (and bill) listings that disappeared, in incremental mode. |
ignoreFieldsForChanges | array | [] | Incremental mode only: field names that never count as a change (kept in the output). |
proxy | object | (prefill: Apify Residential proxy, country TH) | Connection settings. |
startUrls | string/array/integer | (none) | Alternative name for urls; switches to URL mode. See Alternative input names. |
searchQueries | string/array/integer | (none) | Alternative: one search per entry. See Alternative input names. |
maxItems | string/array/integer | (none) | Alternative name for max_properties. See Alternative input names. |
minPrice | string/array/integer | (none) | Alternative name for min_price. See Alternative input names. |
maxPrice | string/array/integer | (none) | Alternative name for max_price. See Alternative input names. |
listingType | string/array/integer | (none) | Alternative name for listing_type. See Alternative input names. |
propertyType | string/array/integer | (none) | Alternative property type names such as th_condo. See Alternative input names. |
country | string/array/integer | (none) | Thailand only; sg, my or vn stop the run with a pointer to our sister scrapers. See Alternative input names. |
mcpConnectors | array | (none) | Optional: send a summary of each listing to apps you authorized under Integrations. |
notionParentPageUrl | string | (none) | Notion connector only: page under which listing pages are created. |
maxNotifyListings | integer | 50 | Cap on items written to each connector per run. |
Alternative input names
If you come from another property scraper, these names work as-is and are folded into the inputs above (the alternative name wins when both are set): startUrls (switches to URL mode; strings or {"url": ...}), searchQueries (one search per entry; the cap is the total across all queries), maxItems = max_properties, minPrice / maxPrice = min_price / max_price, listingType = listing_type, and propertyType with th_condo = CONDO, th_single_detached = BUNG, th_semi_detached = SEMI, th_villa = VIL, th_townhouse = TOWN, th_land = LAND, th_apartment = APT (auto / th_all = no filter). This actor covers Thailand only: country sg, my or vn (and sg_, my_, vn_* property types) stop the run with a pointer to our sister Singapore / Malaysia / Vietnam scrapers.
Extra output fields
Every listing also carries type (listing, or agent for agent rows), sourceSite (ddproperty.com), currency (THB), thumbnail (first image), origin (the start URL or search query that produced it), page (search page number), agentProfileUrl and videos (when the site exposes them), plus search-card flags such as isAgentVerified, isBoosted and buildYear when present. floorAreaSqm is the same value as floor_area and agentName the same value as agent_name. These fields are additive and are not part of incremental change detection. Agent rows are additional rows: each agent row pushed is billed as a result event like a listing row (see Pricing).
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"id": 12345678,"title": "Condo for Sale in Watthana, Bangkok","address": "Sukhumvit Soi 24, Khlong Tan, Watthana, Bangkok","listing_type": "sale","property_type": "CONDO","price": 8500000,"price_formatted": "฿8,500,000","price_per_sqm": "฿141,667 / sqm","bedrooms": 2,"bathrooms": 2,"floor_area": 60,"tenure": "Freehold","developer": "Sample Developer Co.","is_developer_listing": true,"agent_name": "Agent Name","agent_id": 445566,"agency_id": 778899,"region": "Bangkok","region_code": "TH10","district": "Watthana","area": "Khlong Tan","latitude": 13.7305,"longitude": 100.5697,"location_source": "detail_page","nearby_mrt": "7 min (560 m) from E5 Asok BTS","nearby_station_id": "E5","badges": ["Freehold", "Condo"],"posted_date": "07 Jul 2026","posted_unix": 1783382400,"images": ["https://www.ddproperty.com/sample/image1.jpg"],"image_count": 1,"url": "https://www.ddproperty.com/en/property/sample-condo-12345678","status": "ACT","description": "Spacious condo close to Asok BTS, sample listing description.","facilities": ["Swimming Pool", "Fitness Centre"]}
Plan Requirement
The site sits behind a managed browser challenge that blocks plain requests on every connection type, so the actor opens a browsing session first and then reuses it. Keep Connection on its prefilled setting (Apify residential proxy, country Thailand) or supply your own proxy URLs. Apify free plans do not include residential proxies: on a free plan the actor logs "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." and continues on a backup connection, so results on a free plan are not guaranteed. A paid Apify plan is recommended. We have not confirmed that datacenter proxies work.
FAQ
How much does it cost?
You pay a result event for every row pushed to the dataset (listing rows and agent rows from agent profile, directory and search links; unchanged rows suppressed in incremental mode are not pushed and not billed), plus the platform's Actor Start event, charged per GB of the run's memory. The Pricing tab shows the current rates. Use Max properties to cap the cost of any run.
Is it legal to scrape DDproperty?
This actor collects only publicly available real estate listing data. You are responsible for how you use it: follow DDproperty's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing photos and agent or developer names may be subject to third-party rights.
Can I get only new or changed listings on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated and reappeared listings for that search (plus expired ones with Emit expired on), and unchanged ones are not billed unless you turn on Emit unchanged.
What's the difference between Resume and Incremental mode?
Resume (resumeFromRunId) continues one specific interrupted run by its run or dataset ID; it is a one-off continuation. Incremental mode remembers a recurring search itself, across scheduled runs, with no ID to paste, and classifies every listing as new, updated, unchanged, reappeared or expired. They solve different problems and can be combined to bootstrap incremental tracking from an existing run.
Why did my run return fewer listings than expected, or none at all?
The run fails with a clear error message if it cannot establish or refresh a working browsing session, if URL mode is used with no URLs or with a URL that is not a DDproperty link, if a link is under /th/ (the site refuses the Thai-locale path; use /en/), or if a pasted Resume ID cannot be read. If the proxy is refused for your account the run ends with "The proxy connection was refused for this account, so no page could be read"; if every results page fails to load it ends with "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search." If the session is established but a search page later comes back blocked or empty, the actor stops collecting from that search and still finishes successfully, since it cannot tell a temporary block apart from a genuinely short result set. If you expected more listings than you got, check the run log for warnings before assuming there really were none.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.
🔗 Want more real estate data?
Pair this actor with these related scrapers from the same team:
| 🏠 PropertyGuru SG Scraper From $0.80/1K. Scrape PropertyGuru.com.sg sale and rental listings with 30+ structured... | 🏠 CommercialGuru SG Property Listings & Agent Contacts Scraper Scrape commercialguru.com.sg Singapore listings at scale. Extract prices, PSF, floor... |
| 🏠 PropertyGuru MY Scraper Scrape PropertyGuru.com.my sale and rental listings with 30+ structured fields, including... | 🏨 Hipflat Scraper Thailand Property & Projects Scrape hipflat.co.th property listings and new-project data across Thailand: price, price... |
| 🏠 iProperty MY Scraper Scrape property listings from iProperty.com.my for sale or rent: price, PSF, bedrooms... | 🏠 591.com.tw Scraper Extract property listings from 591房屋交易網 591.com.tw, Taiwan’s largest real estate... |
💬 Support
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.