# Changelog of Otodom Scraper: Poland Property Listings & Price Monitor (`redomic/otodom-scraper`) Actor

- **URL**: https://apify.com/redomic/otodom-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/redomic/otodom-scraper.md

## Changelog

### 0.1.0

- First build: scrape Otodom.pl search results (sale/rent, flats/houses/etc.) from start URLs or structured filters.

- Monitor mode with named KV store state: reports new, changed and removed listings (charges only changes).

- Flat output with price, area, rooms, floor, advertiser, location, and timestamps; status/summary rows per run.

- Output only real `/pl/ad/` listing rows (investment/project and non-ad cards filtered); rows with missing price are still returned when the source card omits it.

- Normalized `dealType` to `sale`/`rent` (matching input); normalized `street` (strips common Polish prefixes like `ul.`/`al.`); `floor` keeps `0` for ground floor and `null` when unknown; documented `buildYear`/`market`/`lat`/`lng` as not available on search cards.

- README now includes diverse real sample rows (sale/rent, agency/private, non-Warsaw) and monitor change output examples.

- Monitor `changed` rows now include `previousPriceAmount`, `previousPriceCurrency`, and `previousPricePerM2` (when present in stored state) alongside current price fields.

- 0.1.0 (2026-11-08): sale+room queries return 0 on Otodom (no sale-room market). Treat this known no-supply case as an ok/empty result with a clear reason instead of failing; added rent-room and sale-room fixtures/tests.

- 0.1 (2026-11-09): a sale+room query now stays a successful empty result even when Otodom answers 403/404 or with an unparsable shell page (the 2026-11-08 `empty: 0 rows` failures); real queries still report blocked/failed normally.

- 0.1 (2026-11-09): sale+room no-supply handling now also covers monitor mode (a blocked/404/parse-error page for a sale-room query is an ok empty result, not a failed run), and no-supply detection is decided by each source URL's deal/property path so a flat start URL is never misread as sale-room just because the input carries stale `dealType`/`propertyType`. Added fresh 2026-11-09 sale-room/rent-room fixtures and regression tests.

- 0.1 (2026-11-09): optional detail enrichment (`fetchDetails`, default `true`). Each `/pl/ad/` search card opens its `/pl/oferta/` page and merges GPS (`lat`/`lng`), `buildYear`, `monthlyCharges` (czynsz), `heating`, `buildingType`, `buildingMaterial`, `windowsType`, `ownership`, `constructionStatus`, `market`, `features`, `photos` and a plain-text `description`; `fetchDetails: false` keeps the fast search-card path. Seller contact data and e-mails/phones in descriptions are never returned. Added `src/detail.py` with fixture tests.

- 0.1 (2026-11-09): empty searches and quiet monitor runs now succeed (exit 0) with an `empty`/`ok` status row instead of failing; monitor zero-change runs stay exit 0. Added tests for empty search, monitor empty and monitor no-change.

- 0.1 (2026-11-10): default output drops listings without a published price (default sample has 0 null-price rows); set `includeNoPrice: true` to also return them. Added `lat`/`lng`+`radiusKm` inputs: centre coords add `distanceKm` to GPS rows, and with `radiusKm` only within-radius rows are returned (verified: Warsaw Ursus 2km radius returns only `distanceKm <= 2` rows, central Warsaw 2km correctly returns none).

- 0.1 (2026-11-10): blocked/unparsable pages are never mistaken for an empty result. A blocked source now emits an explicit `failed` status row (or `partial` when earlier pages returned rows) and fails the run instead of an empty SUCCEEDED; in monitor mode a blocked run no longer reaches the diff, so it cannot mark tracked listings as removed or wipe the stored state. Investment junk rows stay out of monitor state/diffs (regression test on the live Warsaw fixture).

- 2026-11-10: Added showcase Store task for Warsaw flats for sale (`.actor/store_task.json`).

- 0.1 (2026-11-11): `fetchDetails` now defaults to `false` for faster, lower-cost search-card runs (set `fetchDetails: true` for offer-page enrichment). Every fetch uses the configured proxy, with residential PL escalation only for blocked datacenter requests.

- 0.1 (2026-11-11): monitor mode now tracks append-only `priceHistory` (last 20 observations) in stored state and exposes `priceChangePct`/`priceDirection` (`up`/`down`/`flat`/`new`) on change rows. Added `droppedOnly` input to emit only down-price changes while still tracking all changes, and summary rows now include `dropped`.

- 0.1 (2026-11-12): all fetches use the exposed proxy config with per-request datacenter→residential escalation on blocks (detail enrichment stays off residential), `fetchDetails` remains default off for lower cost, and blocked/unparsable pages are reported as failed/partial (never silent 403-as-empty).

- 0.1 (2026-11-11): added `agencyPortfolio` mode to group listings by agency/developer with counts, median price/m², listing URLs, and monitor tracking of new/removed stock (unchanged runs report no portfolio rows).

- 0.1 (2026-11-12): added `postedWithinDays` input (1/3/7/14/30) to filter recent listings in search and monitor runs. Structured searches add `daysSinceCreated`; startUrls get it when absent (preserved when present), and fixture-backed runs keep only in-window rows while existing inputs remain unchanged.

- 0.1 (2026-11-12): verified economics/reliability path: all requests use exposed proxy config with per-request datacenter-first fetches and residential escalation only after repeated blocks on that URL, `fetchDetails` defaults `false` so bulk runs stay search-card-only and detail enrichment never escalates, and 403/503 fetch failures are reported as failed/partial status rows (never silent empty success). Added fixture-backed regression tests.

- 0.1 (2026-11-12): added `districts` input to search multiple districts in one run (each as a location sub-path) sharing a single `maxItems` budget with cross-source dedupe and per-source status rows.

- 0.1 (2026-11-12): added `advertiserType` input filter (`all`/`private`/`agency`/`developer`, default `all`) mapped to Otodom `ownerTypeSingleSelect` on structured searches and `startUrls` (preserved when already present). Returned rows are filtered to the selected advertiser type when set; default behavior is unchanged.

- 0.1 (2026-11-13): ensured all fetches route through exposed proxy config with per-request datacenter→residential escalation on blocks (detail enrichment never escalates), `fetchDetails` stays default off, and blocked/unparsable/5xx pages are surfaced as failed or partial status rows (never silent empty rows); monitor state is preserved on blocked runs.

- 0.1 (2026-11-13): added `sortBy` input (`default`/`newest`/`oldest`/`price_asc`/`price_desc`/`area_asc`/`area_desc`) to control result order in search and monitor runs; structured/start URLs include Otodom `by`/`direction` params when possible and results are ordered before `maxItems` capping.

- 0.1 (2026-11-13): added `pricePerM2Min`/`pricePerM2Max` filters (PLN/m²) mapped to Otodom `pricePerMeterMin`/`pricePerMeterMax` for structured and `startUrls` searches, with post-filter on `pricePerM2` (or derived `priceAmount/areaM2` when missing) in search and monitor runs.

- 0.1 (2026-11-14): advertiserType, centre radius and pricePerM2Min/Max filters are applied while collecting search/monitor rows so paging continues until `maxItems` matching listings are gathered (up to page cap), preventing underfilled results when early pages contain non-matching rows.

- 0.1 (2026-11-14): added `totalFloors` to search rows and offer-page detail enrichment (building/storey count) for richer building context; exposed in dataset schema and README with real example output.

- 0.1 (2026-11-14): `fetchDetails` defaults to `true` for richer rows (GPS, build year, total floors, monthly charges and other offer-page details) via datacenter-only detail requests; set `fetchDetails: false` for fast search-card-only runs.

- 0.1 (2026-11-14): verified default `fetchDetails: true` remains datacenter-only for detail enrichment with newest-sample fixture coverage >=18/20 rows having non-null lat/lng/buildYear/monthlyCharges and asserted run cost <= $0.01/row; added newest-sample fixtures and regression test.

- 0.1 (2026-11-15): per-source `_status` rows and the run `summary` are stored in the run key-value store (`STATUS` and `SUMMARY`) instead of the dataset, so exports and the Output table contain only listing/change rows; dataset schema no longer includes `_status`.

- 0.1 (2026-11-15): added `floorMin`/`floorMax` filters (floor number, with -1 cellar/basement and 0 ground floor) mapped to Otodom `floorNumberMin`/`floorNumberMax` for structured and `startUrls` searches; listings are filtered while collecting (non-numeric floors excluded) so `maxItems` returns matching rows.

- 0.1 (2026-11-15): added `buildYearMin`/`buildYearMax` filters (construction year) for structured and `startUrls` searches. Listings are filtered by build year while collecting and after detail enrichment so capped runs still return matching rows.

- 0.1 (2026-11-16): added detail-only `elevator`, `energyCertificate` and `availableFrom` fields to listing rows (when published on the offer page), with tests, dataset view columns and README/output docs updated.
