# Otodom, Storia, Imovirtual Scraper (search, filters, alerts) (`datahamster/olx-group-real-estate`) Actor

Otodom.pl, Storia.ro and Imovirtual.com scraper for OLX Group real estate: flats, houses and land to rent or buy, by location, price, area and rooms or any result URL. Rows with price, price per m2, area, rooms, floor, district, city, agency, photos. Monitor mode alerts on new listings.

- **URL**: https://apify.com/datahamster/olx-group-real-estate.md
- **Developed by:** [Viktor Dubnytskiy](https://apify.com/datahamster) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 result items

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Otodom, Storia, Imovirtual Scraper (search, filters, alerts)

One scraper for OLX Group's three property portals: **Otodom.pl** (Poland), **Storia.ro** (Romania) and **Imovirtual.com** (Portugal). They run on the same platform, so a single actor returns flats, houses, rooms, land, offices and commercial units to rent or buy — filtered by location, price, area and rooms, or taken straight from any result URL — and monitor mode gives you only the listings that are new or re-priced since your last run.

### What you get — example output

One row per listing. Real values from the example dataset (`site: storia`, rent, flat, `bucuresti`):

| Field | Example value | What it is |
|---|---|---|
| `listingId` / `site` | `10572841` / `storia` | Portal listing id and which portal the row came from |
| `title` | `Inchiriere 2 camere 13Septembrie\|Bloc Nou\|Parcare` | Listing headline |
| `price` / `currency` | `649` / `EUR` | Numeric price in the portal's currency |
| `pricePerSqm` | `11.8` | Price per square metre, computed by the portal |
| `area` / `rooms` / `floor` | `55` / `TWO` / `NINTH` | Living area in m², room count and floor as enums |
| `city` / `district` | `Bucuresti` / `13 Septembrie` | Plus `province` and the full `locationPath` (`13 Septembrie, Sectorul 5, Bucuresti`) |
| `isPrivateOwner` | `false` | Private seller vs. agency listing |
| `agencyName` | `George Estate Group` | Agency publishing the listing (`agencyId` alongside) |
| `createdAt` / `pushedUpAt` | `2026-09-04 11:47:09` / `2026-09-04T11:47:09+03:00` | First published and last bumped |

Full field list: `listingId`, `site`, `country`, `url`, `title`, `transaction`, `estate`, `price`, `currency`, `rentPrice`, `pricePerSqm`, `area`, `terrainArea`, `rooms`, `floor`, `city`, `district`, `province`, `locationPath`, `lat`, `lon`, `isPrivateOwner`, `isPromoted`, `isExclusive`, `agencyId`, `agencyName`, `developmentId`, `developmentTitle`, `createdAt`, `pushedUpAt`, `images[]`, `description`, `tags[]`, `page`, `rank`, `scrapedAt`.

### Use cases

- **Rental yield and price research**: pull a district with `price`, `area` and `pricePerSqm` already computed and compare neighbourhoods directly.
- **Flat hunting with alerts**: watch one city and price band and be notified the moment a matching listing appears or drops price.
- **Agency and market analysis**: measure how much of a market is agency-listed vs. private (`isPrivateOwner`, `agencyName`) and how fast listings are bumped.

### How it works

All three portals are one Next.js platform, so the actor reads the structured listing data the pages embed — no login, no cookies, no proxy. Give `site`, `transaction`, `estate` and a `location` segment copied from the portal's own URL, plus optional price/area/room bounds; or paste `searchUrls` to reuse a filter set exactly. Pagination runs 36 listings per page until `maxPages` or `maxItems`. In `monitor` mode the run is compared with the previous run of the same Task and only new or re-priced listings are pushed, with an optional webhook or Telegram summary.

### Input

| Field | Meaning | Default |
|---|---|---|
| `site` | `storia` (RO), `otodom` (PL) or `imovirtual` (PT) — one per run | `storia` |
| `transaction` | `rent` or `sale` | `rent` |
| `estate` | `flat`, `house`, `room`, `terrain`, `commercial`, `office` | `flat` |
| `location` | Location segment as in the portal URL, e.g. `bucuresti` | `bucuresti` |
| `priceFrom` / `priceTo` | Price range in the portal's currency, e.g. `400` / `900` | — |
| `areaFrom` | Minimum living area in m², e.g. `50` | — |
| `roomsFrom` | Minimum rooms, 1–6 | — |
| `searchUrls` | Any result URLs of that portal; overrides the fields above | — |
| `maxPages` | Pages per search, 36 listings each | `3` |
| `maxItems` | Stop after this many listings | `20` |
| `mode` | `scrape`, or `monitor` for new/changed only | `scrape` |
| `webhookUrl`, `telegramBotToken`, `telegramChatId` | Where monitor-mode change summaries are sent | — |

### Pricing

| Event | Price |
|---|---|
| result | $0.001 per listing ($1 per 1,000) |
| monitor-check | $0.005 per monitor run |
| change | $0.001 per new/changed listing |

Charged only for listings actually pushed — an empty run costs you nothing. No proxy needed.

### Why this actor

- Three countries, one schema: Otodom, Storia and Imovirtual rows line up field for field.
- `pricePerSqm` comes straight from the portal, so €/m² comparisons need no maths of your own.
- Private-owner and agency flags on every row, which is what separates real market supply from agency churn.
- Any result URL can be pasted in, so the filters you set on the site are preserved exactly.
- Monitor mode with new-listing and price-change alerts, delivered by webhook or Telegram — and no proxy to configure.

### Limits

- Listing detail pages are not opened (`url` leads to the ad).
- Location must be given as the portal's URL segment (copy it from the site's address bar).
- `lat` / `lon` are filled only where the portal exposes them; `city`, `district`, `province` and `locationPath` are always present.
- One run covers one portal; schedule one Task per country to cover all three.

### FAQ

**Can I monitor new listings for a search?** Yes. Save the input as a Task, set `mode: monitor` and schedule it — each run returns only listings that appeared or changed price since the previous run, and can POST a summary to `webhookUrl` or a Telegram chat.

**Which countries and portals are supported?** Poland via Otodom.pl, Romania via Storia.ro and Portugal via Imovirtual.com. Pick one per run with the `site` field; prices come back in that portal's own currency.

**How do I set the location?** Copy the location part of the portal's own search URL: `bucuresti` or `cluj-napoca` on Storia, `mazowieckie/warszawa/warszawa/warszawa` on Otodom, `lisboa` on Imovirtual. Or skip it and paste the whole result URL into `searchUrls`.

### Changelog

- 0.1: initial release.

***

If this actor saved you time, please leave a review on its Store page — it is the main way other buyers find it. Bug reports and field requests are welcome in the **Issues** tab.

# Actor input Schema

## `site` (type: `string`):

Which OLX Group portal to search. Accepted codes: storia (storia.ro, Romania), otodom (otodom.pl, Poland), imovirtual (imovirtual.com, Portugal). One run covers one portal. Example: "storia".

## `transaction` (type: `string`):

Whether to search listings to rent or to buy. Accepted values: rent, sale. Example: "rent".

## `estate` (type: `string`):

Property type. Accepted values: flat, house, room, terrain (land), commercial, office. Example: "flat".

## `location` (type: `string`):

Location segment(s) exactly as they appear in the portal's own URL after the property type. Examples: "bucuresti" or "cluj-napoca" (storia), "mazowieckie/warszawa/warszawa/warszawa" (otodom), "lisboa" (imovirtual).

## `priceFrom` (type: `integer`):

Minimum price, whole number in the portal's currency (EUR on storia, PLN on otodom, EUR on imovirtual). Example: 400. Optional.

## `priceTo` (type: `integer`):

Maximum price, whole number in the portal's currency. Example: 900. Optional.

## `areaFrom` (type: `integer`):

Minimum living area in square metres. Example: 50. Optional.

## `roomsFrom` (type: `integer`):

Minimum number of rooms, 1 to 6. Example: 2. Optional.

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

Any result URLs of the chosen portal, with the filters you set there; overrides site, transaction, estate and location. Example: \["https://www.storia.ro/ro/rezultate/inchiriere/apartament/bucuresti"]. Optional.

## `maxPages` (type: `integer`):

How many result pages to fetch per search, 36 listings per page. Example: 3.

## `maxItems` (type: `integer`):

Stop the run after this many listings; you are charged only for listings actually pushed. Example: 20.

## `mode` (type: `string`):

scrape = return all matching listings; monitor = return only listings that are new or changed price since the previous run of this saved Task. Example: "scrape".

## `monitorKey` (type: `string`):

Optional state key for monitor mode when the actor is started directly instead of from a saved Task; any stable string, e.g. "bucuresti-2cam-rent".

## `webhookUrl` (type: `string`):

Optional HTTPS URL that receives a POST with the monitor-mode change summary (JSON). Example: "https://hooks.example.com/storia".

## `telegramBotToken` (type: `string`):

Optional Telegram bot token used to send monitor-mode change summaries. Format: "123456789:AA...".

## `telegramChatId` (type: `string`):

Optional Telegram chat id that receives monitor-mode summaries. Example: "-1001234567890".

## Actor input object example

```json
{
  "site": "storia",
  "transaction": "rent",
  "estate": "flat",
  "location": "bucuresti",
  "maxPages": 3,
  "maxItems": 20,
  "mode": "scrape"
}
```

# Actor output Schema

## `results` (type: `string`):

All pushed rows (dataset, JSON)

## `resultsTable` (type: `string`):

Dataset in the Console viewer

## `runSummary` (type: `string`):

RUN\_SUMMARY record (pushed, skipped, emptyReason)

# 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 = {
    "site": "storia",
    "transaction": "rent",
    "estate": "flat",
    "location": "bucuresti",
    "maxItems": 20,
    "mode": "scrape"
};

// Run the Actor and wait for it to finish
const run = await client.actor("datahamster/olx-group-real-estate").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 = {
    "site": "storia",
    "transaction": "rent",
    "estate": "flat",
    "location": "bucuresti",
    "maxItems": 20,
    "mode": "scrape",
}

# Run the Actor and wait for it to finish
run = client.actor("datahamster/olx-group-real-estate").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 '{
  "site": "storia",
  "transaction": "rent",
  "estate": "flat",
  "location": "bucuresti",
  "maxItems": 20,
  "mode": "scrape"
}' |
apify call datahamster/olx-group-real-estate --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datahamster/olx-group-real-estate"
        }
    }
}

```

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/dgnTOcvHVDHkAHUgX/builds/ZTEAN1DPkRyuv8L65/openapi.json
