# Changelog of Immoweb Scraper: Belgian Property Listings, EPC & Agents (`studioxl/immoweb-scraper`) Actor

- **URL**: https://apify.com/studioxl/immoweb-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/studioxl/immoweb-scraper.md

## Changelog

### 0.3 (2026-10-20)

**New filter**

- **Published or updated within** (`modifiedWithinDays`): only listings put
  online or updated in the last N days. A new listing always counts, because
  Immoweb dates it the day it goes up. Updates include price changes and an
  agency re-saving a listing. Listings outside the window are skipped and not
  charged. Also read as `publishedWithinDays` or `updatedWithinDays`.

**New columns** (all additions; no existing column changed)

- `daysOnMarket`: whole days since the listing went online. Filled when full
  details are on, because only the listing page carries the date it went up.
- `floorPlanUrls`: links to the listing's floor plans (PDF), with full details.
  Rare: 1 listing in 17 had any in our check.
- `virtualTourUrl`: the listing's virtual tour or video tour link (YouTube,
  Omnicasa), with full details. Filled on the listings whose search card says
  they have a tour.

**Daily feed** (Only new or changed listings)

- `monitorStatus` on every row: `new`, `changed` or `removed`.
- `changedFields` names what changed on a `changed` row, for example
  `["price"]`. A feed started before this version reports changes without the
  column names once, then with them.
- **Removed listings.** A listing that has left your search since the last run
  (sold, withdrawn, or no longer matching your filters) comes back once as a
  `removed` row with its ID and link. These rows are free. They're only reported
  when the run read the whole search: a run stopped by Maximum results, a
  search too large to read in full, or a run that restarted midway reports
  nothing as removed. A search that suddenly returns no listings at all is not
  taken at its word either. Removal tracking starts with the first run on this
  version, so the second run is the first that can report one.
- A listing link given directly that has been taken down also comes back as a
  free `removed` row in a daily feed.
- New view in Console: **Daily feed changes**.

**Fixes**

- **Runs no longer fail on an unlucky start.** Immoweb turns away up to two in
  three first attempts, in bursts. A run used to give up after 10 refusals in
  a few seconds (2 of 38 test runs failed that way). It now keeps trying the
  first page with a growing wait for up to 4 minutes, and keeps using the
  connection that got through. If Immoweb still refuses after that, the run
  ends with "try again in a few minutes" and nothing is charged.
- **`limit`, `maxResults` and `resultsLimit` are read as Maximum results**, for
  API and AI-agent callers. They used to be ignored, so a run asked for 40
  delivered 100. A Maximum results you set yourself to something other than
  100 still wins.

**Not added:** a "newest first" sort. Results keep Immoweb's own order. Use
**Published or updated within** for recent listings.

### 0.2 (2026-10-20)

Easier to start, cleaner rows.

**Input**

- The first run works on one click: the form opens with postal code `9000`
  (Ghent), houses and apartments for sale, 100 results.
- Larger towns can be typed by name ("Ghent", "Leuven", "Ixelles") and are read
  as that town's own postal code. Province names still search the whole province.
- A postal code or province typed into the links box is read as a place.
- Clear messages instead of odd results, with nothing charged: a minimum above
  the maximum (price, bedrooms, area, plot, rooms, facades, year), a negative
  or non-numeric limit, an unknown property type, listing type, EPC label,
  condition or sub-type. "rent", "FOR_RENT", "flat" and "maison" are understood.
- A link the scraper can't read, or a link file that can't be opened, no longer
  falls back to a search of all of Belgium.
- Prices typed as "250.000" or "250 000" are read as 250000.
- The form is shorter. Filters, the daily feed and less common options sit in
  collapsed sections. Three internal tuning fields left the form; they keep
  safe defaults and are still read if sent.

**Output**

- `streetNumber` is never an empty string. A number written inside `street`
  ("56-58 Chaussée d'Ixelles", "Boeretang 280") moves to `streetNumber`; `address`
  follows.
- Towns and streets written in capitals ("ANDERLECHT", "RUE DU PRÉSIDENT") are
  title-cased. Other spellings are left as the agency wrote them.
- `agencyEmail` now only holds the agency's own address. Lead-form relay
  addresses go to the new `agencyLeadEmail`. When the listing text gives an
  address on the agency's own website domain, that address fills `agencyEmail`.
  **Behaviour change:** rows that used to show a relay in `agencyEmail` now show
  it in `agencyLeadEmail`.
- New `pricePerSqm`: EUR per m² of living area (per month for rentals); empty on
  new-build projects.
- `sourceUrl` is now the public immoweb.be search page the row came from.
- Belgian phone numbers written with a 0 after +32 are corrected.
- Coordinates are rounded to 7 decimals, whole numbers are integers, empty strings
  are null, dates are ISO 8601.
- Dataset views updated to the real columns, including `title`, `pricePerSqm`,
  `streetNumber`, coordinates and `agencyLeadEmail`.

**Run behaviour**

- `maxItems` is now exact in every mode, including new-build units.
- A run that loses rows to duplicates no longer stops short of `maxItems` while
  more pages are available.
- Every finished run ends with a one-line status ("Delivered 100 listings...").
- A run that can't reach Immoweb says so plainly and that nothing was charged.

### 0.1.15 (2026-10-17)

- A pasted agency page now points to the Immoweb Agency Scraper, which returns the
  agency and everything it has online.
- Place parsing is now shared with the Agency Scraper. No change in behaviour; the
  same tests pass.

### 0.1.14 (2026-10-17)

The fixes the Zillow Actors needed after real customer runs, applied here before
anyone hits them.

- **A postal code or place Immoweb does not know is no longer searched as all of
  Belgium.** Immoweb never rejects a search: it drops what it does not recognise and
  searches with the rest, so postal code 1999 came back as 86,354 listings across the
  country, charged. Every search is now checked against what Immoweb says it applied.
  An unknown location is skipped, nothing is charged, and the run says which one. In a
  list like "9000, 1999" the unknown code is named and the rest searched.
- **Postal codes inside free text are read.** "9000 Gent", "B-1050 Ixelles",
  "Rue de la Loi 16, 1000 Bruxelles" and "Provincie Antwerpen" — the formats
  spreadsheets and CRMs produce. A town name alone is still skipped on purpose: Immoweb
  reads "Gent" as the whole Gent district, nearly three times the listings of 9000.
- **A filter Immoweb ignores is caught.** `minBedrooms` in a hand-edited URL is renamed
  to `minBedroomCount`, which Immoweb reads; anything else Immoweb ignored is named in
  the log and the run's status, since those listings are not narrowed by it.
- **Listing IDs work on their own** — `21895815`, as a number or text, in the URL
  field, in `listingIds`, or in an uploaded file.
- **The same listing given twice is fetched once**, whichever URL form it is in.
- **URLs without `https://`, site-relative paths, and URLs pasted with brackets or
  trailing punctuation are accepted**, and one malformed URL can no longer stop the
  run for the rest.
- **Map-view URLs work.** A link to Immoweb's map view returns the search it shows.
- **A server error on the first page of a search is retried** instead of losing the
  search. Uploaded URL files are retried too.
- **A run of removed listings says so**: "2 listing(s) have been removed from
  Immoweb. Nothing was charged." An unknown property type now ends the same way
  instead of failing.
- **"Sold" is refused with a reason**, since Immoweb publishes no sold listings, rather
  than quietly returning listings for sale.
- **Agency pages and the home page get a specific message** instead of "not a search".
- **Monitoring** now notices units selling in a new-build project, and no longer
  reports a listing as changed when an agency only re-saves it.

### 0.1.12 (2026-10-17)

- **The whole market, not the first 10,000.** An Immoweb search stops at 9,960 rows
  whatever its total, and the previous build stopped there silently — or at 300, with
  its 10-page default. A search larger than that is now split by province and property
  type when your maximum asks for more. Antwerp province, 12,939 listings, came back
  complete: 12,474 rows plus 466 merged duplicates.
- **URLs copied from immoweb.be work.** The previous build refused most links copied
  from a browser and failed the run. They now return the same search.
- **French and Dutch URLs return the right property type.** Re-issued on the English
  path, Immoweb misreads `appartement` and returns houses. URLs now keep their language.
- **Three property types were removed from the menu** because Immoweb does not know
  them and answered with every house in Belgium: House group, Other properties, and
  Student accommodation. `Other` and `Mixed-use building` were added. Any search
  Immoweb does not understand is now refused instead of charged.
- **Date first listed is filled on full-details rows.** It was empty on every row:
  the search result's empty value overwrote the listing page's real one.
- **A pasted URL no longer adds a second search.** The platform's default property
  type triggered an extra nationwide search on every run that used URLs.
- **Life-annuity rows outside your price range are dropped before charging.**
  Immoweb's price filter does not apply to them.
- **Faster and lighter.** Runs are quicker and only read as many search pages as your
  maximum needs.
- **New: provinces and postal codes as plain text** in `locations`, in English, French
  or Dutch; **Maximum per search**; uploaded URL files; and field names AI agents
  guess (`url`, `location`, `transactionType`) are read.
- **Removed nine fields** Immoweb never fills, empty on all 165 detail rows tested:
  `hasBalcony`, `hasFitnessRoom`, `hasTennisCourt`, `has360Tour`, `isFirstOccupation`,
  `parkingCountClosedBox`, `pricePerSqm`, `projectConstructor`,
  `urbanPlanningInformation`.
- **Renamed inputs** to match the other studioxl Actors: `listingType`
  (`forSale`/`forRent`), `minBedrooms`, `minLivingArea`, `minYearBuilt`, `minFacades`,
  `mergeDuplicateListings`, `splitLargeSearches`. The previous names are still read.
- An empty result now succeeds with a status message saying why, and nothing charged.
  A removed listing is skipped and not charged instead of failing the run.

### 0.1 — 2026-07-29

First public release.

#### Added

- Search by property type, transaction type, region, postal code, price, bedroom
  count, living area and land area. Every filter is applied by Immoweb server-side,
  so narrowing a search reduces cost rather than just trimming the output.
- **Fast mode** returns 34 fields per listing.
- **Full detail mode** (`scrapeDetails`) adds ~80 more fields per listing: EPC energy
  score and consumption, construction year, building condition, cadastral income,
  flood-zone classification, room-by-room surfaces, kitchen type, 21 amenity flags,
  view and bookmark counts, and the advertiser's email, phone, website and IPI number.
- **Incremental monitoring** (`onlyNewSinceLastRun`). Listings are remembered between
  runs in a named key-value store; later runs return only new or genuinely changed
  listings, flagged with `changeDetected`. Unchanged listings are skipped and never
  charged. View counts are excluded from change detection so ordinary traffic does
  not mark every listing as changed.
- **Two-level deduplication.** Listings are deduplicated by ID, since Immoweb
  reorders results between requests and the same listing often lands on two
  consecutive pages. Properties advertised by several agencies under different IDs
  are also merged, by matching coordinates, price and surface (`dedupeWithinRun`).
- **Fail-closed structure check.** Every page is verified against the structure the
  parser expects. If Immoweb redesigns, the run aborts with an explicit error instead
  of finishing "successfully" with an empty dataset.
- **Migration-safe state.** Run counters and the seen-listing set are persisted to
  the key-value store, so a container migration mid-run cannot cause listings to be
  re-emitted or charged twice.
- Direct classified URLs are accepted alongside search URLs and detected
  automatically.
- Pagination stops cleanly at Immoweb's limit of about 10,000 results per search.

#### Notes

- Immoweb defines `transaction.investor` fields (rental income, ROI, occupancy rate)
  but never populates them — verified across 140 listings including apartment blocks.
  They are therefore not offered as output fields.
