By the end of this tutorial you will have a Python script that pulls classified ads from marktplaats.nl into structured rows: title, price and price type, city, seller, condition, delivery and photo links. The same Actor also reads the Belgian sites 2dehands.be and 2ememain.be.
Marktplaats is the Dutch second-hand marketplace. Marktplaats Scraper (reventadata/marktplaats-scraper) is the Apify Actor built for it: you send a keyword or a category, and it returns one row per listing.
This guide runs it from Python with the Apify API client. The sample rows below come from a real run of the Actor's example task on 15 November 2026 (keyword iphone, site nl, 10 rows), not from an invented example.
Prerequisites
You need three things:
- Python 3.11 or newer (the Apify client for Python 3.x requires it).
- A free Apify account, which you can create with an email address or a GitHub login.
- Your API token, from Settings > API & Integrations in Apify Console.
Install the client and export the token:
pip install -U "apify-client~=3.0"
export APIFY_API_TOKEN="your_apify_token_here"
No scraping code is needed on your side. The Actor does the fetching.
What you'll achieve
You will search marktplaats.nl for iphone, read the first rows in Python and print one line per listing: title, price, how the price is meant, whether the seller looks private or business, and the city.
You will also see why some rows have no price at all. Marktplaats has bidding ads and "see description" ads, and the Actor reports those honestly as null instead of a made-up zero.
The same script works for a bargain hunter watching one model, a reseller comparing asking prices, or an analyst counting business sellers in a category. Only the input changes: another keyword, a category path, the Belgian sites, a price ceiling.
Step 1: Open Marktplaats Scraper
Open the Marktplaats Scraper page on Apify Store. The Try for free button there opens the Actor in Apify Console. This tutorial uses the API from Python, so all you need is the Actor name, reventadata/marktplaats-scraper.
Step 2: Choose the search
The search is described by a handful of input fields:
-
country:nl(marktplaats.nl),be-nl(2dehands.be, Dutch) orbe-fr(2ememain.be, French). Anything else is refused before the run starts. -
searchQueries: a list of keywords. Each keyword is one search across every category. -
categoryPaths: a list of category addresses, the part after/l/in a category URL, such astelecommunicatie/mobiele-telefoons-apple-iphone. A pasted full address works too. At least one ofsearchQueriesandcategoryPathsis required. -
sellers:any,privateorbusiness. -
minPriceandmaxPrice: euro bounds, inclusive. -
excludePromoted,excludeReservedandrequireKeywordMatch: switches, all off by default. -
maxItemsandmaxPages: caps on rows and on pages per search. A page holds 30 listings.
The price, seller and promotion filters are applied by the Actor after it has read a page, so they remove rows but do not reduce the number of pages read. There is no sort option: rows come in the site's own order.
The two Belgian sites show the same ads in two languages, and category slugs differ between them. To read private sellers in a French category on 2ememain.be, without the paid top-of-the-day placements, the input would look like this:
{
"country": "be-fr",
"categoryPaths": ["telecoms/telephonie-mobile-apple-iphone"],
"sellers": "private",
"excludePromoted": true,
"maxItems": 30,
"maxPages": 1
}
Step 3: Run it from Python
This script starts the Actor, waits for it, reads the dataset and prints one line per listing:
import os
from apify_client import ApifyClient
# 1. Initialise the client with your token from the environment
client = ApifyClient(os.environ["APIFY_API_TOKEN"])
# 2. Describe the search: the keyword "iphone" on marktplaats.nl, first page only
run_input = {
"country": "nl",
"searchQueries": ["iphone"],
"sellers": "any",
"maxItems": 10,
"maxPages": 1,
}
# 3. Start the Actor and wait for it to finish
run = client.actor("reventadata/marktplaats-scraper").call(run_input=run_input)
if run is None or run.status != "SUCCEEDED":
status = run.status if run else "unknown"
raise SystemExit(f"Run did not succeed (status: {status}).")
# 4. Read the rows from the run's default dataset
items = client.dataset(run.default_dataset_id).list_items().items
print(f"Retrieved {len(items)} listings.\n")
# 5. Print one line per listing; price is None when the listing shows no amount
for item in items:
price = f"{item['price']:.0f} EUR" if item.get("price") is not None else "no amount"
print(f"{item['title']} | {price} ({item['priceType']}) | {item['sellerTypeInferred']} | {item.get('city')}")
Keep maxPages small while you test: the Actor reads one page after another.
Step 4: Read the output
The loop prints one line per listing. This is the complete output of the run behind this article (keyword iphone on nl, 10 rows). The run finished in about 5 seconds:
iPhone verkopen? Ontvang de hoogste prijs voor je iPhone! | no amount (SEE_DESCRIPTION) | private | Amstelveen
iPhone 15 - Zeer goede staat, 80-84% batterij | no amount (FAST_BID) | private | Franeker
Apple iPhone 14 • Refurbished • 24M Garantie • vanaf €279,- | 279 EUR (FIXED) | business | Haarlem
Iphone 14 pro, 128 GB, onbeschadigd | no amount (FAST_BID) | private | Alphen
Iphone 14 pro max 256 GB | 330 EUR (MIN_BID) | private | Voorburg
iPhone 17 Pro | Black Friday €15 korting + 4 accessoires | 999 EUR (FIXED) | business | None
iPhone 14 plus | 250 EUR (MIN_BID) | private | Gouda
iPhone 13 128gb | 175 EUR (FIXED) | private | Terneuzen
iPhone 17 256GB alle kleuren NIEUW Black Friday | 799 EUR (FIXED) | business | Roelofarendsveen
Iphone 16 | no amount (FAST_BID) | private | 's-Gravenhage
Four of the ten rows have no amount. Three are FAST_BID (the ad asks for bids) and one is SEE_DESCRIPTION. The two MIN_BID rows carry an amount, which is the starting bid. The three rows marked business are the ones whose listings carry business-profile markers; sellerTypeInferred is the Actor's own label, because the site does not state it.
Understanding your results
Every row carries 36 fields. The ones you will use most:
-
price,priceType,currency: the amount in euros and how the site labels it (FIXED,MIN_BID,FAST_BID,SEE_DESCRIPTION).priceisnullwhen there is no amount, never0. -
title,descriptionSnippet,url,imageUrls: the ad text (a snippet of about 200 characters), the link and up to four photos. -
city,country,latitude,longitude: the location as the site gives it. -
sellerName,sellerId,sellerVerified,sellerWebsiteUrl,sellerTypeInferred: the seller. -
condition,deliveryandattributes: the site's localised wording (Dutch onnl), and the other attributes of the category, such as storage, colour or battery health. -
isPromoted,isReserved,priorityProduct,traits: promotion and reservation flags. -
dateText: the placement date as the site words it, for exampleVandaag.
One complete row from the run (imageUrls cut to its first entry):
{
"id": "m2448362351",
"url": "https://www.marktplaats.nl/v/telecommunicatie/mobiele-telefoons-apple-iphone/m2448362351-iphone-13-128gb",
"title": "iPhone 13 128gb",
"descriptionSnippet": "Iphone 13 128gb. Batterij conditie is 76%. Functioneert goed, wel lichte gebruikssporen. (Zie foto's).",
"price": 175,
"priceType": "FIXED",
"currency": "EUR",
"dateText": "Vandaag",
"city": "Terneuzen",
"country": "Nederland",
"countryCode": "NL",
"latitude": 51.325071850783,
"longitude": 3.8383253430983,
"abroad": false,
"imageUrls": [
"https://images.marktplaats.com/api/v1/hz-mp-pro-listing/images/feef3ad5-7b06-4fe7-bffb-f403d6e18ba6?rule=ecg_mp_eps$_82.jpg"
],
"sellerId": 21660597,
"sellerName": "Yannickmb",
"sellerVerified": false,
"sellerWebsiteUrl": null,
"sellerTypeInferred": "private",
"categoryId": 1953,
"parentCategoryId": 820,
"verticals": [
"mobile_phones_apple_iphone",
"telecommunication",
"barcode-supported"
],
"condition": "Gebruikt",
"delivery": "Ophalen of Verzenden",
"attributes": {
"condition": "Gebruikt",
"delivery": "Ophalen of Verzenden",
"color": "Blauw",
"type": "IPhone 13",
"battery health": "76 %"
},
"traits": [
"DAG_TOPPER",
"PACKAGE_FREE"
],
"priorityProduct": "DAGTOPPER",
"isPromoted": true,
"isReserved": false,
"hasPhoneFlag": false,
"searchCountry": "nl",
"searchQuery": "iphone",
"categoryPath": null,
"searchUrl": "https://www.marktplaats.nl/q/iphone/",
"scrapedAt": "2026-11-15T19:31:08.089215+00:00"
}
Pro tips
-
Filter by seller early.
sellers: "private"or"business"removes the other group before it reaches your dataset. In the run above, 7 rows were private and 3 business. -
Expect different attributes per row.
attributesholds whatever the ad carries. In these rowsstorageappears on 7 of 10,typeon 7 andbattery healthon 1, so use.get()rather than indexing. -
requireKeywordMatchtrims noise. With it on, a row is kept only if every word of the keyword is in the title or snippet. It is ignored for category paths. -
Price bounds drop rows without an amount. As soon as
minPriceormaxPriceis set, bidding and "see description" listings are removed, because they have no number to compare. - Each listing is stored once. A listing seen twice in the same run, on another page or under another keyword, produces one row.
-
Keep the first run small.
maxItems: 10andmaxPages: 1show the shape of the data before you widen the search. A single search reaches up to 167 pages of 30 listings, and the sites can stop serving results sooner.
Troubleshooting
- Zero rows, status SUCCEEDED. The site answered but nothing matched your filters. The run's status message says so. Loosen the filters or check the keyword.
- Zero rows, status FAILED. An unknown category path or an invalid input ends the run with a message that says what to fix. Copy category paths from the site you search: on 2ememain.be they are French.
-
Fewer rows than expected. The log ends with
Skipped N: reasonlines for rows removed by your filters, followed byPushed N row(s).. Read it withclient.log(run.id).get().
Check that your use of the data complies with the site's terms and with data-protection law.
Next steps
For current pricing and details, see the Store page.
To repeat a search, save it as an Apify task and attach a schedule, which runs an Actor or task on a cron expression. Datasets export as JSON, CSV, XML, Excel, HTML table, RSS or JSONL (dataset docs), and a webhook can notify your application when a run finishes (webhook docs).
What are you planning to search for on Marktplaats? Drop a comment! 💬
About the author: reventadata builds and publishes data extraction Actors on the Apify Store.
Top comments (0)