Mudah.my Cars, Property, Jobs & Marketplace Scraper avatar

Mudah.my Cars, Property, Jobs & Marketplace Scraper

Pricing

from $1.00 / 1,000 listing results

Go to Apify Store
Mudah.my Cars, Property, Jobs & Marketplace Scraper

Mudah.my Cars, Property, Jobs & Marketplace Scraper

Scrape Mudah.my listings across every category: cars, motorcycles, property, mobiles, electronics, home, hobbies, jobs and services. Search by keyword, filter by category, state and price, or paste URLs. Rich records with seller, store verification, images and category-specific specs.

Pricing

from $1.00 / 1,000 listing results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

0

Bookmarked

8

Total users

2

Monthly active users

7 days ago

Last modified

Share

Mudah.my Scraper: Cars, Property, Mobiles, Jobs & Classifieds Listings

Mudah.my Scraper turns Mudah.my, Malaysia's classifieds marketplace, into a clean structured data feed. Get listings across cars, motorcycles, property, apartments and condos, computers, mobiles and gadgets, home and furniture, hobbies and collectibles, business equipment, jobs, and services, each with seller and store details, images, timestamps, and category-specific specs (vehicle make/model/mileage, property rooms/size/title type, and more). Search by keyword and filters, or paste listing, category or search links directly, then export to JSON, CSV or Excel, or pull results straight into your app through the API.

Why This Scraper?

  • All categories in one Actor. Cars, motorcycles, property, apartments and condos, computers, mobiles and gadgets, home and furniture, hobbies and collectibles, business equipment, jobs, and services.
  • Two ways to start. Keyword and filter search, or paste Mudah.my listing, category, or keyword-search links directly.
  • Narrow results your way. Filter by category, Malaysian state and listing type in search mode; price range also narrows category and search-style pasted links.
  • Category-aware specs extracted automatically. Vehicle make, model, manufacturing year, mileage, transmission, fuel type, and loan estimates for cars; property type, rooms, bathrooms, size, and title type for property.
  • Rich seller and store data. Seller name and account type, store id and verification status, seller badges such as Verified Dealer, primary image, image counts, and full listing timestamps.
  • Built for monitoring. Incremental mode tracks a saved search across scheduled runs and classifies every listing as NEW, UPDATED or UNCHANGED by default (EXPIRED and REAPPEARED need Emit expired), or resume one specific interrupted crawl instead with resumeFromRunId.
  • Optional export into your apps. Send results to Notion, Linear, Airtable, or Apify through MCP connectors.

Use Cases

  • Car and property buyers: track new listings matching a price range, state, and category as soon as they're posted.
  • Classifieds resellers: monitor Mobiles & Gadgets or Home & Furniture listings across every state for pricing and arbitrage opportunities.
  • Market and pricing researchers: build price datasets across categories to track trends in Malaysia's secondhand market.
  • Real estate and rental platforms: aggregate property and apartment/condo listings for a comparison or aggregator site.
  • Recruiters and job boards: pull the Jobs category to cross-post listings or analyze hiring volume by state.
  • Lead generation tools: watch for new for-sale listings from verified dealers and stores in a specific category.

Data You Get

Sample shape: values are illustrative placeholders, not from a live listing.

FieldExample
id"100000001"
title"Sample Listing Title"
url"https://www.mudah.my/sample-listing-100000001.htm"
priceLabel"RM 1,234"
price1234
currency"MYR"
categoryName"Cars"
listingType"sell"
condition.name"Second-hand (Used)"
regionName / subarea"Selangor" / "Sample Subarea"
sellerName"Sample Seller"
sellerVerifiedtrue
seller.storeVerified / seller.badgestrue / ["Verified Dealer"]
images.primaryUrl / images.countimage URL / 5
timestamps.listedAt / timestamps.expiresAt"2026-01-01T00:00:00.000Z" / "2026-02-01T00:00:00.000Z"
car.make.name / car.year / car.mileagecars only: "Sample Make" / 2020 / 50000
property.rooms / property.size.valueproperty only: "3" / 1200
previousPrice / discountAmount / discountPercentthe price before a markdown, and the drop (null when the seller did not mark the price down)
urgent / categorisedBadgestrue when the seller flagged the ad URGENT / seller and ad badges, e.g. { "seller": [...], "ad": [...] }
listingId / typeId / transactionTypelisting id, type code (s sell, u let) and type name (sell, let)
description / galleryImageUrls / detailsInclude listing details only: the seller's full text (line breaks kept), every photo URL (high resolution), and a details object with specs, videoUrls, chatEnabled, whatsappEnabled, sellerOnline, sellerAvatarUrl, storeName, storeSince, companyRoc, and for property agents firmSummary, firmType, estateAgentNumber, agentIdentity, agentNumber (each null when the page does not show it)
reviews[] (see note below)
scrapedAt"2026-01-01T00:00:00.000Z"
changeTypeincremental mode only, e.g. "NEW"
changedFieldsincremental mode, UPDATED rows only, e.g. ["price", "priceLabel"]

Every record also carries: adId, priceAlias, categoryId and categoryLevel1Name, regionId, the full seller object (userId, accountType, companyAd, storeId), images.mediaCount, the full timestamps object (modifiedAt, origDate, listTs), highlights, bundle, and raw (the complete upstream attribute passthrough, so nothing is ever dropped). Car listings also carry car.transmission, car.fuelType, car.engineCapacity, car.carType, car.monthlyPayment, and car.carLoan; property listings also carry property.type, property.bathrooms, property.titleType, and property.building. Mudah.my does not operate a seller star-rating or written-review system, so reviews is always an empty array; seller trust is instead surfaced through sellerVerified, store verification, and seller badges. Incremental mode also adds firstSeenAt and lastSeenAt to every row.

How to Use

  1. Pick a mode: search (keyword and filters) or url (paste Mudah.my listing, category, or search links).
  2. In search mode, add keywords and narrow by category, state, listing type, and price range. In URL mode, paste the links you want scraped. If you leave Start URLs untouched, a URL-mode run scrapes the ten example links (a keyword search within a category, a category page, a state page, an all-categories keyword search, a page-2 category link, a page-2 keyword link, a price-filtered link, a rental page, a long-slug category page and one listing page), so clear or replace them to scrape only your own links. Category pages, state pages (for example /selangor/cars-for-sale), rental pages (/malaysia/properties-for-rent), keyword links (?q=) and page links (?o=2, mapped to an approximate start position of about 46 results per page, since the live feed shifts between fetches; the start position is logged) are translated automatically; a link with no recognisable category, state or keyword logs a warning that it scrapes all listings.
  3. Set Max items (and optionally Max pages) to control run size and cost, then click Start.
  4. Download the dataset as JSON, CSV or Excel, or read it through the API.

Search a keyword across all categories:

{
"mode": "search",
"queries": ["iphone"],
"maxItems": 20
}

Search cars in one state within a price range:

{
"mode": "search",
"queries": ["honda civic"],
"category": "cars",
"state": "selangor",
"listingType": "for-sale",
"minPrice": 50000,
"maxPrice": 150000,
"maxItems": 50
}

Browse a whole category by state, no keyword:

{
"mode": "search",
"category": "property",
"state": "kuala-lumpur",
"maxItems": 100
}

Scrape pasted URLs (a category/search link plus an individual listing link):

{
"mode": "url",
"startUrls": [
"https://www.mudah.my/malaysia/cars-for-sale?q=honda+civic",
"https://www.mudah.my/sample-listing-100000001.htm"
],
"maxItems": 30
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/mudah-my-scraper").call(run_input={"mode": "search", "queries": ["iphone"], "maxItems": 20})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
print(listing["title"], listing["priceLabel"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/mudah-my-scraper').call({ mode: 'search', queries: ['iphone'], maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Max items, Max pages and filters

maxItems caps the total listings returned across every search and URL in the run (maximum 100000; 0 uses that maximum, and the run stops earlier when results are exhausted). When you supply several keywords at once, that budget is split evenly across them so every keyword gets a fair share instead of the first one consuming the whole cap. maxPages optionally bounds how many result pages each search or URL walks; leave it empty to walk pages until maxItems is reached or a search's results run out, with a hard limit of 500 pages per search or URL.

Category, state and listing type filters apply in search mode only; a pasted search or category link carries its own region, category and type, so those three inputs are ignored for it. Price range narrows both keyword searches and category/search-style pasted links. A pasted link to one specific listing page is looked up directly and returned if found (subject to Max items, Resume and Incremental rules); the category, state, listing type and price filters have no effect on it.

Include listing details

Search results alone carry the title, price, photos count, seller and category fields, but not the seller's text or the full photo gallery. Turn on Include listing details (includeDetails, off by default) and the actor also opens each listing's own page and adds description, galleryImageUrls and a details object (specs table, videos, whether chat and WhatsApp are offered, whether the advertiser is online, store name, and the agent and firm numbers property agents publish). Phone numbers are never collected.

It costs one extra page request per listing, so runs take longer; the number of rows and the billing unit (one result per row returned) do not change. If a listing's page cannot be read, the row is still returned without these three fields and the run log says how many were missed; in incremental mode the values stored from the last run are kept, so a one-off miss does not mark the listing as changed. Switching the option on or off does not start a new incremental baseline and never marks existing listings UPDATED in either direction: the first run with it on adds the three fields silently, and a run with it off keeps the values stored by the last run with it on (so turning it on again still compares against them). whatsappEnabled is the single WhatsApp contact flag the listing page exposes (true when a WhatsApp contact is offered, null when the page lists no contacts), and the advertiser's online-now flag (sellerOnline) is a live presence indicator that never makes a listing UPDATED.

Resume and recurring updates

Resume (resumeFromRunId) continues one specific interrupted or previous large crawl: paste a run ID or dataset ID and this run skips listings already collected there (matched by listing id), returning only the remaining new ones. A checkpoint is also saved automatically as the run progresses, so an Apify platform migration or a Resurrect of a failed run picks back up in the same run without re-saving or re-charging listings it already collected. No input is needed for the checkpoint; it's automatic.

Incremental mode (incrementalMode) is for scheduling the same search again and again (for example, daily): the actor remembers the previous run of that search itself, so no run ID needs to be pasted. The first run returns every listing as NEW. Later runs classify each listing as NEW, UPDATED (with changedFields), UNCHANGED (suppressed and not billed unless emitUnchanged is on), or REAPPEARED. A listing can only classify REAPPEARED if an earlier run had already marked it EXPIRED, which only happens when that earlier run had emitExpired on and completed a full scan; a listing that has never been marked expired classifies NEW or UPDATED/UNCHANGED instead, never REAPPEARED. A listing is UPDATED when any scraped field changed (price, title, description, images, seller, highlights, timestamps, raw attributes) and changedFields names them; scrapedAt, the per-search rank and the advertiser online flag details.sellerOnline are always excluded, and any path you list in ignoreFieldsForChanges is excluded too (it stays in the output). Changing the ignore list never causes a one-time wave of UPDATED rows. State is isolated per mode/keyword/category/state/listing type/price/urgent-only filter/URL setup automatically; set stateKey to name or deliberately share a monitoring campaign.

Turn on emitExpired to also see listings from a previous run that are no longer found, marked EXPIRED (and billed). This only happens once a run completes a full, uncapped scan of the tracked search: not when maxItems or maxPages cut the walk short, not when resumeFromRunId was used, and not on a URL-mode run that only looked up individual listing URLs (such a run never walks the tracked search, so it cannot prove anything is gone). An EXPIRED row carries the listing exactly as it looked on the last run that still found it.

Combining incrementalMode and resumeFromRunId is only allowed to bootstrap the very first incremental run from an existing crawl, when no incremental baseline is saved yet for that state key. Once a baseline exists, supplying both fails the run with an explanatory error; remove resumeFromRunId or set a different stateKey.

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 (price, category, location, condition, seller, URL) flattened to plain text. The complete record always stays in the Apify dataset.

  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. (If the picker is empty, you haven't authorized a connector yet.)
  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.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch (keyword and filters) or url (paste links).
queriesarrayno default (prefill: ["iphone"])Keywords to search. Leave empty to browse a whole category or state.
categorystringallCategory to restrict to (cars, property, mobiles-gadgets, jobs, and more). Search mode only.
statestringallMalaysian state to restrict to. Search mode only.
listingTypestringallall, for-sale or for-rent.
onlyUrgentbooleanfalseOnly listings flagged URGENT by the seller. Search mode and category or keyword links in URL mode.
startUrlsarrayten example links, one per URL shape (search, category, state, all-categories, paged, price-filtered, rental, long-slug category, listing)URL mode: listing, category, or search pages to scrape.
minPriceintegerno defaultMinimum price in Ringgit. Applies to searches and category/search-style links, not to a pasted single-listing link.
maxPriceintegerno defaultMaximum price in Ringgit. Same scope as minPrice.
maxItemsinteger20Maximum listings to return across all searches / URLs. Maximum 100000; 0 uses that maximum.
maxPagesintegerno defaultBound on result pages walked per search/URL. Leave empty to walk up to the hard limit of 500 pages.
includeDetailsbooleanfalseAlso open each listing's own page for the description, the full gallery and seller/contact flags. One extra page request per listing; row count and billing unit unchanged.
resumeFromRunIdstringno defaultPrevious run ID or dataset ID; listings already collected there are skipped.
incrementalModebooleanfalseDaily/recurring monitoring of this same search.
stateKeystringno defaultOptional name for a monitoring campaign, so its incremental state stays stable or is deliberately shared.
emitUnchangedbooleanfalseIncremental mode only. Also return (and bill) listings unchanged since the last run, marked UNCHANGED.
emitExpiredbooleanfalseIncremental mode only. Also return (and bill) listings no longer found, marked EXPIRED, once a run completes a full scan.
ignoreFieldsForChangesarray[]Incremental mode only. Field paths (dots for nested, e.g. timestamps.modifiedAt) that never make a listing UPDATED; they stay in the output.
proxyobject{ "useApifyProxy": true }Connection configuration. The default is recommended.
mcpConnectorsarrayno defaultOptional MCP connectors to also receive a summary of results.
notionParentPageUrlstringno defaultNotion connector only: page under which item pages are created.
maxNotifyListingsinteger50Cap on items written to each connector per run. Does not affect the dataset.

Output Example

Sample shape: values are illustrative placeholders, not from a live listing.

{
"id": "100000001",
"adId": "100000002",
"url": "https://www.mudah.my/sample-listing-100000001.htm",
"title": "Sample Listing Title",
"price": 65000,
"priceLabel": "RM 65,000",
"priceAlias": null,
"currency": "MYR",
"categoryId": "1000",
"categoryName": "Cars",
"categoryLevel1Name": null,
"category": { "id": "1000", "name": "Cars", "level1Id": null, "level1Name": null },
"listingType": "sell",
"condition": { "id": "1", "name": "Second-hand (Used)" },
"regionId": "8",
"regionName": "Selangor",
"location": { "regionId": "8", "region": "Selangor", "subareaId": "301", "subarea": "Sample Subarea", "raw": null },
"sellerName": "Sample Seller",
"seller": { "userId": "10000000", "name": "Sample Seller", "accountType": 1, "companyAd": false, "storeId": "100000", "storeVerified": true, "badges": ["Verified Dealer"] },
"sellerVerified": true,
"images": { "primaryUrl": "https://img.example.com/images/00/000000000000000000.jpg", "count": 5, "mediaCount": 5 },
"timestamps": { "listedAt": "2026-01-01T00:00:00.000Z", "modifiedAt": "2026-01-01T00:00:00.000Z", "origDate": "2026-01-01T00:00:00.000Z", "expiresAt": "2026-02-01T00:00:00.000Z", "listTs": 1767225600 },
"car": { "make": { "id": "10", "name": "Sample Make" }, "model": { "id": "20", "name": "Sample Model" }, "year": 2020, "mileage": 50000, "transmission": "Automatic", "fuelType": "Petrol" },
"highlights": [],
"bundle": null,
"raw": { "...": "the complete upstream attribute passthrough" },
"reviews": [],
"scrapedAt": "2026-01-01T00:00:00.000Z"
}

Dataset views

The dataset has five ready-made views in the Console: Listings (core fields for every row, including price drop and urgency), Changes (incremental) (change type, changed fields, first/last seen), Vehicles (make, model, year, mileage, transmission, fuel, loan eligibility) Property (type, rooms, bathrooms, size, title type) and Listing details (description, gallery and seller/contact flags, filled when Include listing details is on).

Plan Requirement

Mudah.my serves its listing data from a search interface that answers from ordinary datacenter connections. With Include listing details on, the actor also reads each listing's own page, which in testing loaded from the same datacenter connection with a browser-grade client (some category pages answer a plain client with a browser challenge). The default connection (useApifyProxy: true) is what the actor starts with. A listing page that is refused only moves that one page to the next connection; later pages still start on the default connection. If that connection is refused, the actor tries a residential connection automatically when your plan includes one, and then a backup connection. Apify free plans do not include proxy access or residential proxies, so on a free plan expect the backup connection only: some requests may still fail, and a run that collects nothing ends as FAILED instead of reporting an empty success. For large or frequent runs a paid plan with residential access gives more headroom; pick the group under Connection.

FAQ

How much does it cost?

You pay per listing returned, plus a small one-time run-start charge. The Pricing tab shows the current rates. Use Max items to cap the cost of any run.

This actor collects only publicly available classifieds data. You are responsible for how you use it: follow Mudah.my's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Seller-submitted content such as photos and descriptions may carry their own 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 (plus reappeared ones if you also use Emit expired), and unchanged ones are not billed unless you turn on Emit unchanged.

What's the difference between Resume and Incremental mode?

They solve different problems. Resume (resumeFromRunId) continues one specific interrupted or oversized crawl so you don't re-scrape and re-pay for listings already saved. Incremental mode is for running the same search again and again on a schedule and getting only what changed; it remembers state itself, so you never paste a run ID. Use Resume for a one-off continuation, Incremental mode for recurring monitoring.

Why did my run fail, or come back with fewer listings than expected?

A run ends as FAILED (never as an empty success) in these cases:

  • resumeFromRunId is invalid or unreadable, or incrementalMode and resumeFromRunId are both set while a monitoring baseline already exists for the state key.
  • Every list-page request was refused by the connection: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required". Check your plan, or that your own proxy URLs have valid credentials.
  • No results page could be loaded for another connection reason: "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later." On a free plan the message says residential proxies are unavailable and the backup connection could not load the pages.

On a free plan the log also shows a warning block "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."

If some requests fail but others succeed, the run still completes with what it collected. A run that loaded pages but returned zero items logs "No listings were returned this run" and finishes SUCCEEDED: the search matched nothing, or widen the filters. A pasted link that scrapes everything because it has no recognisable category, state or keyword logs a warning. Multi-search runs also save an OUTPUT record with per-search counts.

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 classifieds data?

Pair this actor with these related scrapers from the same team:

🏷️ Avito.ru Scraper
Scrape structured listings from Avito.ru by region, category, filters, or direct URL...
🏷️ Subito.it Scraper
Scrape Subito.it listings across cars, real estate, marketplace, jobs, and more. Search...
🏷️ Kijiji.ca Scraper
Scrape Kijiji.ca listings across property, vehicles, jobs, electronics, furniture...
🚗 mobile.de Scraper
Scrape mobile.de vehicle listings with 30+ fields including price, mileage, registration...
🚗 Willhaben.at Scraper
Scrape Willhaben.at listings into clean JSON with 40+ structured fields, including...
🚗 Carsales.com.au Scraper
Scrape structured vehicle listings from Carsales.com.au from $1 per 1K results. Built to...

👉 Browse all abotapi scrapers

💬 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.