# Otodom, Storia & Imovirtual Real Estate Scraper (`gololo/otodom-scraper`) Actor

Fast real estate scraper for Otodom.pl (Poland), Storia.ro (Romania) & Imovirtual.com (Portugal). Extracts property prices, price/m², seller phone numbers, GPS coordinates, floor plans, photos & attributes.

- **URL**: https://apify.com/gololo/otodom-scraper.md
- **Developed by:** [gol lol](https://apify.com/gololo) (community)
- **Categories:** Real estate, Lead generation, E-commerce
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Otodom, Storia & Imovirtual Real Estate Scraper (Poland, Romania & Portugal)

Extract structured real estate listings from OLX Group's three flagship European property portals in a single fast, low-memory (`128 MB`) Actor:

- **Poland (`otodom.pl`)** — Apartments, houses, rooms, plots, commercial properties, and garages for sale (`sprzedaz`) or rent (`wynajem`).
- **Romania (`storia.ro`)** — Residential and commercial real estate across Bucharest, Cluj-Napoca, Timișoara, Iași, and all of Romania (`vanzare` & `inchiriere`).
- **Portugal (`imovirtual.com`)** — Property listings across Lisbon, Porto, Algarve, Setúbal, and nationwide (`comprar` & `arrendar`).

***

### Key Features

- **Seller Phone Numbers & Lead Generation (`phones`, `primaryPhone`)** — Automatically extracts direct owner (`PRIVATE` / FSBO / *Bezpośrednio*) and agency phone numbers from Next.js property data payloads without requiring a headless browser.
- **Exact GPS Coordinates & Full Address Hierarchy** — Captures `latitude`, `longitude`, `province`, `county`, `city`, `district`, `subdistrict`, `street`, and `fullAddress`.
- **55+ Normalized Real Estate Fields** — Total price, `pricePerSqm` (price/m²), monthly administrative `rentPrice`, `areaSqm`, `terrainAreaSqm`, `rooms`, `floor`, `buildingFloors`, `buildYear`, `buildingType`, `constructionStatus`, `heating`, `ownership`, `energyCertificate`, `floorPlans`, 3D virtual tours, high-resolution `photos`, `equipment`, `extras`, and `security`.
- **Direct URLs or Structured Filters** — Paste any search URL or individual offer URL from `otodom.pl`, `storia.ro`, or `imovirtual.com`, or build a query using structured filters (`country`, `transactionType`, `propertyType`, `locationSlug`, `priceMin`, `priceMax`, `areaMin`, `areaMax`, `sellerType`, `marketType`, `sortBy`).
- **Bypass Pagination Caps (`splitByPrice`)** — Automatically bisects large searches by price range when a search exceeds 3,600 listings.
- **Cross-Run Price-Drop & Price-Change Tracking (`trackPriceChanges`)** — Automatically tracks historical property prices across runs in `otodom-scraper-monitor-state` and populates `previousPrice`, `priceDelta`, and `priceChangePercent` whenever an owner or agency lowers or raises the asking price.
- **Incremental Deal & Price-Drop Monitoring (`onlyNewListings` + `includePriceDrops`)** — Remembers previously scraped property IDs so scheduled runs only return newly listed properties and existing properties whose price dropped since the last run.

***

### Quick Start Examples

#### 1. Scrape Warsaw Private Owner (FSBO) Apartments on Otodom.pl

```json
{
  "country": "pl",
  "transactionType": "SELL",
  "propertyType": "FLAT",
  "locationSlug": "mazowieckie/warszawa",
  "sellerType": "PRIVATE",
  "sortBy": "LATEST",
  "includeFullDetails": true,
  "maxListings": 100
}
```

#### 2. Paste Any Otodom.pl, Storia.ro, or Imovirtual.com URLs

```json
{
  "searchUrls": [
    { "url": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/malopolskie/krakow" },
    { "url": "https://www.storia.ro/ro/rezultate/vanzare/apartament/bucuresti" },
    { "url": "https://www.imovirtual.com/pt/resultados/comprar/apartamento/lisboa" }
  ],
  "includeFullDetails": true,
  "maxListings": 250
}
```

#### 3. 15-Minute New Property Alert (Incremental Monitoring)

```json
{
  "country": "pl",
  "transactionType": "SELL",
  "propertyType": "FLAT",
  "locationSlug": "mazowieckie/warszawa",
  "sellerType": "PRIVATE",
  "sortBy": "LATEST",
  "onlyNewListings": true,
  "maxListings": 50
}
```

***

### Sample Output Record

```json
{
  "id": "68598012",
  "slug": "bemowo-47-81-m-ogrodek-64-m-garaz-w-cenie-metro-bezposrednio-ID4DPty",
  "url": "https://www.otodom.pl/pl/oferta/bemowo-47-81-m-ogrodek-64-m-garaz-w-cenie-metro-bezposrednio-ID4DPty",
  "portal": "www.otodom.pl",
  "country": "PL",
  "title": "Bemowo| 47,81 m² | Ogródek 64 m²| Garaż w cenie | Metro | Bezpośrednio",
  "transactionType": "SELL",
  "estateType": "FLAT",
  "marketType": "SECONDARY",
  "price": 974990,
  "currency": "PLN",
  "pricePerSqm": 20393.01,
  "rentPrice": 960,
  "hidePrice": false,
  "areaSqm": 47.81,
  "terrainAreaSqm": null,
  "rooms": 2,
  "floor": 0,
  "floorRaw": "ground_floor",
  "buildingFloors": 4,
  "buildYear": 2017,
  "buildingType": "block",
  "buildingMaterial": null,
  "constructionStatus": "ready_to_use",
  "heating": "urban",
  "ownership": "full_ownership",
  "windowsType": "plastic",
  "energyCertificate": null,
  "availableFrom": "2026-11-04",
  "isPrivateOwner": true,
  "sellerType": "private",
  "sellerId": "10394678",
  "sellerName": "Marcel T",
  "phones": ["+48518031104"],
  "primaryPhone": "+48518031104",
  "agencyId": null,
  "agencyName": null,
  "agencySlug": null,
  "developmentTitle": null,
  "developmentUrl": null,
  "province": "mazowieckie",
  "county": null,
  "city": "Warszawa",
  "district": "Wola",
  "subdistrict": "Ulrychów",
  "street": "ul. Jana Olbrachta",
  "fullAddress": "ul. Jana Olbrachta, Ulrychów, Wola, Warszawa, mazowieckie",
  "latitude": 52.233852,
  "longitude": 20.928814,
  "description": "Na sprzedaż nowoczesne, dwupokojowe mieszkanie o powierzchni 47,81 m²...",
  "thumbnailUrl": "https://ireland.apollo.olxcdn.com/v1/files/.../image;s=314x236;q=80",
  "photos": [
    "https://ireland.apollo.olxcdn.com/v1/files/.../image;s=2048x1536;q=80"
  ],
  "photosCount": 14,
  "floorPlans": [
    "https://ireland.apollo.olxcdn.com/v1/files/.../image"
  ],
  "videoUrl": null,
  "view3dUrl": null,
  "walkaroundUrl": null,
  "equipment": ["furniture", "washing_machine", "dishwasher", "fridge", "stove", "oven"],
  "extras": ["garage", "garden", "air_conditioning", "lift", "terrace"],
  "security": ["entryphone", "monitoring", "closed_area", "anti_burglary_door"],
  "tags": ["SEPARATE_KITCHEN", "PARKING_SPOT", "SECURE_BUILDING"],
  "isPromoted": false,
  "isExclusiveOffer": false,
  "createdAt": "2026-11-03T21:27:28Z",
  "modifiedAt": "2026-11-03T21:38:39Z",
  "scrapedAt": "2026-11-04T06:20:00+00:00"
}
```

# Actor input Schema

## `searchUrls` (type: `array`):

Paste one or more search result URLs or direct property offer URLs from otodom.pl, storia.ro, or imovirtual.com. Leave empty to use the structured filters below.

## `country` (type: `string`):

Select which OLX Group real estate portal to query when searchUrls is empty.

## `transactionType` (type: `string`):

Filter by sale (SELL) or rental (RENT) listings when building a search from filters.

## `propertyType` (type: `string`):

Select the type of real estate property to search for.

## `locationSlug` (type: `string`):

Optional location path slug from the portal URL, e.g. 'mazowieckie/warszawa', 'malopolskie/krakow', 'bucuresti', 'cluj/cluj-napoca', 'lisboa', or 'porto'. Leave empty for nationwide.

## `priceMin` (type: `integer`):

Optional minimum total price filter in local currency (PLN or EUR).

## `priceMax` (type: `integer`):

Optional maximum total price filter in local currency (PLN or EUR).

## `areaMin` (type: `integer`):

Optional minimum property surface area in square meters (m²).

## `areaMax` (type: `integer`):

Optional maximum property surface area in square meters (m²).

## `sellerType` (type: `string`):

Filter by all sellers, private owners only (FSBO / Bezpośrednio), real estate agencies, or developers.

## `marketType` (type: `string`):

Filter by primary market (new builds) or secondary market (resale).

## `sortBy` (type: `string`):

Sort search results by newest first, lowest price, highest price, or largest area.

## `maxListings` (type: `integer`):

Maximum total number of property listings to scrape across the entire run.

## `maxListingsPerSearch` (type: `integer`):

Optional cap per individual search URL (0 = use total maxListings).

## `includeFullDetails` (type: `boolean`):

When enabled (recommended), fetches each listing's Next.js data payload to extract seller phone numbers, exact GPS coordinates, full description, floor plans, build year, heating, and equipment.

## `includeDescription` (type: `boolean`):

Include the cleaned plain-text property description in each output record.

## `splitByPrice` (type: `boolean`):

Automatically bisect large searches by price range when a query exceeds 3,600 listings.

## `onlyNewListings` (type: `boolean`):

Deduplicate across runs using a persistent Key-Value Store (otodom-scraper-monitor-state) so scheduled runs only output newly posted properties.

## `trackPriceChanges` (type: `boolean`):

Remembers the last seen price of each property across runs and populates previousPrice, priceDelta, and priceChangePercent whenever a seller or agency changes the price.

## `includePriceDrops` (type: `boolean`):

When 'Only new listings since last run' is enabled, also emit previously seen properties if their price dropped since the last run.

## `skipPromoted` (type: `boolean`):

Exclude promoted or highlighted tiles from the output.

## `proxyConfiguration` (type: `object`):

Optional proxy settings. Direct connections work out of the box via TLS fingerprint impersonation.

## Actor input object example

```json
{
  "searchUrls": [
    {
      "url": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa?by=LATEST&direction=DESC"
    }
  ],
  "country": "pl",
  "transactionType": "SELL",
  "propertyType": "FLAT",
  "sellerType": "ALL",
  "marketType": "ALL",
  "sortBy": "DEFAULT",
  "maxListings": 100,
  "maxListingsPerSearch": 0,
  "includeFullDetails": true,
  "includeDescription": true,
  "splitByPrice": true,
  "onlyNewListings": false,
  "trackPriceChanges": true,
  "includePriceDrops": true,
  "skipPromoted": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Main property table with price, price/m², area, rooms, floor, seller phone, and location.

## `contacts` (type: `string`):

Lead generation view with seller names, phone numbers, private owner flags, and agency details.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchUrls": [
        {
            "url": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa?by=LATEST&direction=DESC"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gololo/otodom-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "searchUrls": [{ "url": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa?by=LATEST&direction=DESC" }] }

# Run the Actor and wait for it to finish
run = client.actor("gololo/otodom-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchUrls": [
    {
      "url": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa?by=LATEST&direction=DESC"
    }
  ]
}' |
apify call gololo/otodom-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gololo/otodom-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/h4mQfHxZkciuWcwic/builds/aHPXFjj7L93QXuh3K/openapi.json
