Mudah.my Cars, Property, Jobs & Marketplace Scraper
Pricing
from $1.00 / 1,000 listing results
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
Maintained by CommunityActor stats
0
Bookmarked
7
Total users
3
Monthly active users
14 days ago
Last modified
Categories
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.
| Field | Example |
|---|---|
id | "100000001" |
title | "Sample Listing Title" |
url | "https://www.mudah.my/sample-listing-100000001.htm" |
priceLabel | "RM 1,234" |
price | 1234 |
currency | "MYR" |
categoryName | "Cars" |
listingType | "sell" |
condition.name | "Second-hand (Used)" |
regionName / subarea | "Selangor" / "Sample Subarea" |
sellerName | "Sample Seller" |
sellerVerified | true |
seller.storeVerified / seller.badges | true / ["Verified Dealer"] |
images.primaryUrl / images.count | image URL / 5 |
timestamps.listedAt / timestamps.expiresAt | "2026-01-01T00:00:00.000Z" / "2026-02-01T00:00:00.000Z" |
car.make.name / car.year / car.mileage | cars only: "Sample Make" / 2020 / 50000 |
property.rooms / property.size.value | property only: "3" / 1200 |
reviews | [] (see note below) |
scrapedAt | "2026-01-01T00:00:00.000Z" |
changeType | incremental mode only, e.g. "NEW" |
changedFields | incremental 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
- Pick a mode:
search(keyword and filters) orurl(paste Mudah.my listing, category, or search links). - In search mode, add keywords and narrow by category, state, listing type, and price range. In URL mode, paste the links you want scraped.
- Set Max items (and optionally Max pages) to control run size and cost, then click Start.
- 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 ApifyClientclient = 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 (0 means unlimited, the run then stops 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 every page until maxItems is reached or a search's results run out.
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.
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. State is isolated per mode/keyword/category/state/listing type/price/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.
- 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 | search | search (keyword and filters) or url (paste links). |
queries | array | no default (prefill: ["iphone"]) | Keywords to search. Leave empty to browse a whole category or state. |
category | string | all | Category to restrict to (cars, property, mobiles-gadgets, jobs, and more). Search mode only. |
state | string | all | Malaysian state to restrict to. Search mode only. |
listingType | string | all | all, or for-sale only. Search mode only. |
startUrls | array | no default (prefill: a sample cars-for-sale search URL) | URL mode: listing, category, or search pages to scrape. |
minPrice | integer | no default | Minimum price in Ringgit. Applies to searches and category/search-style links, not to a pasted single-listing link. |
maxPrice | integer | no default | Maximum price in Ringgit. Same scope as minPrice. |
maxItems | integer | 20 | Maximum listings to return across all searches / URLs. Set 0 for unlimited. |
maxPages | integer | no default | Bound on result pages walked per search/URL. Leave empty to walk every result page. |
resumeFromRunId | string | no default | Previous run ID or dataset ID; listings already collected there are skipped. |
incrementalMode | boolean | false | Daily/recurring monitoring of this same search. |
stateKey | string | no default | Optional name for a monitoring campaign, so its incremental state stays stable or is deliberately shared. |
emitUnchanged | boolean | false | Incremental mode only. Also return (and bill) listings unchanged since the last run, marked UNCHANGED. |
emitExpired | boolean | false | Incremental mode only. Also return (and bill) listings no longer found, marked EXPIRED, once a run completes a full scan. |
proxy | object | { "useApifyProxy": true } | Connection configuration. The default is recommended. |
mcpConnectors | array | no default | Optional MCP connectors to also receive a summary of results. |
notionParentPageUrl | string | no default | Notion connector only: page under which item pages are created. |
maxNotifyListings | integer | 50 | Cap 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"}
Plan Requirement
The default connection works out of the box, and the actor rotates connections automatically when one is refused. For large or frequent runs, a residential proxy group gives more headroom; pick it 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.
Is it legal to scrape Mudah.my?
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 only fails outright (status FAILED) for a small set of clear input problems: an invalid or unreadable resumeFromRunId, or supplying both incrementalMode and resumeFromRunId when a monitoring baseline already exists for that state key. If Mudah.my refuses requests during a run, the actor rotates its connection and retries; if a particular search still comes back empty, the run still completes normally (status SUCCEEDED) with a warning logged and whatever other searches or URLs did return. A run with zero items and no error usually means the search matched nothing, or every attempt for that search was refused, try it again in a few minutes.
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:
| 🏷️ Subito.it Scrape Subito.it listings across cars, real estate, marketplace, jobs, and more. Search... | 🏷️ Leboncoin FR Pull public leboncoin.fr listings across cars, property, jobs, electronics, furniture... |
| 🏷️ Avito.ru Scraper From $1/1K. Scrape structured listings from Avito.ru by region, category, filters, or... | 🏷️ Kijiji.ca Scraper Scrape Kijiji.ca listings across property, vehicles, jobs, electronics, furniture... |
| 🚗 mobile.de From $0.8/1K. Scrape mobile.de vehicle listings with 30+ structured fields including... | 🏷️ Tutti.ch Scrape listings from tutti.ch across all 23 categories, including vehicles, property... |
💬 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.