# Otodom Commercial Scraper - Offices, Warehouses & Plots (`studio-amba/otodom-commercial-scraper`) Actor

Scrape commercial real estate from Otodom.pl: commercial premises (lokale użytkowe), offices, retail units, warehouses and plots for sale or rent across Poland. Prices, price per m2, surfaces, locations and agency details.

- **URL**: https://apify.com/studio-amba/otodom-commercial-scraper.md
- **Developed by:** [Studio Amba](https://apify.com/studio-amba) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 result scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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 Commercial Scraper

Extract commercial real estate listings from [Otodom.pl](https://www.otodom.pl), Poland's largest property portal. This scraper covers the commercial branches of the site: lokale użytkowe (commercial premises), biura (offices), lokale handlowo-usługowe (retail and service units), hale i magazyny (warehouses and industrial halls) and działki (plots of land, a common investment category in Poland).

For each listing you get the price, price per square meter, surface, location down to district level, agency details and photos, as structured JSON.

### What data does it extract?

| Field | Description |
|---|---|
| title | Listing title |
| segment | Commercial segment: premises, office, retail, warehouse, plot |
| propertyType | Otodom category slug: lokal, haleimagazyny, dzialka |
| listingType | sale or rent |
| price | Price in PLN. Null when the advertiser hides it (price on application) |
| currency | PLN |
| priceUnit | What the price means: PLN/miesiąc for rent (total per month), PLN for sale |
| pricePerSqm | Price per square meter where quoted |
| pricePerSqmUnit | PLN/m2/miesiąc for rent, PLN/m2 for sale |
| additionalRent | Additional monthly charge (czynsz) where quoted |
| priceHidden | True when the price is on application |
| surface | Area in m2 (plot area for działki) |
| terrainSurface | Separate land area in m2 where quoted |
| floor | Floor code as reported by Otodom (GROUND, SECOND, ...) |
| street, city, district, province | Location, down to district level |
| latitude, longitude | GPS coordinates (detail pages) |
| imageUrl, imageUrls | Listing photos |
| agencyName, agencyUrl | Listing agency |
| isPrivateOwner | True for private (non-agency) listings |
| datePublished | First publication date |
| url | Full listing URL |
| source, scrapedAt | Provenance |

A note on prices: hidden prices are normal in commercial real estate. When an advertiser quotes no price, the scraper reports `price: null` and `priceHidden: true`. It never invents a number.

### How to scrape Otodom commercial real estate data

1. Set **Search Location** to a Polish city or region, for example `Warszawa`, `Kraków` or `Trójmiasto`. The scraper resolves the name through Otodom's own location search, so results are scoped to that place, not a free-text match.
2. Pick a **Segment**:
   - `premises` — all lokale użytkowe (about 7,800 listings countrywide)
   - `office` — offices (biura), a use-type filter within premises
   - `retail` — retail and service units (handlowo-usługowe)
   - `warehouse` — hale i magazyny
   - `plot` — działki (land)
3. Pick a **Listing Type**: `rent` (wynajem) or `sale` (sprzedaż).
4. Set **Max Results** and run. Results land in the dataset as structured JSON and can be exported as JSON, CSV or Excel.

You can also paste Otodom URLs directly into **Start URLs** — both search-result pages and individual listing pages work. Start URLs override the search inputs.

#### Example: offices for rent in Warsaw

```json
{
    "searchQuery": "Warszawa",
    "segment": "office",
    "listingType": "rent",
    "maxResults": 100
}
```

#### Example: plots for sale in Kraków

```json
{
    "searchQuery": "Kraków",
    "segment": "plot",
    "listingType": "sale",
    "maxResults": 200
}
```

#### Example output item

```json
{
    "title": "Lokal użytkowy 32 m² w Ursusie",
    "segment": "premises",
    "propertyType": "lokal",
    "listingType": "rent",
    "price": 2400,
    "currency": "PLN",
    "priceUnit": "PLN/miesiąc",
    "pricePerSqm": 75,
    "pricePerSqmUnit": "PLN/m2/miesiąc",
    "additionalRent": null,
    "priceHidden": false,
    "surface": 32,
    "terrainSurface": null,
    "floor": "SECOND",
    "street": "ul. Gilarska",
    "city": "Warszawa",
    "district": "Targówek",
    "province": "mazowieckie",
    "agencyName": "Example Nieruchomości",
    "url": "https://www.otodom.pl/pl/oferta/lokal-uzytkowy-32-m-w-ursusie-ID4CdXf",
    "source": "otodom.pl",
    "scrapedAt": "2026-08-18T14:00:00.000Z"
}
```

### Who is this for?

- **Investors and analysts** tracking commercial rents and land prices per city or district
- **Brokers and agencies** monitoring competing supply in a market
- **Proptech and data teams** feeding Polish commercial supply into models and dashboards
- **Site-selection teams** comparing retail and office availability across locations

Plots (działki) deserve a mention: land is a mainstream investment asset in Poland, and this scraper returns plot listings with price per m2, which makes cross-city comparisons straightforward.

### Cost of usage

Pricing is per result: you pay a small fixed amount per listing extracted, plus a small actor-start fee. A run of 100 listings typically completes in a few minutes. The scraper uses a real browser with Polish residential proxies (Otodom sits behind Cloudflare), which is included in the per-result price.

One thing to know: a run's usage cost only settles after the run has SUCCEEDED. Failed runs are not charged per result.

### Tips

- Keep the default proxy settings (Polish RESIDENTIAL). Otodom blocks datacenter IPs.
- `premises` is the widest segment; use `office` or `retail` when you want the use-type filter applied server-side instead of filtering afterwards.
- The scraper reads Otodom's embedded JSON payloads, not the rendered HTML, so fields arrive typed and stable.
- District-level filtering: pass a district name as the location (for example `Mokotów`) — Otodom's location search resolves districts too.
- For monitoring, schedule the actor and dedupe on `url` downstream; `datePublished` tells you what is new.

### Limitations

- Coordinates are only available when scraping listing detail URLs; search results carry location names without GPS.
- Otodom quotes commercial rents as total PLN per month. Per-m2 rents appear in `pricePerSqm` where the advertiser provides the surface.
- The `maxResults` cap is a hard stop; the run finishes as soon as the cap is reached.

### Is scraping Otodom.pl legal?

This scraper extracts publicly available listing data only. It collects no personal data beyond the agency name the advertiser publishes with the listing. You are responsible for how you use the data; check Otodom's terms of service and applicable law for your use case.

### Support

Found an issue or missing a field? Open an issue on the actor's Issues tab and it will be looked at quickly.

# Actor input Schema

## `searchQuery` (type: `string`):

City or region to search, e.g. 'Warszawa', 'Kraków', 'Gdańsk', 'Wrocław'. Leave empty to search Warszawa.

## `startUrls` (type: `array`):

Otodom.pl search-result or listing-detail URLs (e.g. https://www.otodom.pl/pl/wyniki/wynajem/lokal/mazowieckie/warszawa/warszawa/warszawa). Overrides searchQuery, listingType and segment if provided. Maximum 500 URLs.

## `listingType` (type: `string`):

Properties for rent (wynajem) or for sale (sprzedaż).

## `segment` (type: `string`):

Commercial segment. 'premises' covers all lokale użytkowe; 'office' and 'retail' are use-type filters within premises; 'warehouse' is hale i magazyny; 'plot' is działki (land).

## `maxResults` (type: `integer`):

Hard cap on the number of listings to scrape. The run stops as soon as this many items are pushed.

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

Polish RESIDENTIAL proxies are required — Otodom.pl sits behind Cloudflare and blocks datacenter IPs.

## Actor input object example

```json
{
  "searchQuery": "Warszawa",
  "listingType": "rent",
  "segment": "premises",
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "PL"
  }
}
```

# Actor output Schema

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

No description

# 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 = {
    "searchQuery": "Warszawa",
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "PL"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studio-amba/otodom-commercial-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 = {
    "searchQuery": "Warszawa",
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "PL",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("studio-amba/otodom-commercial-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 '{
  "searchQuery": "Warszawa",
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "PL"
  }
}' |
apify call studio-amba/otodom-commercial-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,studio-amba/otodom-commercial-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/rTenaMcw6Hdqc3cZ0/builds/caHEsFReS7fTw0h9d/openapi.json
