CommercialGuru SG Property Listings & Agent Contacts Scraper
Pricing
from $1.00 / 1,000 results
CommercialGuru SG Property Listings & Agent Contacts Scraper
Scrape commercialguru.com.sg Singapore listings at scale. Extract prices, PSF, floor area, tenure, property type, images, agent contacts, coordinates, nearby MRT stations and more. Ideal for commercial real estate analytics, investment research and market intelligence.
Pricing
from $1.00 / 1,000 results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
1
Bookmarked
12
Total users
3
Monthly active users
14 hours ago
Last modified
Share
CommercialGuru SG Scraper: Prices, PSF, Agents & Coordinates
CommercialGuru SG Scraper turns commercialguru.com.sg into a clean commercial property data API. Get office, retail, warehouse, factory, land and 6 other Singapore commercial property types for sale or for rent, with price, price per square foot, floor area, tenure, agent contacts, district and nearest MRT station. Search by filters or paste your own CommercialGuru links, add exact coordinates and full listing-page detail on demand, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.
Why This Scraper?
- Two ways to collect data. Build a search from filters (listing type, property type, district, price, floor area) or paste CommercialGuru search and listing URLs directly.
- 11 commercial property types. Office, retail, shop/shophouse, warehouse, factory, industrial, land, food and beverage, business park and dormitory, both for sale and for rent.
- Coordinates on demand. Turn on coordinate enrichment to add latitude and longitude from CommercialGuru's own map data, or fetch each listing's detail page for exact coordinates plus agent phone, postal code and more.
- District-aware. Filter and read results by Singapore's D01-D28 district codes, each already mapped to a readable area name.
- Built for monitoring. Incremental mode returns only new and changed listings on recurring runs, and a blocked run fails loudly instead of returning an empty dataset.
- Resume large pulls. Point a new run at a previous run or dataset id and it skips everything already collected there.
- Multiple locations in one run. Search several districts or free-text areas at once and get one combined dataset.
Use Cases
- Commercial property investment research: collect prices, PSF and tenure across districts to compare deal quality and spot underpriced listings.
- Market intelligence and reporting: build a dataset of active office, retail and industrial supply by district for market reports.
- Lead generation for agents and brokers: pull agent names, agency and CEA license numbers alongside the listings they represent.
- Site selection and location analysis: filter warehouses and factories by floor area and proximity to MRT stations.
- Portfolio and competitor monitoring: schedule the actor with incremental mode to track new listings and price changes on a search over time.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
id | 500073292 |
title | "Novena Medical Center" |
address | "10 Sample Drive" |
listing_type | "sale" (or "rent") |
property_type | "Office" |
price / price_formatted | 3800000 / "S$ 3,800,000" |
price_psf | "S$ 4,973.82 psf" |
floor_area / land_area | 764 / 5000 (sqft) |
tenure | "99-year Leasehold" |
developer / is_developer_listing | "Sample Developer Pte Ltd" / false |
agent_name / agent_id / agent_license | "Sample" / 1758000 / "R0123000" |
agency_name | "Sample PTE LTD" |
district / district_code | "Newton / Novena" / "D11" |
region | "Central (D09-11)" |
nearby_mrt | "2 min (170 m) from NS20 Novena MRT Station" |
badges | ["Office", "99-year Leasehold"] |
posted_date / posted_unix | "2 Apr 2026" / 177508000 |
recency | "Listed on Apr 02, 2026 (30m ago)" |
images / image_count | array of image URLs / 1 |
url | link to the listing on commercialguru.com.sg |
status | "ACT" |
latitude / longitude / location_source | added when coordinate enrichment or detail pages are on |
changeType | incremental mode only: NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED |
With Fetch detail pages turned on, each listing also carries a full description and description_headline, postal_code, exact latitude/longitude from the listing page itself, agent_phone and agent_title, a full image gallery (beyond the 10-image card cap), structured nearby_mrt_stations and nearby_schools (each with distance and walk time), floor_level, rental_type, unit_condition, unit_features, lease_term, amenities, available_date and a detail_fetched flag; these fields are omitted, not blank, when detail fetching is off.
How to Use
- Pick a mode:
search(build a URL from filters) orurl(paste CommercialGuru links). - In search mode, set listing type, property type, district or free-text location, and any price or floor area filters.
- Set Max properties and Max pages to control run size and cost, and turn on coordinate or detail enrichment if you need them.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.
Search offices for rent in a district:
{"mode": "search","listing_type": "rent","property_type": "office","district": "D01","max_properties": 50,"max_pages": 5}
Shops for sale by price and location, with coordinates:
{"mode": "search","listing_type": "sale","property_type": "shop","search": "Orchard","min_price": 500000,"max_price": 5000000,"enable_coordinates": true,"max_properties": 100}
Warehouses by floor area, sorted by price:
{"mode": "search","listing_type": "rent","property_type": "warehouse","min_floor_area": 5000,"max_floor_area": 20000,"sort": "price","sort_order": "asc"}
Scrape specific URLs with full listing detail:
{"mode": "url","urls": ["https://www.commercialguru.com.sg/office-for-rent?district_code=D01","https://www.commercialguru.com.sg/shop-for-sale?freetext=Orchard"],"fetch_details": true,"max_properties": 50}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/commercialguru-sg-scraper").call(run_input={"mode": "search", "listing_type": "sale", "district": "D01"})for listing in client.dataset(run["defaultDatasetId"]).iterate_items():print(listing["title"], listing.get("price_formatted"))
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/commercialguru-sg-scraper').call({ mode: 'search', listing_type: 'sale', district: 'D01' });const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Resume and recurring updates
There are two different things here, pick the one that matches what you are doing:
| Need | Use |
|---|---|
| A crawl stopped and should continue | resumeFromRunId |
| Run the same search every day and receive only changes | incrementalMode |
| Keep separate daily campaigns for similar searches | distinct stateKey values |
| Run a normal full snapshot | leave both off |
Resume (resumeFromRunId) continues one specific interrupted or previous crawl: paste a run id or dataset id and this run skips listings already collected there, returning only the remaining new ones.
Incremental mode (incrementalMode) is for a schedule, for example daily: the actor remembers the previous run of the same search by itself, so you never paste a run id. The first run returns everything as NEW. Later runs return only NEW and UPDATED listings by default (with changedFields on UPDATED rows), and duplicates and unchanged listings are suppressed, never pushed and never billed. Turn on emitUnchanged only when you also want those rows returned and billed for. State is kept separately per search or URL and filter setup automatically; set stateKey to name or deliberately share a monitoring campaign.
REAPPEARED and EXPIRED are not produced by default. EXPIRED tombstones are only created when emitExpired is on, and only once a run has fully scanned the tracked search (not capped by Max properties or Max pages, and not a resume). REAPPEARED can then only happen to a listing that a prior run already tombstoned EXPIRED this way: without that earlier emitExpired run, a listing that briefly drops out of results and comes back later is simply classified UPDATED or left UNCHANGED against its last known values, not REAPPEARED. If you want reappearance tracking, turn emitExpired on for your scheduled runs.
Identity is tracked by id, commercialguru.com.sg's own listing id; a listing without one is dropped before it reaches change detection. One field is excluded from change detection: recency (for example "Listed on Aug 03, 2026 (2h ago)") is a relative-age string the site renders at request time, so it drifts with elapsed time alone, not real content. It is still returned in full on every row, just never used to decide UPDATED. posted_date/posted_unix, the real "when was this posted" fields, stay in change detection.
If Fetch detail pages is on and a listing's own detail-page fetch fails on a given run, that listing's detail-only fields are treated as unknown for this run, not removed: the actor carries the last successful detail snapshot forward before comparing, so a transient per-listing failure is reported UNCHANGED and never billed, and recovers cleanly once the fetch succeeds again. A genuine change to any field the fetch did return, including price, is still reported normally.
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 have not 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 | search | search (build a URL from filters) or url (use provided URLs). |
listing_type | string | sale | sale or rent. |
property_type | string | none | Filter by commercial property type. See Property Types below. |
locations | array | none | One or more free-text locations to search; each is searched separately and results combined. Takes priority over search. |
search | string | none | Single free-text location search, e.g. "Raffles Place". |
district | string | none | Singapore district code D01-D28. See Districts below. |
min_price | integer | none | Minimum price in SGD. |
max_price | integer | none | Maximum price in SGD. |
min_floor_area | integer | none | Minimum floor area in sqft. |
max_floor_area | integer | none | Maximum floor area in sqft. |
sort | string | date | date, price, psf or area. |
sort_order | string | desc | asc or desc. |
urls | array | none (prefill: one CommercialGuru sale-search URL) | CommercialGuru search or listing URLs, used in url mode. |
enable_coordinates | boolean | false | Add latitude/longitude to listings from CommercialGuru's own map data. Adds roughly 30-60s to the run. |
fetch_details | boolean | false | Fetch each listing's own detail page for description, agent phone, exact coordinates, postal code, full image gallery, nearby MRT and schools, lease term, condition, amenities and floor level. Slower and uses more requests. |
max_properties | integer | 10 | Maximum listings to scrape (0 = unlimited). |
max_pages | integer | 5 | Maximum search result pages to scrape (20 listings per page). |
resumeFromRunId | string | none | Continue one specific previous run or dataset: listings already collected there are skipped. For recurring monitoring, use Incremental mode instead. |
incrementalMode | boolean | false | Daily or recurring monitoring of the same search. First run returns everything as NEW; later runs return only NEW/UPDATED by default. |
stateKey | string | none | Names this monitoring campaign, or leave empty to derive one automatically from the search and filter settings. |
emitUnchanged | boolean | false | Also return listings unchanged since the last run, marked UNCHANGED. Adds and bills extra rows you already have. |
emitExpired | boolean | false | Also return listings from a previous run no longer found, marked EXPIRED, once a run has fully scanned the search. Adds and bills extra synthetic rows, and is required for REAPPEARED tracking to ever occur. |
proxy | object | none (prefill: Apify Proxy, RESIDENTIAL group, country SG) | Connection settings. The actor also defaults to a Singapore residential connection automatically when this field is left out entirely. |
mcpConnectors | array | none | Optional MCP connectors (Notion, Linear, Airtable, Apify) to pipe a condensed summary of results into. |
notionParentPageUrl | string | none | Notion parent page URL or id. Only used when a Notion connector is selected in mcpConnectors. |
maxNotifyListings | integer | 50 | Cap on items written to each MCP connector per run. Does not affect the dataset. |
Property Types
| Code | Type |
|---|---|
| office | Office |
| retail | All Retail |
| shop | Shop / Shophouse |
| shophouse | Shophouse |
| warehouse | Warehouse |
| factory | Factory / Industrial |
| industrial | All Industrial |
| land | Commercial Land |
| food_beverage | Food & Beverage |
| business_park | Business / Science Park |
| dormitory | Dormitory |
Singapore Districts
| Code | Area | Code | Area |
|---|---|---|---|
| D01 | Raffles Place / Marina | D15 | East Coast / Marine Parade |
| D02 | Chinatown / Tanjong Pagar | D16 | Bedok / Upper East Coast |
| D03 | Alexandra / Commonwealth | D17 | Changi Airport / Changi Village |
| D04 | Harbourfront / Telok Blangah | D18 | Pasir Ris / Tampines |
| D05 | Buona Vista / West Coast | D19 | Hougang / Punggol / Sengkang |
| D06 | City Hall / Clarke Quay | D20 | Ang Mo Kio / Bishan / Thomson |
| D07 | Beach Road / Bugis | D21 | Clementi Park / Upper Bukit Timah |
| D08 | Farrer Park / Serangoon Rd | D22 | Boon Lay / Jurong / Tuas |
| D09 | Orchard / River Valley | D23 | Bukit Batok / Bukit Panjang |
| D10 | Tanglin / Holland / Bukit Timah | D24 | Lim Chu Kang / Tengah |
| D11 | Newton / Novena | D25 | Admiralty / Woodlands |
| D12 | Balestier / Toa Payoh | D26 | Mandai / Upper Thomson |
| D13 | Macpherson / Potong Pasir | D27 | Sembawang / Yishun |
| D14 | Eunos / Geylang / Paya Lebar | D28 | Seletar / Yio Chu Kang |
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"id": 500073292,"title": "Novena Medical Center","address": "10 Sample Drive","listing_type": "sale","property_type": "Office","property_type_group": "O","price": 3800000,"price_formatted": "S$ 3,800,000","currency": "SGD","price_psf": "S$ 4,973.82 psf","floor_area": 764,"tenure": "99-year Leasehold","agent_name": "Sample","agent_id": 1758000,"agent_license": "R0123000","agency_name": "Sample PTE LTD","district": "Newton / Novena","district_code": "D11","region": "Central (D09-11)","nearby_mrt": "2 min (170 m) from NS20 Novena MRT Station","latitude": 1.3000,"longitude": 103.000,"location_source": "map_api","badges": ["Office", "99-year Leasehold"],"posted_date": "2 Apr 2026","posted_unix": 177508000,"recency": "Listed on Apr 02, 2026 (30m ago)","images": ["https://sg1-cdn.pgimgs.com/listing/500073292/UPHO.123456.V800/Novena-Sample.jpg"],"image_count": 1,"url": "https://www.commercialguru.com.sg/listing/for-sale-novena-medical-center-Sample","status": "ACT"}
Plan Requirement
The default connection setting works out of the box: the actor uses a Singapore residential connection automatically, and retries with a fresh connection when one is refused. Turning on coordinate enrichment or detail-page fetching adds a small per-item charge for the extra data those options return, shown on the Pricing tab alongside the base per-record rate.
FAQ
How much does it cost?
You pay per record returned, plus a small additional charge only for listings that actually receive coordinate or detail-page enrichment. Unchanged listings suppressed by incremental mode are never billed. The Pricing tab shows the current rates. Use Max properties and Max pages to cap the cost of any run.
Is it legal to scrape CommercialGuru?
This actor collects only publicly available commercial property listing data. You are responsible for how you use it: follow CommercialGuru's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing facts such as price and floor area are generally data, but photos and agent contact details 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 and updated listings by default, and unchanged ones are not billed. Turn on Emit expired as well if you also want listings that disappeared, and want later reappearances tracked as REAPPEARED rather than a plain UPDATED or UNCHANGED.
Why did my run return fewer listings than expected?
Check Max properties and Max pages first; a capped run stops once either limit is reached. If detail-page fetching is on and one listing's detail page could not be fetched, that listing is still returned with its search-card fields; only its detail-only fields are missing. In Incremental mode the last known detail fields are carried forward instead, so the listing is not falsely reported as changed.
Why did my run fail instead of returning an empty dataset?
The actor fails loudly, instead of returning zero listings, when it could not read a single result page: that means every connection attempt was refused, which is a connection problem, not an empty search. It also fails if the run's time limit is reached before any page can be read, if url mode is used with no URLs, or if a resumeFromRunId cannot be resolved to a readable run or dataset. If some pages were read successfully but others were not, you still get what was found, and the run does not fail. Simply run it again in a few minutes if it fails on a connection problem.
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... | ๐ DDProperty TH From $1/1K. Extract property listings from ddproperty.com Thailand at scale. Get... |
| ๐ PropertyGuru MY From $1/1K. Fast, reliable scraper for propertyguru.com.my. Extract sale and rent... | ๐ Propertyfinder UAE From $0.8/1K. Extract property listings from propertyfinder.ae at scale. Get... |
| ๐ iProperty MY Scraper Scrape property listings from iProperty.com.my for sale or rent: price, PSF, bedrooms... | ๐ Funda in Business Scraper Scrape Funda in Business commercial listings: offices, retail, industrial, catering and... |
๐ Browse all abotapi scrapers
๐ฌ Support & custom scrapers
- ๐ 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 abotapi@proton.me or message Telegram @abotapi.
- โญ Enjoying it? A quick review on the actor page helps other users find it.