Search PropertyGuru Malaysia Room Rentals

Site propertyguru.com.myTask search-room-rent-listingsVersion v8Updated Aug 20, 2026Category real-estate

Search PropertyGuru Malaysia room and rental listings by free-text location or corridor query and extract result cards, rents, agents, and canonical listing URLs. This skill was captured from a live agent session on propertyguru.com.my and publishes here verbatim, exactly as an agent receives it.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Purpose

Search PropertyGuru Malaysia for room-for-rent or general rental listings matching a supplied query, such as a room type plus a transit corridor or neighborhood, and return visible result cards with canonical listing URLs, displayed rent text, bounded card text, and agent names when available. For multi-area searches, optionally probe the same search endpoint from one page and combine results without navigating between every query.

When to Use

Use when the caller provides a free-text search, {query}, and wants matching PropertyGuru rental listings. For a corridor spanning several neighborhoods, run the same direct URL workflow for each optional {area} query and deduplicate results by canonical listing URL. Prefer room-for-rent when the search is specifically for rooms; use property-for-rent when the caller wants broader rental inventory or when the room query is being scraped through that endpoint.

Workflow

  1. Construct the results URL directly; do not visit the homepage or use the search form:
  • Rooms: https://www.propertyguru.com.my/room-for-rent?freetext={url-encoded-query}
  • General rentals or broad room searches: https://www.propertyguru.com.my/property-for-rent?freetext={url-encoded-query}
  1. Navigate to the selected URL with waitUntil: "domcontentloaded", wait for listing-card anchors or client rendering, and solve a Cloudflare challenge if shown. After solving, wait for the listing content to render and do not extract a challenge page.

  2. On the rendered results page, run this evaluator as-is:

(() => {
const clean = s => (s || '').replace(/\s+/g, ' ').trim();
const rentRe = /RM\s*[0-9][0-9,]*(?:\.[0-9]+)?(?:\s*\/\s*(?:month|mo|bulan)|\s*per\s+month)?/i;
const anchors = [...document.querySelectorAll('a[href*="/property-listing/"]')];
const seen = new Set();
const listings = [];
for (const a of anchors) {
const href = new URL(a.getAttribute('href'), location.origin).href.split('#')[0];
if (seen.has(href)) continue;
seen.add(href);
const card = a.closest('[data-testid="listing-card"], article, li, [data-testid*="listing"], [class*="listing-card"], [class*="property-card"]') || a.parentElement;
const text = clean(card?.innerText || a.innerText);
const title = clean(a.innerText) || clean(card?.querySelector('h2,h3,h4,[class*="title"]')?.textContent) || null;
const rent = text.match(rentRe)?.[0] || null;
const agent = clean(card?.querySelector('a[href*="/property-agents/"], [class*="agent"]')?.textContent) || null;
listings.push({url: href, title: title ? title.slice(0, 300) : null, rent, agent, text: text.slice(0, 1200)});
}
const body = document.body?.innerText || '';
const lower = `${document.title}\n${body}`.toLowerCase();
return {
url: location.href,
title: document.title,
blocked: /checking your browser|verify you are human|just a moment|cloudflare|enable javascript and cookies/.test(lower),
resultCount: (body.match(/([0-9,]+)\s+(?:houses?|apartments?|condos?|properties|listings|results)\s+for\s+rent/i) || [])[1] || null,
listings
};
})()
  1. If searching multiple corridor areas, repeat only steps 1–3 for each direct area URL and merge the returned listings, removing duplicate URLs. One-area-at-a-time navigation is the reliable fallback for sequential searches. Apply caller-specified room-type, location, rent, or availability criteria to extracted card text; do not infer a qualification from a title alone when the card text lacks supporting evidence.

  2. Optional same-origin batch shortcut: after any PropertyGuru page has loaded, use one evaluate() call to fetch multiple search-result HTML responses from the relative /property-for-rent?freetext=... or /room-for-rent?freetext=... endpoint. This avoids a navigation round-trip per area when only counts and canonical listing URLs are needed:

async () => {
const queries = [{query: '{query}', label: '{label}'}];
const path = '/property-for-rent';
const results = await Promise.all(queries.map(async ({query, label}) => {
try {
 const response = await fetch(path + '?freetext=' + encodeURIComponent(query), {credentials: 'same-origin'});
 const html = await response.text();
 const title = (html.match(/<title[^>]*>([^<]*)<\/title>/i) || [])[1]?.trim() || '';
 const count = (html.match(/([0-9,]+)\s+(?:Houses?|Apartments?|Condos?|Properties?)\s+for\s+Rent/i) || [])[1] || null;
 const urls = [...new Set([...html.matchAll(/\/property-listing\/[^"'?#\s]+-[0-9]{6,}/g)].map(m => new URL(m[0], location.origin).href))];
 return {label, query, ok: response.ok, status: response.status, title, resultCount: count, urls: urls.slice(0, 50)};
} catch (error) {
 return {label, query, error: String(error).slice(0, 200)};
}
}));
return {origin: location.origin, path, results};
}

Use path = '/room-for-rent' for room-only searches. This batch method reads server-returned HTML and may not include every client-rendered card field; navigate to a result URL and use the DOM extractor when rent text, agent names, or full card text is required.

Site-Specific Gotchas

  • PropertyGuru may show a Cloudflare anti-bot challenge after navigation. Solve it, wait for listing anchors or rendered content, and never extract a challenge page.
  • Search results are reachable directly through /{room-for-rent|property-for-rent}?freetext={url-encoded-query}; the query value must be URL-encoded. The broader property-for-rent endpoint can also return room-query results.
  • For corridor searches, one-area-at-a-time navigation can be more reliable than repeatedly changing the same page or issuing many navigations at once; use the batch shortcut only when session stability permits.
  • The search endpoint also responds to same-origin relative fetch() requests, returning HTML containing the page title, result-count text, and canonical listing paths. Use this only from a PropertyGuru page and handle non-OK responses or challenge HTML.
  • Multiple same-origin queries can be fetched in one evaluation call with Promise.all; this is useful for corridor/area collection, but server HTML extraction is less complete than the rendered DOM-card extractor.
  • Listing cards link to canonical paths containing an opaque numeric ID, typically /property-listing/{slug}-{numeric-listing-id}. Preserve the returned URL and never invent an ID.
  • The same listing can appear through multiple anchors or across overlapping area searches; deduplicate by normalized listing URL.
  • Card class names may change. The extractor prioritizes the stable a[href*="/property-listing/"] pattern and supports data-testid="listing-card", article, and listing/property-card fallbacks. The text field is retained for downstream qualification.
  • Agent links, when present, commonly use /property-agents/; the agent field is optional and may be null.
  • The HTML batch shortcut identifies listing paths with a numeric-suffix pattern; treat it as a URL/count probe, not as proof that each result is currently visible or available.
  • A successful navigation with zero listing anchors may indicate a challenge, incomplete client rendering, an empty search, or changed markup; inspect blocked, title, and resultCount rather than treating it as proof that no listings exist.
  • For detailed verification of a returned listing's live status, rent, or tenant preferences, use the separate verify-rental-listing-live skill with the canonical listing URL.

Expected Output

Return one search object per query containing the results URL, page title, optional displayed result count, a Cloudflare blocked flag, and listings. Each listing contains its canonical url, visible title when available, extracted rent when available, optional agent, and bounded card text for qualification. When multiple area queries are used, return the deduplicated union of these listing objects. If the same-origin batch shortcut is used, return one probe result per query with its label, query, HTTP status, title, optional result count, and deduplicated canonical listing URLs; enrich those URLs with the DOM extractor when full card fields are needed.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=propertyguru.com.my&task=search-room-rent-listings