1. 2scraper
  2. Classifieds, property & travel
  3. Zimmo
Open source · MITFree to run · paid add-ons optional

Zimmo Scraper

Zimmo.be sale and rent listings — price, address, living area, bedrooms and EPC label — from the Dutch and French sites.

  • Playwright
  • Selenium
  • Puppeteer
  • Scraping Browser API
  • JSON
  • CSV
zimmo-scraper
# For sale (Dutch site)
$ python3 playwright_scraper.py --url "https://www.zimmo.be/nl/gent-9000/te-koop/" --pages 3 --headful --category gent-koop --format both --out gent_listings
✓ 21 sample rows · 22 columns in sample_output.json
2listing types: sale and rent
2site languages: Dutch and French
0field mismatches on 105 tiles checked against their own labels (Sep 2026)
63rows from a 3-page run on a Belgian home connection (Sep 2026)
Available Zimmo scrapers

What the Zimmo scraper collects

Reads zimmo.be search-results pages, for sale and rent on the Dutch and French sites, and writes one row per listing: price, address, property type, living area, bedrooms and energy grade, with the listing's own Zimmo code as sku. Output is JSON and/or CSV with a run-metadata sidecar.

For sale (Dutch site)

Input: A zimmo.be /nl/…/te-koop/ search URL

One row per sale listing: price, address, property type, living area, bedrooms and EPC label.

For rent

Input: A zimmo.be /nl/…/te-huur/ search URL

The same row schema for rental listings, with listing_type set to rent.

French site

Input: A zimmo.be /fr/…/a-vendre/ search URL

Listings from the French-language site; bedrooms, area and energy grade are read from icons, identical across both languages.

Quick start

From zero to Zimmo data in 3 steps

  1. Install

    Clone the repository and install one browser engine. Python 3.9+.

    $ git clone https://github.com/2scraper/zimmo-scraper.git
    $ cd zimmo-scraper
    $ pip install -r requirements.txt -r requirements-playwright.txt
    $ playwright install chromium
    $ cp .env.example .env
    $ python3 playwright_scraper.py --url "https://www.zimmo.be/nl/gent-9000/te-koop/" --headful
  2. Run a scraper

    Pick what you need and run it. Every flag is documented in the README.

    $ python3 playwright_scraper.py --url "https://www.zimmo.be/nl/gent-9000/te-koop/" --pages 3 --headful --category gent-koop --format both --out gent_listings
  3. Get structured data

    Rows land in JSON or CSV (--format), with a .meta.json sidecar recording status, pages fetched and why the run stopped.

Output

Zimmo data fields

22 columns per row in a predictable schema. The most useful ones:

FieldWhat it holds
skuThe listing's own Zimmo code
urlListing URL
titleListing heading, e.g. Huis te koop
priceAsking price or rent; null for "price on request"
currencyEUR; null when no price is stated
listing_typesale or rent
property_typeProperty type as the site states it, e.g. huis
locationAddress
surface_m2Living area in m²; null when the listing states none
bedroomsNumber of bedrooms; null for garages and commercial property
epc_labelEnergy performance grade, e.g. B or F
epc_valueEnergy performance value when shown
image_urlListing photo
categoryYour --category label for the run
[
  {
    "source": "zimmo.be",
    "scraped_at": "2026-09-23T14:20:10.757167+00:00",
    "url": "https://www.zimmo.be/nl/gent-9000/te-koop/huis/LRZLA",
    "sku": "LRZLA",
    "title": "Huis te koop",
    "brand": null,
    "price": 210000.0,
    "currency": "EUR",
    "original_price": null,
    "discount_pct": null,
    "rating": null,
    "review_count": null,
    "in_stock": null,
    "image_url": "https://files.zimmo.be/backend-api/qMbJs7nn9uXzLU5aLtWl6XQOLD0=/828x618/filters:image-f…",
    "category": "gent-koop",
    "listing_type": "sale",
    "property_type": "huis",
    "location": "Zwijnaardsesteenweg 685 9000 Gent",
    "surface_m2": null,
    "bedrooms": 3,
    "epc_label": "F",
    "epc_value": null
  },
  {
    "source": "zimmo.be",
    "scraped_at": "2026-09-23T14:20:10.758284+00:00",
    "url": "https://www.zimmo.be/nl/gent-9000/te-koop/appartement/LRZKI",
    "sku": "LRZKI",
    "title": "Appartement te koop",
    "brand": null,
    "price": 355000.0,
    "currency": "EUR",
    "original_price": null,
    "discount_pct": null,
    "rating": null,
    "review_count": null,
    "in_stock": null,
    "image_url": "https://files.zimmo.be/backend-api/uoqMARL1lhm7fEW59A_yJjriSMI=/828x618/filters:image-f…",
    "category": "gent-koop",
    "listing_type": "sale",
    "property_type": "appartement",
    "location": "Martelaarslaan 287 9000 Gent",
    "surface_m2": 92.0,
    "bedrooms": 2,
    "epc_label": null,
    "epc_value": null
  }
]

First 2 of 21 rows from a real run — sample_output.json. Long values shortened here.

Built for Zimmo

Handles what breaks naive Zimmo scrapers

No structured data, read robustly anyway

Zimmo's grid carries no listing data in JSON-LD or its Angular state. Rows come from each <zimmo-listing> element, and bedrooms, area and energy grade are read by icon file, which is the same on the Dutch and French sites.

Verified field by field

On 105 tiles over 7 live pages, every field was checked against the tile's own accessibility labels with 0 mismatches (Sep 2026).

Out-of-range pages detected

A page number past the end returns page 1 again under HTTP 200. The run recognises that by its data, stops, and reports the run as complete with listing_exhausted in the sidecar.

Honest change reports

diff_runs.py refuses incomplete runs and runs of different searches. When a run read only part of a listing, a missing listing is reported as "left selection", not removed.

Everything built in

Scraping infrastructure included

The same tested core runs every 2scraper repository, so the hard parts work the same way on every site.

CAPTCHA handling

Detects the challenge a page really serves and, with a 2Captcha key, solves it only when it blocks the data — a capped number of solves per page.

Proxy rotation

Single proxy or a pool from a file, rotated on blocks with a fresh browser per exit. Credentials stay out of argv and logs.

Fingerprints

Optional complete device identity from the 2Captcha Fingerprint API — user agent, client hints, locale and timezone that agree with each other.

Scraping Browser API

Point --cdp-endpoint at a managed Chromium with its own exit country — no browser infrastructure to run yourself.

Honest run metadata

Every run writes a .meta.json sidecar and distinct exit codes: complete, blocked, empty and partial are never confused, and a failed run never overwrites good data.

Diff two runs

diff_runs.py compares two complete Zimmo snapshots by id — new, removed and changed rows for monitoring.

Use cases

What teams do with Zimmo data

Belgian property price tracking

Follow asking prices and rents by city or postcode, on the Dutch or French site.

Energy performance analysis

Collect EPC labels alongside price and living area to study how energy grade relates to price.

Rental market monitoring

Track new and withdrawn rental listings in cities like Antwerp, Ghent or Brussels.

Agency and investor lead lists

Build lists of listings by property type, bedrooms and area for a target market.

Access & pricing

Free to run. Pay only for what the site requires.

Free to run · paid add-ons optional

No 2Captcha key is needed, but Cloudflare refuses datacentre addresses and headless browsers (measured Sep 2026). On a home connection --headful is the whole requirement; the exit need not be Belgian. On a server you need a residential proxy plus a display (xvfb-run), or the Scraping Browser API, which brings both. The Scraper API also returned a page with no browser at all, at $0.0005 per request; the captcha solver was not exercised in these runs.

Optional 2Captcha services — captcha solving, the Scraping Browser API, residential proxies and fingerprints — work behind one key.

Good to know

  • Datacentre addresses and headless browsers are refused; a residential exit and a headful browser, or the Scraping Browser API, are needed.
  • Only search-results pages are read; there is no single-listing detail mode.
  • fingerprint_client.py is shipped but not yet wired into any engine.

2scraper vs. hosted scraper APIs

2scraperTypical hosted API
CodeOpen source, MIT — read and patch itClosed
CostFree; optional services billed only when usedSubscription or per request / per result
Runs onYour machine, server or CIThe vendor's infrastructure
OutputJSON/CSV files + run metadataAPI responses
When the site changesFix it yourself or open an issue / PRWait for the vendor
FAQ

Zimmo scraper FAQ

Do I need a proxy to scrape Zimmo?

Not from a home connection, as long as you pass --headful. From a server or datacentre you need a residential proxy and a display, or the Scraping Browser API.

Why is --headful in every example?

Headless browsers were refused by Cloudflare even from a residential address, while a visible browser was served. The flag's default stays headless for parity with the rest of the family.

Does it support the French-language site?

Yes. Both /nl/ and /fr/ search URLs work, for sale and rent, and fields are read from icons that are the same on both sites.

Why is price null on some rows?

Those listings say "price on request". They are kept as rows with price and currency null.

What happens if I ask for more pages than exist?

Zimmo serves page 1 again past the end. The scraper detects that no new listings appeared, stops, and records the listing as exhausted.

Is scraping Zimmo legal?

It reads public search-results pages. Public data scraping is generally lawful, but follow Zimmo's terms and the law where you operate.