# Marktplaats & 2dehands Scraper: Listings, Price Drops & Stats (`datasmithlab/marktplaats-2dehands-scraper`) Actor

Export Marktplaats.nl, 2dehands.be and 2ememain.be classifieds by category or search words, past the 5,000-result limit: price, price type, city, date, condition, photo, seller type. Track new ads, price drops and removed ads; get median prices per category. No seller names, phones or emails.

- **URL**: https://apify.com/datasmithlab/marktplaats-2dehands-scraper.md
- **Developed by:** [Datasmithlab](https://apify.com/datasmithlab) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.72 / 1,000 listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Marktplaats & 2dehands Scraper: Listings, Price Drops & Market Stats

Export classified ads from **[Marktplaats.nl](https://www.marktplaats.nl)** (Netherlands) and **[2dehands.be](https://www.2dehands.be) / [2ememain.be](https://www.2ememain.be)** (Belgium, Dutch and French). They are one platform, so one Actor covers all three. Search by **category**, by **search words**, or paste a list page URL. Every row has the price (EUR) and price type, city, listing date, condition, photo, the ad's attributes (mileage, build year, brand...) and whether the seller is private or professional.

The sites show at most **167 pages (about 5,000 ads) per search**. When your search has more, the Actor reads it **sub-category by sub-category** and reports the coverage in `HEALTH`.

Two things a plain scraper does not give you:

- **Monitoring.** Schedule it and every later run returns only what changed: `NEW` ads, `PRICE_DROP` / `PRICE_UP` with the old price and the % change, and `REMOVED` ads. Unchanged ads are not returned and **not charged**.
- **Market summary.** One row per site, category and subcategory: number of ads, median and quartile price, price range, share with a price, professional-seller share and ads listed today or yesterday.

No login, no browser, no detail pages. **No seller names or ids, no phone numbers, no e-mail addresses, no coordinates** are collected: sellers appear only as `Private` / `Professional`, and phone numbers, e-mails and links inside ad texts are removed. Only public category and search list pages that the sites' `robots.txt` allows are read (never the internal search API, ad pages or seller pages).

### What you get

- **Price.** `price` (EUR), `priceType` (`FIXED`, `MIN_BID` = "bieden vanaf", `FAST_BID`, `SEE_DESCRIPTION`, `FREE`...). `price` is empty when the ad has no number.
- **The ad.** `title`, `description` (first 500 characters, cleaned), `category`, `subcategory`, `condition`, `delivery`, `attributes` (e.g. `mileage`, `constructionYear`, `fuel` for cars; size, material for bikes), `isPromoted`, `isReserved`.
- **Where and when.** `city`, `country`, `listedText` (as shown: "Vandaag", "9 okt 26"), `listedDate` (ISO date).
- **Seller type.** `Private` or `Professional` only.
- `url`, `imageUrl`, `photoCount`, `portal`.
- **Reports** in the key-value store: `SEARCHES` (per search and sub-category: ads available, pages read, status), `MARKET_SUMMARY`, `HEALTH`.

### How to search

| You want | Set |
|---|---|
| All bikes on Marktplaats | **Categories** `fietsen-en-brommers` |
| Volkswagens in Belgium | **Site** `2dehands.be`, **Categories** `auto-s/volkswagen` |
| "koga" bikes | **Categories** `fietsen-en-brommers`, **Search words** `koga` |
| Everything matching "iphone 15" | **Search words** `iphone 15` |
| Ads under EUR 200 in Utrecht | **Price to** `200`, **Cities** `Utrecht` |
| A daily new-ad / price-drop feed | **Only new or changed listings** on, schedule the Actor daily |
| Median prices per category | **Market summary** on |
| A list page you already have open | paste it in **list page URLs** |

Category names are the ones in the site links (`https://www.marktplaats.nl/l/`**`auto-s/volkswagen`**`/`). Top level examples: Marktplaats `auto-s`, `fietsen-en-brommers`, `huis-en-inrichting`, `computers-en-software`, `huizen-en-kamers`; 2dehands.be `auto-s`, `immo`, `elektronische-apparatuur`; 2ememain.be `autos`, `velos-velomoteurs`, `maison-meubles`, `immo`. Unknown categories are reported in `SEARCHES` and are not charged.

### Good to know

- **No sorting or price filter in the list URL.** The sites apply price, distance and sorting only in the browser, so the Actor filters price, city, seller type and listing date on its side. Ads come in the site's default order (relevance, promoted ads first); a daily monitoring run therefore reads the whole search.
- **5,000 ads per search.** Use a specific category (`auto-s/volkswagen`), or let the Actor split a big search into sub-categories. Leaf categories above 5,000 ads are capped and flagged in `SEARCHES`.
- **Dates.** The site shows "Vandaag" / "Gisteren" and later "9 okt 26". The Actor turns them into `listedDate` using the site's time zone.
- **Seller type** is derived from the ad id prefix: ads from professional sellers (shops, dealers) vs private people.

### Output example

```json
{
  "recordType": "listing",
  "id": "marktplaats.nl:m2448725723",
  "title": "Volkswagen Golf 1.4 TSI Highline",
  "price": 16490,
  "priceType": "FIXED",
  "currency": "EUR",
  "category": "auto-s",
  "subcategory": "volkswagen",
  "city": "Barendrecht",
  "country": "NL",
  "listedText": "Vandaag",
  "listedDate": "2026-10-10",
  "sellerType": "Private",
  "condition": null,
  "attributes": {"constructionYear": "2017", "mileage": "130491 km", "fuel": "Benzine", "transmission": "Automaat"},
  "url": "https://www.marktplaats.nl/v/auto-s/volkswagen/m2448725723-volkswagen-golf",
  "imageUrl": "https://images.marktplaats.com/api/v1/...",
  "photoCount": 3
}
```

In monitoring mode rows also carry `changeType`, `previousPrice`, `priceChange`, `priceChangePct` and `firstSeenAt`.

### Pricing

Pay per event: a small start fee, a fee per ad row and a fee per market summary row. In monitoring mode only new, changed and removed ads are charged. Set **Max listings** (and your run's maximum charge) to cap spending.

### Notes

- Public pages only. Respect the sites' terms and the applicable law (GDPR) when you use the data; the Actor does not collect personal data on purpose.
- Not affiliated with Marktplaats, 2dehands, 2ememain or eBay Classifieds Group.

# Actor input Schema

## `portal` (type: `string`):

The three sites are one platform. 2ememain.be uses French category names (autos, velos-velomoteurs...).

## `categories` (type: `array`):

One per line, as in the site links: 'auto-s', 'auto-s/volkswagen', 'fietsen-en-brommers', 'huizen-en-kamers'. Copy it from https://www.marktplaats.nl/l/<category>/. Searches above the 5,010-ad limit per search are split into sub-categories automatically.

## `searchQueries` (type: `array`):

Optional keywords such as 'koga' or 'volkswagen golf'. With categories: each category x each word. Without categories: searched on the whole site.

## `startUrls` (type: `array`):

Category or search list pages such as https://www.2dehands.be/l/auto-s/ or https://www.marktplaats.nl/q/fiets/. Filters in the URL are ignored; use the settings below.

## `maxItems` (type: `integer`):

Stop after this many listing rows (you pay per row). Market summary rows are not counted.

## `maxPagesPerSearch` (type: `integer`):

30 ads per page, at most 167 pages (about 5,000 ads) per search. Leave empty for all pages.

## `priceFrom` (type: `number`):

Ads without a price (see description, bidding, on request) are left out when a price limit is set.

## `priceTo` (type: `number`):

Leave empty for no limit.

## `cities` (type: `array`):

Only ads in these cities (name as shown on the site, not case-sensitive), e.g. Utrecht, Gent.

## `sellerType` (type: `string`):

Sellers appear only as Private or Professional: no names, ids, phones or e-mails are collected.

## `listedWithinDays` (type: `integer`):

1 = today, 2 = today and yesterday, ... Uses the listing date shown on the list page.

## `requirePrice` (type: `boolean`):

Leave out ads that have no price (see description, on request, swap).

## `onlyNewOrChanged` (type: `boolean`):

For scheduled runs: each run returns only ads that are NEW, had a PRICE_DROP or PRICE_UP since the last run with the same settings, and (when every search was read completely) REMOVED ads. The first run returns everything as NEW. Unchanged ads are not returned and not charged.

## `marketSummary` (type: `boolean`):

Add one row per site, category and subcategory: ads, median and quartile price, price range, share with price, professional share and ads listed today or yesterday. Uses every matching ad, also unchanged ones in monitoring mode.

## `splitLargeSearches` (type: `boolean`):

The sites show at most 167 pages (about 5,000 ads) per search. When a search has more, it is read per sub-category. Turn off to read only the first 5,000. List URLs cannot be sorted or price-filtered, so coverage is reported in HEALTH (splitCoverage).

## `proxyConfiguration` (type: `object`):

Requests go out without a proxy first. Only if the site blocks them is this proxy used (at most 3 new IPs, then the run stops and reports it).

## Actor input object example

```json
{
  "portal": "marktplaats.nl",
  "categories": [
    "fietsen-en-brommers"
  ],
  "maxItems": 100,
  "sellerType": "all",
  "requirePrice": false,
  "onlyNewOrChanged": false,
  "marketSummary": false,
  "splitLargeSearches": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `listings` (type: `string`):

Listings table (JSON/CSV/Excel export).

## `marketSummary` (type: `string`):

One row per site, category and subcategory (when enabled).

## `allFields` (type: `string`):

Every field, including attributes (mileage, year, brand...), photo and description.

## `searches` (type: `string`):

For each search and sub-category: listings available, pages read, status.

## `health` (type: `string`):

Empty-field ratios, proxy use, failed searches, split coverage.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "categories": [
        "fietsen-en-brommers"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datasmithlab/marktplaats-2dehands-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "categories": ["fietsen-en-brommers"] }

# Run the Actor and wait for it to finish
run = client.actor("datasmithlab/marktplaats-2dehands-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "categories": [
    "fietsen-en-brommers"
  ]
}' |
apify call datasmithlab/marktplaats-2dehands-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datasmithlab/marktplaats-2dehands-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/JokApj6kC76PbDScR/builds/JBt3sC3IfMqT0tAW4/openapi.json
