Otodom Scraper - Polish Real Estate Listings avatar

Otodom Scraper - Polish Real Estate Listings

Pricing

from $1.50 / 1,000 listings

Go to Apify Store
Otodom Scraper - Polish Real Estate Listings

Otodom Scraper - Polish Real Estate Listings

Scrape verified Otodom.pl listings for sale or rent using structured filters. Get compact prices, areas, rooms and locations, plus details and raw data.

Pricing

from $1.50 / 1,000 listings

Rating

0.0

(0)

Developer

Amadeusz

Amadeusz

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Collect verified live Otodom.pl listings for market research, price monitoring and lead lists. Use structured location and filter fields from Console, API or MCP; copied result URLs remain an advanced option. Results are compact, deduplicated and ready for JSON, CSV, Excel or AI-agent workflows.

Run Otodom Scraper · Input schema · Output schema

Quick start

  1. Set Location, Estate type and Transaction.
  2. Add price, area, room, market, building, owner, date or sorting filters.
  3. Set Max items. Enable Enrich with offer details only when you need full descriptions and attributes.
  4. Click Start.
  5. Export the Overview or Enriched details table, or read it through the API.

Otodom Scraper input form with a structured Kraków search and a 100-item limit

The default demo collects up to 100 Kraków apartment-sale listings. An API run with no target uses the same bounded demo instead of starting an unlimited nationwide crawl.

Otodom Scraper output table with normalized listing columns

Ready-to-use recipes

1. Build a search from structured input

This is the primary path for Console, API and MCP clients.

{
"locationQuery": "Kraków",
"estate": "FLAT",
"transaction": "SELL",
"priceMax": 900000,
"roomsNumber": ["TWO", "THREE"],
"maxItems": 100
}

Common cached locations work without live autocomplete.

2. Use one browser search URL

As an advanced alternative, copy a complete Otodom results URL. Its path and all query parameters take full priority over structured search fields.

{
"startUrls": [
"https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/malopolskie/krakow/krakow/krakow?priceMax=900000"
],
"maxItems": 100
}

If an uncommon location cannot be resolved from structured input, use this path or a direct searchingCriteria.

3. Collect several searches in one run

{
"startUrls": [
"https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/malopolskie/krakow/krakow/krakow",
"https://www.otodom.pl/pl/wyniki/wynajem/mieszkanie/mazowieckie/warszawa/warszawa/warszawa"
],
"maxItems": 500
}

URLs run sequentially. Listing IDs are deduplicated across the whole run.

4. Estimate result count before collecting

{
"locationQuery": "Kraków",
"estate": "FLAT",
"transaction": "SELL",
"countOnly": true
}

The result is a free recordType: "estimate" record in both the default dataset and named status output. The live SSR result-page total is authoritative; CountAds is only an optional control measurement.

5. Monitor only listing IDs not seen before

{
"startUrls": [
"https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/malopolskie/krakow/krakow/krakow"
],
"mode": "monitor",
"seenStoreName": "otodom-krakow-monitor",
"seenIdsKey": "OTODOM_SEEN_IDS",
"maxItems": 500
}

Save this input as an Actor Task and attach an Apify Schedule. Every scheduled run must use the same named key-value store and key. Run 1 publishes unseen IDs; later runs publish only IDs not stored before.

Monitoring currently detects new IDs only. It does not report price changes, updated descriptions or expired listings.

Use a separate named store for each monitored search. Leaving seenStoreName empty uses the run's default store, so independent runs do not share history. The ID list grows without automatic retention. By default, monitoring stops after the first page containing only seen IDs; increase stopMonitorOnAllSeenPages when the search order does not reliably put new listings first.

6. Call the synchronous API

curl -X POST \
"https://api.apify.com/v2/acts/ziomixshot~otodom-pl-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"locationQuery": "Kraków",
"estate": "FLAT",
"transaction": "SELL",
"roomsNumber": ["TWO", "THREE"],
"maxItems": 100
}'

The Actor also works through the Apify MCP server, so agents can inspect the input and output schemas before running it.

Input reference

FieldPurpose
locationQueryPrimary target: a Polish city, district or region.
estate, transactionProperty and sale/rent selection.
maxItemsHard stop for verified unique listing rows and listing charges.
enrichDetailsAdds normalized offer-page description, features, status and images.
includeRawDataOpt-in raw and detailRaw diagnostics; disabled by default.
countOnlyReturns an estimate instead of collecting listing rows.
modescrape or monitor.
seenStoreName, seenIdsKeyPersistent state shared by monitor runs.
startUrlsAdvanced array of copied /pl/wyniki/ URLs; each URL fully replaces structured search fields.

Filters for price, area, rooms, market, building type, owner type, date and sorting are available in the full input schema.

Output contract

Every dataset item has a recordType discriminator:

  • listing - normalized Otodom listing;
  • estimate - countOnly result;
  • status - actionable soft-fail status.

Normal scrape and monitor runs also write a run summary to the named status output (OUTPUT key-value record). Use the Overview view for core columns and Enriched details for offer-page fields.

BREAKING CHANGE in v2: listing rows contain only normalized public fields. Keys without values and empty arrays are omitted. Legacy aliases and source-shaped detail* fields are removed.

{
"recordType": "listing",
"id": 68458846,
"url": "https://www.otodom.pl/pl/oferta/fort-bema-3-pokoje-mozliwe-4-do-wejscia-garaz-ID4DfgV",
"title": "Fort Bema | 3 pokoje | możliwe 4 | garaż",
"price": 1329000,
"priceCurrency": "PLN",
"administrativeRent": 1940,
"pricePerSquareMeter": 18985.71,
"area": 70,
"rooms": 3,
"floor": "FOURTH",
"estate": "FLAT",
"transaction": "SELL",
"market": "SECONDARY",
"buildingType": "APARTMENT",
"location": "Fort Bema, Bemowo, Warszawa, mazowieckie",
"city": "Warszawa",
"province": "mazowieckie",
"isPrivateOwner": false,
"dateCreated": "2026-09-25T03:00:27Z",
"createdAtFirst": "2026-09-25T03:00:21Z"
}

For SELL, price is the sale price. administrativeRent is a separate monthly administrative rent in PLN. Estimate and fail-closed status records are free and use their own recordType.

raw and detailRaw appear only with includeRawData: true. They are unstable diagnostics; build integrations from normalized fields.

Pricing

Pay per event: empty results, duplicates, count estimates and soft-fail records are not charged.

EventPrice
Listing$1.50 per 1,000 unique listings
Listing details$2.25 per 1,000 successfully enriched listings
Listing plus details$3.75 per 1,000 full rows

maxItems caps listing rows. Apify's maximum-cost-per-run setting can add an account-level spending limit.

Verified performance snapshot

Observed on 2026-09-26 for a whole-Poland apartment-sale search using build 1.2.10, Polish residential proxies, limit: 72, maxItems: 20000 and maxConcurrency: 16:

  • 20,000 / 20,000 requested listing rows were pushed from 344 result pages;
  • runtime was approximately 73 seconds and compute usage approximately 0.08 CU;
  • failedPages was empty; the run saw no HTTP 403/429 and did not engage adaptive throttling.

Listing pages have up to five attempts with a new proxy session and 1/2/4/8-second backoff before a page is recorded in failedPages. A separate transient-connection test exercised that recovery path; the 73-second benchmark itself did not need retries.

In a 2,000-row Kraków staging run on 2026-09-25, 236 repeated listing appearances were skipped and the final dataset contained no duplicate IDs. Billing happens only after this deduplication. Empty pages, duplicate IDs, count estimates and soft-fail records do not emit a chargeable listing event.

These are dated measurements of those inputs and platform conditions, not a guarantee for every location, result volume or future Otodom version.

Reliability and limitations

  • The Actor paginates until the last page, a configured limit or repeated listing IDs. Otodom has no observed 1,000-result cap, but a moving search index can still change during a long crawl.
  • Search pages use Polish residential proxies. The proxy choice is locked because datacenter routes were unreliable in project tests.
  • Listing pages retry transient failures up to five times. Any page still skipped is listed in failedPages in the named status output.
  • Deduplication is by Otodom listing ID within a run. Monitor mode extends that ID set across runs.
  • Live SSR must confirm every requested filter. An unconfirmed filter returns FILTER_VERIFICATION_FAILED without paid listing rows.
  • Investment/HPR cards and individual records that fail verifiable predicates are excluded before charging.
  • Phone-related keys and phone contact entries are removed from retained raw search and detail objects.
  • Otodom can change page or API structures. A successful run proves the observed result, not future availability.

Development

Runtime and CI use Node.js 20.

npm ci
npm test
apify validate-schema

Run npm run quality:audit with APIFY_TOKEN and an optional ACTOR_BUILD before promoting a release. Pushes to main test and deploy through .github/workflows/deploy-apify.yml, then synchronize the public example input. The workflow requires the APIFY_TOKEN GitHub Actions secret.

Reverse-engineering artifacts are in docs/openapi/; current product decisions and verified evidence are tracked in docs/backlog.md and docs/strategia.md.

FAQ

How fresh are the results?

Listings are fetched from Otodom during each run. Timestamps identify both the source publication time and scrapedAt.

Why is the price field missing?

Otodom can hide a listing price. Output v2 preserves that absence by omitting the field instead of emitting null or inventing a value.

Does the Actor collect seller phone numbers?

No. Phone-related keys and phone contact entries are stripped from both normalized and retained raw output.

How do I report a failure?

Open the Actor's Issues tab and include the run URL. Shared run logs are the fastest way to reproduce proxy or source-site failures.