2dehands & 2ememain Scraper avatar

2dehands & 2ememain Scraper

Pricing

from $1.59 / 1,000 results

Go to Apify Store
2dehands & 2ememain Scraper

2dehands & 2ememain Scraper

Scrape 2dehands.be and 2ememain.be as clean JSON: paste search links from either site or run several searches at once, and get only new listings on a schedule. Prices, condition, specs, locations and images. GDPR-safe: no seller names or personal data.

Pricing

from $1.59 / 1,000 results

Rating

0.0

(0)

Developer

Lowland Data

Lowland Data

Maintained by Community

Actor stats

0

Bookmarked

5

Total users

3

Monthly active users

3 days ago

Last modified

Share

Belgium's second-hand market runs on one platform under two names: 2dehands.be in Dutch, 2ememain.be in French. Same listings, different interface. If you buy or sell across Flanders and Wallonia you are checking both by hand, and even one side is slow going.

Point this at a search on either domain and the matches come back as structured rows: price and price type, condition and category specs, municipality and coordinates, photos, posting date. Price bands, postcode radius and date windows happen before the run. Seller names and profiles are never collected, so nothing needs scrubbing later.

Live health record: lowlanddata.com/status/tweedehands-scraper - 30-day success rate and daily canary results, failures included. Full input/output reference: tool page.

No seller personal data, ever. This scraper is built GDPR-first: seller names, IDs, phone numbers and profile links never appear in the output, not as an option you have to remember to switch off, but by design. The only seller information included is sellerType, and it is a signal rather than a verified status: an account is marked business when it exposes a website link, so private means "no business signal was found" and some dealers will sit in it.

Quick start (30 seconds)

  1. Put what you'd type in the 2dehands search box into searchQuery, e.g. gazelle.
  2. Click Start. That's the whole minimum setup.
  3. When the run finishes, open the dataset's Overview tab for a clean table, or Export it as CSV/Excel/JSON.

Everything else (price range, category, sorting, the French-language 2ememain.be variant) is an optional refinement, explained in the Input table below. To make it run by itself, create a Schedule in the Apify Console with the same input and add an email or Slack integration on the runs.

Already have your search set up on 2dehands.be or 2ememain.be? Paste the address of the results page into startUrls instead, with every filter you picked on the site (category, brand, condition, distance...). Paste several to watch several searches in one run, from either site, and switch on onlyNewListings so each scheduled run returns only the listings you have not received yet.

What you can build with it

  • Flip underpriced finds before anyone else. Sort by date, watch a category like road bikes or PS5 consoles, and get every new listing the minute it posts, with the price and condition already parsed, so your alert fires while the deal is still live.
  • Price a car properly. Pull every BMW 5-Series within a year range and read mileage, constructionYear, fuel, transmission and the options list straight from attributes: build the price-vs-mileage curve that tells you what a car is really worth.
  • Track a market week over week. Snapshot "e-bikes in Antwerp under €800" on a schedule and watch supply, median price and how fast things sell move over time, the raw material for a pricing report or a dashboard.
  • Feed an AI agent clean data. Every field is structured, predictable and free of personal data, so an assistant or pipeline can consume it directly, no scrubbing, no compliance review before you store it.

What you get

Each listing is one dataset item:

{
"listingId": "m2419606891",
"title": "Jongensfiets Gazelle Esprit zwart",
"description": "Nieuw aangekocht in oktober 2024...",
"priceCents": 40000,
"priceType": "MIN_BID",
"city": "Balen",
"country": "België",
"latitude": 51.166343,
"longitude": 5.189532,
"postedAt": "Vandaag",
"imageUrls": ["//images.2dehands.com/api/v1/..."],
"categoryId": 455,
"url": "https://www.2dehands.be/v/fietsen-en-brommers/...",
"attributes": { "condition": "Zo goed als nieuw", "frameHeight": "57 cm", "color": "Zwart" },
"reserved": false,
"sellerType": "private"
}

Category-specific attributes come through automatically. A car carries constructionYear, mileage, fuel, transmission, body and its full options list; a house carries livingArea and numberOfRooms; a phone carries storage and cameraResolution, whatever the marketplace records for that category, minus anything that identifies a seller.

Field notes, so you know exactly what you are buying:

  • priceCents is the number the marketplace attaches to the listing, in euro cents. Read it together with priceType, which says what it means: FIXED is an asking price, MIN_BID is a minimum bid (not what the seller expects to get), and the site's other kinds mean the figure is indicative at best. Listings without a price omit the field.
  • postedAt is the posting date exactly as the marketplace displays it, a date, or a relative label like Vandaag (today) for recent listings. postedDate is the same date normalized to YYYY-MM-DD (relative labels resolved in Belgian local time); it is omitted on the rare row whose label cannot be parsed.
  • latitude/longitude are the marketplace's own municipality-level coordinates, not a seller address.
  • description is the snippet the marketplace search returns, not the full listing page text.
  • imageUrls are protocol-relative (//images...); prepend https: to use them.
  • attributes is the whitelisted, non-personal spec set for the listing's category: condition and delivery on anything, plus e.g. car specs, property size or phone storage where they apply. Multi-value fields like a car's options come through as a list.
  • reserved tells you whether the marketplace currently marks the listing as reserved.

How much does it cost to scrape 2dehands?

$1.99 per 1,000 listings delivered, pay-as-you-go, no subscription, and you pay per result rather than per hour. A run that finds nothing still costs the $0.005 actor start. In plain dollars:

  • 100 listings ≈ $0.20, a daily category watch.
  • 500 listings ≈ $1.00, a solid market snapshot.
  • A 5,000-listing crawl ≈ $10, a full category, deduplicated.

The per-result price is all-inclusive: no separate platform-usage charges, no residential proxy surcharge. Higher Apify plans get automatic unit-price discounts (down to $1.99/1,000 on Gold).

Free-plan runs are limited to a sample of 5 items, enough to evaluate the output format against your real query.

Not technical? Let your AI assistant set it up

Copy this into ChatGPT, Claude or any AI assistant, fill in the one line, and follow the conversation:

Help me set up the "2dehands & 2ememain Scraper" actor on Apify
(https://apify.com/lowlanddata/tweedehands-scraper). Guide me one step at a time.
What I want to watch: [E.G. "electric bikes under 800 EUR, alert me on new listings"]
Guide me to:
1. Propose my input values: searchQuery (what I'd type in the site's search box),
optional priceMinEur/priceMaxEur, optional postcode + radiusKm for
listings near me (Belgian postcode, e.g. 2000 for Antwerpen), optional
conditions (new/as-new/used/refurbished/not-working) and delivery
(pickup/shipping), sortBy "date" for newest-first monitoring, postedAfter/postedBefore
(YYYY-MM-DD) to keep only listings from a date window, and maxItems.
To re-check specific listings I already know, use listingIds instead.
If I already have searches set up on 2dehands.be or 2ememain.be, use startUrls
with the full addresses of their results pages instead, and onlyNewListings true
so scheduled runs return only listings I have not received yet.
2. If my watch fits one category: run one broad search first, read the categoryId
from the results I care about, and add it to the input for precise runs.
3. Create a free Apify account (apify.com), open the actor page, paste the values
into the Input form, and start a run.
4. Set up a daily Schedule in the Apify Console with the same input, plus an email
or Slack integration so new results reach me automatically.
5. Show me how to export results as CSV/Excel, or read them from the API if I code.
6. If the results are what I wanted, remind me at the end to leave a quick rating on the actor page, and to report anything broken or missing on its Issues tab.

Input

FieldDescription
searchQueryFree-text search, e.g. gazelle. Provide this, a category, or both.
searchQueriesSeveral free-text searches in one run, each with the filters below. A listing found by two searches is delivered once. Up to 50.
startUrlsAddresses of 2dehands or 2ememain results pages, copied from your browser including everything after the #. Every filter in the URL applies, and each URL keeps its own site. When given, the form's search fields are ignored and the run status says which. Up to 50.
domain2dehands.be (Dutch, default) or 2ememain.be (French). Same listings, different language.
categoryIdNumeric category ID to scope the crawl. See below for finding IDs.
listingIdsFetch these exact listings instead of searching, e.g. ["m2107483664"] (the id in every listing URL and dataset item). Great for re-checking price or availability.
postcode / radiusKmRadius search: Belgian postcode (e.g. 2000) plus a radius in km (up to 300). This narrows the search; the distance itself is not returned as a field.
conditionsOnly these conditions: new, as-new, used, refurbished, not-working.
deliverypickup, shipping, or any (default).
buyItNowOnlyOnly listings with the "Direct Kopen" option.
priceMinEur / priceMaxEurOnly listings within this price range, in EUR.
sortBydate (newest first, default), price_asc, price_desc, or relevance.
postedAfter / postedBeforeDate window (YYYY-MM-DD). Keeps only listings posted inside it, using the normalized postedDate field. Listings whose date cannot be read are excluded while a window is set. The site serves only its newest results per query, so a window further back may need a narrower query. Not applied to listingIds lookups.
maxItemsStop after this many listings, across all searches of the run. Default 1,000. With several searches, each gets an equal share of what is left.
onlyNewListingsReturn only listings this search has not delivered before. The first run returns the usual results and remembers them; skipped listings are not charged.
resetStateWith onlyNewListings: forget what was delivered and return the full results once.

A run needs at least a search query, a category or a search URL: the marketplace has no "everything" feed, and the actor tells you so up front instead of returning an empty dataset.

Condition, delivery and buy-it-now filters work inside a category. The marketplace ignores them on a search across all categories. When you set them without a categoryId, the actor runs your search category by category with the filters applied, and skips categories that have no such filter (cars have no condition filter), so the results are right. If you choose a category yourself and it has no such filter, the run stops before delivering anything and tells you which filter; it does not hand you listings that only look filtered; it is a little slower, and the listings come grouped by category. Add a categoryId to avoid both.

Pasting a search URL

Run your search on 2dehands.be or 2ememain.be, pick every filter you want, and copy the whole address, e.g. https://www.2dehands.be/l/fietsen-en-brommers/fietsen-dames-damesfietsen/#q:gazelle|f:31|PriceCentsTo:50000|postcode:2000|distanceMeters:10000. Filters live after the #, so copy that part too. The page number does not matter, every run starts from the first page. Without a sort order in the URL the run goes newest first. A radius in the URL needs its postcode there as well: the site otherwise takes the postcode from your browser, which the actor cannot see, and the status says so. A listing page (/v/...) or an address from another site is refused before the run starts.

Finding a category ID

Every result includes its categoryId. The quickest way to discover IDs is to run a broad search once and read the category IDs off the results you care about, then use them as input for scoped crawls.

Use it from your code

Run the actor and get items straight back with one HTTP call (fine for scoped runs up to ~5 minutes):

curl "https://api.apify.com/v2/acts/lowlanddata~tweedehands-scraper/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
-X POST -H "Content-Type: application/json" \
-d '{"searchQuery": "gazelle", "maxItems": 100}'

Node.js:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('lowlanddata/tweedehands-scraper').call({
searchQuery: 'gazelle',
maxItems: 100,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("lowlanddata/tweedehands-scraper").call(
run_input={"searchQuery": "gazelle", "maxItems": 100})
items = client.dataset(run["defaultDatasetId"]).list_items().items

For large split crawls, start the run asynchronously and fetch the dataset when the finished-run webhook fires. Schedules (e.g. a daily price snapshot), webhooks and the Make/Zapier/n8n integrations all work out of the box. This is a standard Apify actor.

Use it in n8n

There is a dedicated community node, so 2dehands / 2ememain listings can flow straight into an n8n workflow - a scheduled search into Sheets, Telegram alerts on new rows, a database sync - with the Actor's full input as a form.

In n8n: Settings → Community nodes → Install, enter:

n8n-nodes-tweedehands-scraper

Connect it with your Apify API token (or OAuth2 on n8n Cloud) and the Actor runs on your Apify account, billed exactly like a run started here.

Use it with AI agents (MCP)

Claude, Cursor and other MCP-capable agents can run this scraper as a tool through Apify's hosted MCP server: the agent fills in the search itself, starts the run and reads the results, no glue code.

Claude Code:

$claude mcp add apify --transport http "https://mcp.apify.com?actors=lowlanddata/tweedehands-scraper"

Cursor or Claude Desktop (add a custom connector / MCP server with this URL):

https://mcp.apify.com?actors=lowlanddata/tweedehands-scraper

Sign in with your Apify account when prompted. Runs are billed to it. Setup details per client: Apify MCP docs, or start from this actor's own MCP page: apify.com/lowlanddata/tweedehands-scraper/api/mcp.

Prompts that work once connected:

  • "Find the 20 newest Gazelle e-bike listings under €800 on 2dehands and put them in a table by price."
  • "Snapshot road bikes in the €500–1500 range and tell me the median asking price."
  • "Check 2ememain for PS5 consoles posted today and flag anything at least 20% under the median."

Beyond the 5,000-result window

The marketplace API returns at most 5,000 results per query. This scraper automatically splits large queries by category and price band and deduplicates across the shards, so broad crawls are not silently truncated at 5,000 items, a common limitation of similar scrapers. A 60,000-result crawl has been verified end-to-end.

Public listing data (prices, descriptions, categories, locations) is public commercial information, and this scraper is built so that the hard part of the question never arises: no personal data enters your dataset in the first place. That is the difference between this actor and a generic scraper, where GDPR compliance is your problem to solve after the fact.

Seller-identifying fields are removed structurally before anything reaches your dataset: the mapper whitelists fields in, it does not filter fields out, so new marketplace fields can never leak through. The only seller signal kept is business vs private. Requests are rate-limited to keep load on the marketplace negligible, and no anti-bot protections are bypassed.

One honest limit: titles and descriptions are the seller's own words, delivered as-is. If a seller chooses to type contact details into their listing text, that text is not rewritten: the guarantee covers the marketplace's data fields, not the content sellers publish about themselves.

Troubleshooting

The actor fails fast with the reason in the run's status message:

  • "Provide a search query, a category, search URLs, specific listingIds, or a combination.": the input was empty; fill in searchQuery, categoryId or startUrls.
  • "... is a single listing, not a search" / "... is not a search results page": a URL in startUrls points at a listing or another page. Open the search results on the site and copy that address, or put a listing's m-number in listingIds.
  • "... is not a category on 2dehands.be": the category in a pasted URL no longer exists; run the search on the site again and copy the fresh address.
  • "priceMinEur must not be higher than priceMaxEur.": inverted price range.
  • Unknown category errors: the marketplace rejected the categoryId; re-check it against a recent result.
  • Fewer items than requested on a free plan: the 5-item free sample cap; run on a paid Apify plan for full results.

Is there a 2dehands API alternative?

2dehands and 2ememain publish no public API. This actor is the practical alternative: the same listings as structured JSON through one HTTP call (run-sync-get-dataset-items), on a schedule, or as an MCP tool for AI agents, with the GDPR question already answered in the data itself.

Does 2dehands block scrapers?

The marketplace serves search results openly, and this actor stays inside that welcome: paced requests, standard datacenter proxies, load kept negligible. No CAPTCHA fights, no bot-wall cat-and-mouse, which is also why runs are fast and reliable enough for daily schedules.

How do I monitor 2dehands prices?

Paste your searches into startUrls (or list them in searchQueries), switch on onlyNewListings, and add a daily (or hourly) Schedule in the Apify Console with an email/Slack integration on the runs. The first run returns the current listings; every run after that returns only the listings that appeared since, with the price already parsed. Listings you already received are skipped and not charged, and so are listings posted on a day before your previous run, so a watch never back-fills older listings it simply had not reached. A newest-first search stops reading as soon as it reaches known listings; paid top listings that sit above every search do not stop it early. Keep the default newest-first order for this: sorted by price there is no "since". The AI-assistant prompt above walks a non-technical user through exactly this setup.

FAQ

Can I get seller names or phone numbers? No, by design. That is the product: data you can store, share and process without a GDPR review. The output tells you only whether the seller is a business or a private person.

What is the difference between 2dehands.be and 2ememain.be? The same marketplace in Dutch and French. Pick the domain whose language you want listings in; the data shape is identical.

How fresh is the data? Live at run time. Every run queries the marketplace directly. For continuous freshness, schedule the actor.

Can I export to Excel or CSV? Yes. Every dataset exports as CSV, Excel, JSON or XML from the Apify Console or API.

What happens above 5,000 results? The marketplace API caps any single query at 5,000; the actor automatically splits by category and price band and deduplicates, so large crawls come through complete.

Do I get full listing descriptions? You get the description snippet the marketplace search returns, not the full listing page text, stated honestly here rather than discovered later.

Does it work with Make, Zapier or n8n? Yes. It is a standard Apify actor; all platform integrations, webhooks and schedules apply.

Do I need an API key to scrape 2dehands? There is none to need. The sites publish no public API. Your Apify account token is the only credential involved, and a first test run works on the free plan.

How much does it cost per 1,000 listings? A flat $1.99 for every thousand listings delivered, covering proxies and platform usage. Runs that fail or come back empty cost nothing, and the Gold plan lowers the unit price to $1.59.

Why does a free-plan run return only 5 listings? Free Apify accounts get a 5-item sample per run, enough to see the exact output shape on your own search before upgrading.

Can I limit results to listings near me? Give a Belgian postcode (like 2000 for Antwerpen) plus a radiusKm of up to 300 km. That narrows which listings come back; the rows carry the seller’s stated city, not a computed distance.

How do I check whether a listing is still available? Put its id in listingIds (it appears in every listing URL and dataset item) and the actor fetches just that listing again, price and reserved status included.

Can I exclude broken or for-parts items? Yes. The conditions filter takes any combination of new, as-new, used, refurbished and not-working, so leave the last one out.

Can I get only listings that ship? Set delivery to shipping (or pickup for the reverse). buyItNowOnly further narrows to listings with the "Direct Kopen" option. The marketplace applies these filters only inside a category, so without a categoryId the actor searches category by category to keep them working.

Can I paste a search URL from 2dehands or 2ememain? Yes, put the full results page address into startUrls, including the part after the # where the site keeps its filters. Category, brand and other attribute filters, price, postcode radius, posting age and sort order all apply, and each URL keeps its own site. Paste up to 50 to run them all in one go.

Can a scheduled run return only the new listings? Yes, switch on onlyNewListings. Each search remembers what it delivered, for 90 days, in a key-value store in your own Apify account. Listings you already received, or posted before your previous run, are skipped and not charged. Changing a search's filters starts a fresh memory, and resetState clears it on purpose.

Does it capture car details like mileage? It does: car listings arrive with constructionYear, mileage, fuel, transmission, body and the options list as structured attributes, ready for a price-vs-mileage analysis.

What does MIN_BID mean in priceType? The seller set a minimum bid instead of a fixed asking price. FIXED marks a normal asking price; other marketplace price kinds come through as-is when they occur.

Is the location a seller's home address? Never. city and the coordinates are the marketplace's own municipality-level location, area data, not an address.

What happens when a run goes wrong? The actor fails fast with the reason spelled out in the run's status message, rather than handing you a silently empty dataset, and a failed or empty run is never billed.

Can Claude or another AI agent use this scraper directly? Yes, through Apify's hosted MCP server: the agent composes the search input, launches the run and reads the results without any glue code.

Is scraping 2dehands allowed under GDPR? The output is designed so the question resolves itself: the mapper whitelists non-personal fields in, seller identity is structurally unreachable, and what lands in your dataset is public commercial data only.

How big a crawl can it handle? The marketplace caps one query at 5,000 results, so the actor splits big crawls by category and price band and deduplicates, a 60,000-result crawl has been verified end-to-end.

Is there the same scraper for the Netherlands or Germany? Yes. The Marktplaats.nl and Kleinanzeigen.de scrapers below share the output shape and the GDPR-clean guarantee.

Working the Benelux/German second-hand market? The same GDPR-clean guarantee, same output shape:

Support

Found an issue or missing a field you need? Open an issue on the actor's Issues tab: reports get fixed, this actor is actively maintained.

Working well for you? A rating on this page takes ten seconds and helps other buyers find a GDPR-clean option among the lookalikes. It is also the clearest signal of what we should build next.