Marktplaats.nl Scraper - Listings, Prices, Sellers & Reviews
Pricing
from $1.00 / 1,000 listing scrapeds
Marktplaats.nl Scraper - Listings, Prices, Sellers & Reviews
From $1/1K. Scrape Marktplaats.nl listings by keyword or URL. Extract title, price, condition, location and coordinates, photos, seller details, ratings and reviews, shipping, views, and category-specific attributes for cars, bikes, electronics, and more.
Pricing
from $1.00 / 1,000 listing scrapeds
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
0
Monthly active users
7 days ago
Last modified
Categories
Share
Marktplaats.nl Scraper: Listings, Prices, Sellers & Automotive Data
Marktplaats.nl Scraper turns Marktplaats, the biggest classifieds and marketplace site in the Netherlands, into clean structured data. Search by keyword and filters or paste listing and result-page URLs, and get 150+ fields per advert: title, price, condition, full description, every photo, location with GPS coordinates, seller name/type/rating/reviews, shipping options, view and favorite counts, and the complete per-category attribute set (cars, bikes, electronics and more). Turn on Incremental mode for a recurring search that returns only what changed, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.
Why This Scraper?
- Two ways to reach listings. Build a search from keywords plus filters, or paste any listing or result-page URL directly.
- Deep per-category attributes. Automotive listings carry make, model, trim, construction year, mileage, fuel, transmission, body type, energy label, options and more, alongside the 150+ fields every listing gets.
- Seller insight. Seller name, private vs professional, active-since, rating, number of reviews and payment method.
- Location-aware filtering. Centre a search on a Dutch postcode with a radius, or narrow by price range and seller type, all applied to the result pages the search reads anyway, with no extra requests.
- Optional detail enrichment. Switch on full detail pages for view/favorite counts, shipping options, bids, seller reviews and the complete description.
- Duplicate-safe pagination. Every result page is deduplicated by listing id, so paging never returns the same card twice.
- Built for monitoring. Resume continues one interrupted run, and Incremental mode returns only new, updated and reappeared listings on a recurring schedule.
Use Cases
- Resellers and flippers: watch a keyword or category for new listings to spot underpriced items quickly.
- Pricing and market research: build a price-history dataset for a product category (bikes, phones, cars) across the Netherlands.
- Automotive marketplaces and lead generation: collect structured vehicle listings (make, model, mileage, fuel, transmission) for aggregation or outreach.
- Local and postcode-based apps: pull listings within a radius of a postcode to power a local classifieds feed.
- Brand monitoring: watch for unauthorized resale of a brand's products across the marketplace.
- Professional-seller discovery: filter to Commercial sellers in a category to build a list of active dealers or shops.
Data You Get
Sample shape: values are illustrative placeholders, not from a live listing.
| Field | Example |
|---|---|
id / itemId | "m0000000000" |
title | "Elektrische fiets Cortina E-U4" |
url | Marktplaats link to the listing |
priceNumeric | 399.0 |
priceType | "FIXED" (also "MIN_BID", "NOTK", "ON_REQUEST") |
currency | "EUR" |
condition | "Zo goed als nieuw" |
brand | "Cortina" |
description | "Nette fiets, weinig gebruikt, incl. oplader." |
category | { "id": 447, "name": "Fietsen", "parentName": "Fietsen en Brommers" } |
location_cityName | "Utrecht" |
location_latitude / location_longitude | 52.0907 / 5.1214 |
sellerName | "FietsenhuisXYZ" |
sellerType | "CONSUMER" (also "COMMERCIAL"), needs Fetch full detail pages |
sellerRating / sellerReviews | 4.8 / 21, needs Fetch full detail pages |
viewCount / savedCount | 58 / 4, needs Fetch full detail pages |
imageUrl / image_urls | listing photo URLs |
attributes | flattened per-category attributes, e.g. { "condition": "Zo goed als nieuw", "delivery": "Ophalen of Verzenden" } |
shipping | { "method": "DELIVERY", "carrierName": "PostNL", "price": "5,49" }, needs Fetch full detail pages |
date | "Vandaag" |
scrapedAt | "2026-01-01T00:00:00+00:00" |
Vehicle listings additionally carry mileage, fuelType, transmission, bodyType, energyLabel, numberOfDoors, numberOfSeats, horsePower, upholstery, licensePlate, apkUntil and constructionYear, pulled from the per-category attribute set and enriched further from the detail page when Fetch full detail pages is on.
Incremental mode adds four more fields to every returned record, described in prose rather than a table because they only appear in that mode:
changeType: one ofNEW,UPDATED,UNCHANGED,REAPPEAREDorEXPIRED.changedFields: the top-level fields that changed since last seen; only non-empty forUPDATED.firstSeenAt: when this listing was first observed by this monitoring campaign.lastSeenAt: when this listing was last observed.
How to Use
- Pick a mode:
search(keywords + filters) orurl(paste listing or result-page links). - Fill in the fields for that mode: keywords, category, price range and postcode for Search, or the URLs to scrape for URL mode. Seller type and the sort order apply either way.
- Turn on Fetch full detail pages if you need view counts, shipping, bids, seller reviews or the full description, and set Max listings to control run size and cost.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.
Keyword search with detail enrichment:
{"mode": "search","queries": ["fiets"],"fetchDetails": true,"maxListings": 50}
Browse a category with price and seller filters:
{"mode": "search","queries": [],"categoryId": 91,"minPrice": 5000,"maxPrice": 20000,"sellerType": "COMMERCIAL","maxListings": 100}
Search near a postcode:
{"mode": "search","queries": ["bankstel"],"postcode": "1011AB","distanceMeters": 15000,"maxListings": 40}
URL mode (listing or result pages):
{"mode": "url","urls": ["https://www.marktplaats.nl/l/auto-s/","https://www.marktplaats.nl/v/fietsen-en-brommers/fietsen-dames-damesfietsen/m0000000000-elektrische-fiets"],"fetchDetails": true,"maxListings": 60}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/marktplaats-nl-scraper").call(run_input={"mode": "search", "queries": ["fiets"], "maxListings": 20})for listing in client.dataset(run["defaultDatasetId"]).iterate_items():print(listing["title"], listing["priceNumeric"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/marktplaats-nl-scraper').call({ mode: 'search', queries: ['fiets'], maxListings: 20 });const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Caps and cost control
Max listings is the run's real cap: the total number of listings returned across every keyword, category or URL, split proportionally between them. Max pages per search only bounds how many result pages are walked per keyword/category/URL; leave it empty to walk every result page and let Max listings do the capping.
Resume and recurring updates
Two different things, pick the one that matches what you are doing:
- 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, returning only the remaining new listings. An automatic same-run checkpoint also protects against platform migrations and the Resurrect button without any input needed. - 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 asNEW. Later runs return onlyNEW,UPDATEDandREAPPEAREDlistings by default; unchanged listings are suppressed and not charged unlessemitUnchangedis on.EXPIREDrows are only produced after a run that scanned the whole tracked search end to end, uncapped and not resumed; turn onemitExpiredto get them.
Known limitation: combining several overlapping keywords, categories or URLs in one run can stop a later one early. Duplicate protection is tracked per run, not per keyword: once a listing id has been returned by one search in the run, every later search in that same run treats it as already seen. If two of your searches genuinely overlap (for example "fiets" and "fietsen"), a later search can hit a results page made up entirely of listings the earlier search already claimed, and the actor reads that as reaching the natural end of the results, stopping before it reaches further pages that hold listings neither search has seen yet. The visible effect is fewer listings than expected from the second search, and, with Incremental mode and emitExpired on, listings that are genuinely still live can be marked EXPIRED because that search's scan looked complete but was not. Workaround: run each keyword, category or URL you need an exhaustive or Incremental-mode scan of in its own actor run instead of combining several overlapping ones together; unrelated (non-overlapping) keywords in one run are not affected.
The same rule affects Resume when the results have shifted. Resume and the automatic restart checkpoint work by skipping every listing id the earlier run already collected. If new listings were posted at the top of the results since then (for example, one new ad pushes every result down by one), the first results page holds a few fresh listings and the next page consists entirely of listings the earlier run already has, and the actor stops there. The resumed run then returns only those few newest listings and never reaches the part the earlier run had not collected. Workaround: resume soon after the interruption; if a resumed run returns far fewer listings than you expected, run the same search again without Resume, or use Incremental mode for anything you collect on a recurring basis (it is not affected).
Pasted result-page URLs keep only the keyword and the main category. Subcategories and filters you set on the site before copying the link (price, distance, brand and so on) are not carried over, so the run walks the broader search. For narrower results, use Search mode with categoryId, minPrice/maxPrice and postcode/distanceMeters.
Send results into your apps (MCP connectors)
Optionally pipe the scraped listings 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 listings should be created. - Use
maxNotifyListingsto cap how many listings are written to each connector per run.
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 (keywords + filters) or url (paste URLs). |
queries | array | [] (prefilled ["fiets"]) | Search keywords (search mode). Leave empty to browse a whole category. |
categoryId | integer | (none) | Numeric Marktplaats category id to restrict results, for example 91 for Auto's or 447 for Fietsen. |
minPrice | integer | (none) | Keep listings priced at or above this amount, in euros. |
maxPrice | integer | (none) | Keep listings priced at or below this amount, in euros. |
postcode | string | (none) | Dutch postcode to centre a distance search on, for example 1011AB. |
distanceMeters | integer | (none) | Radius in meters around the postcode to search within. |
urls | array | (none) | Listing or result-page URLs (URL mode). |
sellerType | string | any | any, CONSUMER (private sellers) or COMMERCIAL (professional sellers). Applies to a keyword search and to pasted URLs. |
sortBy | string | OPTIMIZED | OPTIMIZED, SORT_INDEX, PRICE, or DATE. Applies to a keyword search and a pasted result-page URL. |
sortOrder | string | DECREASING | DECREASING (highest/newest first) or INCREASING. |
fetchDetails | boolean | true | Visit each listing page for view/favorite counts, shipping, bids, seller reviews/rating and the full description. |
maxPages | integer | (none) | Optional bound on result pages walked per keyword/category/URL. Leave empty to walk every result page; Max listings does the real capping. |
maxListings | integer | 20 | The run cap: total listings to return across all searches. Set 0 for unlimited. |
resumeFromRunId | string | (none) | ID of a previous run of this actor, or a dataset ID. Listings already collected there are skipped, so this run returns only the remaining new listings. |
incrementalMode | boolean | false | Turn on for daily or recurring monitoring of the same search. Later runs return only new, updated and reappeared listings. |
stateKey | string | (none) | Name or share an incremental-mode monitoring campaign. Leave empty to derive a key automatically from the search and filters. |
emitUnchanged | boolean | false | Incremental mode only. Also return, and bill, listings unchanged since the last run. |
emitExpired | boolean | false | Incremental mode only. Also return, and bill, listings no longer found. Only produced after a complete, uncapped, non-resumed scan. |
proxy | object | Apify Proxy | Connection settings used for all requests. The default connection works out of the box. |
residentialCountries | array | ["NL", "BE", "DE", "FR", "GB"] | Countries rotated through when the premium connection is used as a fallback. |
maxResidentialRequests | integer | 200 | Caps how many requests may use the premium connection in one run. Set 0 to disable it entirely. |
backupProxyUrl | string | (none) | Optional last-resort proxy gateway URL. Leave blank to use the built-in default. |
mcpConnectors | array | (none) | Optional MCP connectors to pipe a copy of each listing into (Notion, Linear, Airtable, Apify). |
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. Does not affect the dataset. |
Output Example
Sample shape: values are illustrative placeholders, not from a live listing.
{"id": "m0000000000","itemId": "m0000000000","title": "Elektrische fiets Cortina E-U4","url": "https://www.marktplaats.nl/v/fietsen-en-brommers/fietsen-dames-damesfietsen/m0000000000-elektrische-fiets","priceNumeric": 399.0,"priceType": "FIXED","currency": "EUR","condition": "Zo goed als nieuw","brand": "Cortina","description": "Nette fiets, weinig gebruikt, inclusief oplader.","categoryId": 447,"category": { "id": 447, "name": "Fietsen", "parentName": "Fietsen en Brommers" },"location_cityName": "Utrecht","location_latitude": 52.0907,"location_longitude": 5.1214,"imageUrl": "https://images.marktplaats.com/api/v1/listing/images/0000.jpg","image_urls": ["https://images.marktplaats.com/api/v1/listing/images/0000.jpg"],"attributes": { "condition": "Zo goed als nieuw", "brand": "Cortina", "delivery": "Ophalen of Verzenden" },"seller": {"id": 0,"name": "FietsenhuisXYZ","type": "CONSUMER","activeSince": "1 jaar","paymentMethod": "ideal"},"sellerName": "FietsenhuisXYZ","sellerType": "CONSUMER","sellerRating": 4.8,"sellerReviews": 21,"viewCount": 58,"savedCount": 4,"shipping": { "method": "DELIVERY", "carrierName": "PostNL", "price": "5,49" },"date": "Vandaag","scrapedAt": "2026-01-01T00:00:00+00:00"}
Plan Requirement
The default connection works out of the box on every Apify plan and rotates automatically when one is refused. For large or frequent runs, or with detail enrichment on every listing, the premium connection fallback gives more headroom; it is budgeted by Premium connection request budget so a run never spends more of it than you allow.
FAQ
How much does it cost?
You pay per listing returned, with optional detail enrichment billed only when you switch Fetch full detail pages on. The Pricing tab shows the current rates. Use Max listings to cap the cost of any run.
Is it legal to scrape Marktplaats.nl?
This actor collects only publicly available listing data. You are responsible for how you use it: follow Marktplaats's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing text, prices and attributes are generally facts, but photos may be subject to third-party rights.
What's the difference between Resume and Incremental mode?
Resume (resumeFromRunId) continues one specific interrupted or previous large run by pasting its run or dataset id. Incremental mode (incrementalMode) is for scheduling the same search again and again, for example daily, and getting back only what changed, without ever pasting an id. Resume is best used soon after the interruption: if even a few new listings were posted in between, a resumed run can stop after returning only those newest ones (see "Known limitation" under How to Use).
Can I run several keywords or URLs together and still get everything?
Usually yes, but if two of them genuinely overlap in the underlying catalogue (for example "fiets" and "fietsen"), a later one in the same run can stop early once it hits a results page made up entirely of listings an earlier search in that run already returned. See "Known limitation" under How to Use for the concrete mechanics and the workaround: give each keyword, category or URL you need a complete scan of its own run when the terms are likely to overlap.
Why did my run fail instead of returning an empty dataset?
If the site refuses every request, the run stops with a clear message so "no listings matched" is never confused with "nothing could be read". Run it again in a few minutes. If only part of a run could be read, you still get what was found.
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 car data?
Pair this actor with these related scrapers from the same team:
| ๐ AutoTrader UK Scraper Pull structured vehicle listings from autotrader.co.uk at scale. Search by filters or use... | ๐ท๏ธ 2dehands & 2ememain Scrape classifieds from 2dehands.be and 2ememain.be by keyword, filters, or URLs. Returns... |
| ๐ Willhaben.at Scraper From $1/1K. Scrape Willhaben.at listings into clean JSON with 40+ structured fields... | ๐ Cars.com Scraper Scrape cars.com listings by make, ZIP and radius. 90+ fields per car: price, MSRP... |
| ๐ท๏ธ Craigslist Scraper Scrape Craigslist postings in any of 700+ cities worldwide: price, title, posting text... | ๐ Autotrader US Scrape US car listings from Autotrader by make, model, ZIP, price, year, body style... |
๐ 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.