# Real Estate: ImmoScout24, ImmoWelt, Bayut, PropertyFinder (`acumix/real-estate-scraper`) Actor

1.7M+ property listings from Germany's two biggest portals and the UAE's two biggest, with price, area, rooms, energy data, agent contacts and coordinates. Every field exactly as the portal publishes it. Pay only for rows you get.

- **URL**: https://apify.com/acumix/real-estate-scraper.md
- **Developed by:** [liviu robert ciocazan](https://apify.com/acumix) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 property listings

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/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

## Real Estate Scraper — ImmoScout24, ImmoWelt, Bayut, PropertyFinder

**1,924,637 property listings** from the two largest portals in Germany and the
two largest in the UAE, refreshed weekly. Price, area, rooms, energy data, agent contacts and
coordinates — **every field exactly as the portal publishes it**.

| portal | country | listings |
|---|---|---|
| ImmoScout24 | Germany | 740,280 |
| ImmoWelt | Germany | 434,594 |
| Bayut | UAE | 413,445 |
| PropertyFinder | UAE | 336,318 |

No run triggers a live crawl, so a query returns in seconds instead of hours, and you are
charged per row delivered. **Run the free `Coverage` output first** — it lists every portal's
cities, property types and field coverage with counts, so you can size a job before paying
for it.

### Values are verbatim, by design

Each row carries that portal's own wording and units, untranslated and unharmonised:

- ImmoWelt stores `deal_type` as **`sale`**; the other three store **`buy`**. Choosing
  "For sale" in the input matches both — the row keeps its portal's spelling.
- Property types stay in the portal's language: `Etagenwohnung`,
  `Einfamilienhaus zum Kauf`, `Apartment`, `Villa`.
- Prices are `EUR` on the German portals and `AED` on the UAE portals; `price_currency`
  says which on every row.
- A field a portal does not publish is **empty**, never guessed.

If you are joining the four portals, the tables below tell you which columns you can rely on.

### `geo_precision` — read this before any radius search

Coordinates do not mean the same thing on every portal, so every row is labelled:

| value | meaning | where |
|---|---|---|
| `exact` | the point the portal published for this listing | ImmoScout24; ImmoWelt where the address is published |
| `building` | one point per building/tower, shared by all its units | PropertyFinder |
| `community` | the centroid of a whole community | Bayut |
| `district` | the centroid of the district outline shown when the seller withholds the address | ImmoWelt |

Bayut's `Al Zahya` puts 5,035 listings on a single coordinate. That is the portal's own
granularity, not an error — but a radius search against `community` points will not do what
you expect, so filter on `geo_precision` when precision matters.

### Fields populated on all four portals (26)

These are safe to use in a cross-portal query.

| field | ImmoScout24 (DE) | ImmoWelt (DE) | PropertyFinder (AE) | Bayut (AE) |
|---|---|---|---|---|
| `url` | 100% | 100% | 100% | 100% |
| `scraped_at` | 100% | 100% | 100% | 100% |
| `listing_id` | 100% | 100% | 100% | 100% |
| `title` | 100% | 100% | 100% | 100% |
| `deal_type` | 100% | 100% | 100% | 100% |
| `property_type` | 100% | 100% | 100% | 100% |
| `is_commercial` | 100% | 92% | 100% | 100% |
| `price` | 92% | 93% | 100% | 100% |
| `price_currency` | 100% | 100% | 100% | 100% |
| `price_per_sqm` | 69% | 80% | 100% | 100% |
| `rooms` | 100% | 76% | 2% | 95% |
| `bathrooms` | 48% | 2% | 98% | 94% |
| `living_space` | 100% | 72% | 100% | 100% |
| `country` | 100% | 100% | 100% | 100% |
| `state` | 100% | 99% | 100% | 100% |
| `city` | 100% | 100% | 100% | 100% |
| `district` | 71% | 89% | 100% | 100% |
| `lat` | 98% | 99% | 100% | 100% |
| `lon` | 98% | 99% | 100% | 100% |
| `address_published` | 100% | 100% | 100% | 100% |
| `n_images` | 100% | 100% | 100% | 100% |
| `image_urls` | 92% | 99% | 100% | 100% |
| `agency` | 97% | 100% | 100% | 49% |
| `publisher_type` | 100% | 100% | 100% | 100% |
| `is_private` | 100% | 100% | 100% | 100% |
| `geo_precision` | 98% | 99% | 100% | 100% |

### Fields only some portals publish (87)

| field | ImmoScout24 (DE) | ImmoWelt (DE) | PropertyFinder (AE) | Bayut (AE) |
|---|---|---|---|---|
| `reference` | — | 92% | 100% | — |
| `is_new_build` | — | 100% | 100% | — |
| `price_label` | — | 100% | — | — |
| `extra_costs` | 21% | — | — | — |
| `service_charge` | 8% | 9% | — | — |
| `commission` | 64% | 87% | — | — |
| `commission_free` | — | 100% | — | — |
| `deposit` | 19% | — | — | — |
| `heating_costs` | 4% | — | — | — |
| `total_rent` | 12% | — | — | — |
| `commission_note` | 12% | 28% | — | — |
| `rental_income` | 3% | 3% | — | — |
| `marketing_type` | 21% | 99% | 100% | — |
| `rent_per_sqm` | 6% | — | — | — |
| `rent_frequency` | — | — | — | 51% |
| `bedrooms` | 44% | — | 86% | 95% |
| `plot_space` | 41% | 50% | 26% | — |
| `usable_space` | 19% | 19% | 3% | — |
| `divisible_from` | 7% | — | — | — |
| `floor` | 20% | 20% | — | — |
| `total_floors` | 29% | 20% | — | — |
| `availability` | 49% | 13% | 46% | — |
| `zip` | 100% | 100% | — | — |
| `street` | 40% | 18% | 100% | — |
| `year_built` | 70% | 63% | — | — |
| `condition` | 58% | 63% | 46% | — |
| `heating_type` | 53% | 56% | — | — |
| `energy_source` | 44% | 57% | — | — |
| `energy_class` | — | 37% | — | — |
| `energy_value` | 33% | 44% | — | — |
| `energy_cert_type` | 55% | 45% | — | — |
| `last_renovated` | 13% | 2% | — | — |
| `has_balcony` | 100% | 100% | 100% | — |
| `has_garden` | 100% | 100% | 100% | — |
| `has_cellar` | 100% | 100% | 100% | — |
| `has_lift` | 100% | 100% | 100% | — |
| `has_parking` | 100% | 100% | 100% | — |
| `is_rented` | 100% | 100% | 100% | — |
| `barrier_free` | 100% | 100% | 100% | — |
| `furnished` | 100% | 100% | 100% | — |
| `fitout_quality` | 43% | 44% | — | — |
| `flooring` | 7% | 32% | — | — |
| `development_status` | 2% | 5% | 46% | — |
| `furnishing` | 3% | 6% | 98% | — |
| `pets` | 10% | 2% | — | — |
| `parking_type` | 27% | 52% | — | — |
| `parking_price` | 8% | 9% | — | — |
| `heritage` | 2% | 2% | — | — |
| `features_json` | 100% | 99% | 92% | — |
| `n_floorplans` | — | 100% | — | — |
| `n_videos` | — | 100% | — | — |
| `n_tours` | — | 100% | — | — |
| `agency_id` | — | 100% | 100% | — |
| `agent_name` | 96% | 100% | 100% | — |
| `phones` | 73% | 90% | 100% | — |
| `website` | 60% | 92% | — | — |
| `profile_url` | — | 100% | 100% | — |
| `description` | 74% | 100% | 100% | — |
| `location_description` | — | 85% | — | — |
| `features_description` | 51% | 92% | 92% | — |
| `created_date` | 90% | 100% | 100% | — |
| `updated_date` | — | 100% | — | — |
| `geo_bbox` | — | 81% | — | — |
| `geo_node_id` | — | — | 100% | — |
| `agency_email` | — | — | 100% | — |
| `agency_address` | — | 99% | 99% | — |
| `listing_level` | — | — | 100% | — |
| `geo_path` | — | — | 100% | — |
| `whatsapp` | — | — | 100% | — |
| `is_verified` | — | — | 100% | — |
| `state_id` | — | 99% | — | — |
| `region_id` | — | 52% | — | — |
| `county_id` | — | 99% | — | — |
| `municipality_id` | — | 99% | — | — |
| `district_id` | — | 24% | — | — |
| `neighbourhood_id` | — | 88% | — | — |
| `county` | — | 79% | — | — |
| `county_code` | — | 79% | — | — |
| `municipality_code` | — | 97% | — | — |
| `property_type_key` | — | 99% | — | — |
| `property_subtype_key` | — | 59% | — | — |
| `commercial_space_type` | — | 16% | — | — |
| `floor_num` | — | 21% | — | — |
| `floor_label` | — | 28% | — | — |
| `bathroom_detail` | — | 33% | — | — |
| `n_wc` | — | 4% | — | — |
| `equipment_description` | — | 69% | — | — |

#### Notable gaps worth knowing

- **Bayut** publishes no amenity flags (`has_balcony`, `has_lift` …), no `description`,
  no agent name and no phone on its listing pages. Area, price, rooms, baths and location
  are complete.
- **ImmoWelt** publishes no `state`, no `bedrooms`/`bathrooms` split and no
  `is_commercial`. It is the only portal that gives floorplan, video and virtual-tour counts.
- **The UAE portals** publish no postcode, no `year_built` and no energy data — those are
  German-market fields.
- **Amenity flags** (`has_*`) mean *advertised by the listing*: `1` where the listing states
  it, `0` where it does not. On ImmoScout they are read from the listing text with negation
  handling, because that portal exposes no amenity field of its own.

### Output types

| type | one row per | charged |
|---|---|---|
| `coverage` | portal × region × deal type, plus field coverage and vocabularies | **free** |
| `listings` | property, with every column that portal populates | per row |
| `price_stats` | area, with median/average price and price per m² | per row |
| `agencies` | agency, with listing counts, cities and contacts | per row |

### Freshness

Every delivered listing was captured within the last 14 days; `scraped_at` is a Unix
timestamp on every row. Older captures are never sold. If a portal is mid-refresh and
briefly unreadable the run says so in the log and does not charge you for it.

### Known characteristics of the source data

- A small number of listings carry an implausible `living_space` (a few thousand above
  10,000 m² on ImmoScout). These are the portal's own published values, passed through
  unchanged rather than silently corrected. Use `maxLivingSpace` if it matters to you.
- `deal_type` is stated on every portal. ImmoScout leaves it blank on 370 of its
  740,280 listings (0.05%) — a handful whose listing type and price criteria name
  neither a sale nor a rent.
  investment sales whose listing type names neither sale nor rent.
- These columns are present on every row but no portal currently populates any of
  them, so they always arrive empty: `estimated_value`, `permit_number`, `other_info`, `n_beds`.

# Actor input Schema

## `outputType` (type: `string`):

**Coverage is free.** Run it first to see every portal, city and property type we hold, with listing counts — then come back and pick what you actually want.

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

You are charged per delivered row. Start small; Coverage is free and tells you how large the full result would be.

## `countries` (type: `array`):

Leave empty for both. Picking a country also narrows the portals to that country's.

## `platforms` (type: `array`):

Leave empty for all four. Each portal publishes its own fields in its own language — see the README for exactly which columns each one fills.

## `dealType` (type: `string`):

Picking "For sale" also returns ImmoWelt rows, which store this value as `sale` rather than `buy`. The row keeps the portal's own wording.

## `regions` (type: `array`):

German Bundesland or UAE emirate. These 24 options reach every listing we hold, so use them when you want a whole area rather than named cities.

## `cities` (type: `array`):

Every city holding at least 100 German listings, plus all UAE cities, with counts. Smaller places are reachable through Regions; the free Coverage output lists all of them.

## `zipPrefix` (type: `string`):

e.g. `10` for Berlin. German portals only — the UAE portals publish no postcode.

## `zips` (type: `array`):

Exact German postcodes, e.g. `10115`. The UAE portals publish no postcode.

## `bboxMinLon` (type: `number`):

Bounding box west edge. Supply all four corners or none. Check `geo_precision` — Bayut's points are community centroids.

## `bboxMinLat` (type: `number`):

Bounding box south edge. Supply all four corners or none.

## `bboxMaxLon` (type: `number`):

Bounding box east edge. Supply all four corners or none.

## `bboxMaxLat` (type: `number`):

Bounding box north edge. Supply all four corners or none.

## `propertyTypes` (type: `array`):

In the portal's own words, because that is how the value is delivered. German types are German; UAE types are English.

## `conditions` (type: `array`):

As the portal states it. German portals use German terms; PropertyFinder uses completed / off_plan.

## `energyClasses` (type: `array`):

ImmoWelt publishes a letter class; ImmoScout publishes a kWh value instead, so this filter applies to ImmoWelt rows.

## `minPrice` (type: `integer`):

In the portal's own currency — EUR on the German portals, AED on the UAE portals.

## `maxPrice` (type: `integer`):

In the portal's own currency — EUR on the German portals, AED on the UAE portals.

## `minRooms` (type: `number`):

Total rooms as the portal counts them. ImmoWelt and PropertyFinder report rooms on only part of their stock.

## `maxRooms` (type: `number`):

Total rooms as the portal counts them.

## `minLivingSpace` (type: `number`):

Living area in m². Every portal publishes this in m²; Bayut's own sqft values are converted.

## `maxLivingSpace` (type: `number`):

Living area in m². Useful for excluding the handful of listings whose portal-published area is implausible.

## `minPricePerSqm` (type: `number`):

Price per m² as the portal's own price and area give it.

## `maxPricePerSqm` (type: `number`):

Price per m² as the portal's own price and area give it.

## `minYearBuilt` (type: `integer`):

German portals only.

## `hasBalcony` (type: `string`):

Advertised by the listing. Bayut does not publish amenity flags — see the README.

## `hasGarden` (type: `string`):

Advertised by the listing. Bayut publishes no amenity flags.

## `hasLift` (type: `string`):

Advertised by the listing. Bayut publishes no amenity flags.

## `hasParking` (type: `string`):

Advertised by the listing. Bayut publishes no amenity flags.

## `furnished` (type: `string`):

Advertised by the listing. Bayut publishes no amenity flags.

## `commercialOnly` (type: `boolean`):

Offices, retail, warehouses, industrial.

## `residentialOnly` (type: `boolean`):

Excludes offices, retail, warehouses and industrial units.

## `privateOnly` (type: `boolean`):

Only listings published by private sellers rather than agencies.

## `withCoordinatesOnly` (type: `boolean`):

Check `geo_precision` on each row: `exact`, `building`, `community` or `district`.

## `agencyContains` (type: `string`):

Case-insensitive substring of the agency name, e.g. `Engel` or `Betterhomes`.

## `titleContains` (type: `string`):

Case-insensitive substring of the listing title.

## `groupBy` (type: `string`):

Only used by the Price statistics output.

## `minGroupSize` (type: `integer`):

Groups smaller than this are suppressed, because a median of two listings is noise.

## `minAgencyListings` (type: `integer`):

Only used by the Agencies output. Hides agencies with fewer listings than this.

## Actor input object example

```json
{
  "outputType": "listings",
  "maxItems": 1000,
  "dealType": "any",
  "hasBalcony": "any",
  "hasGarden": "any",
  "hasLift": "any",
  "hasParking": "any",
  "furnished": "any",
  "commercialOnly": false,
  "residentialOnly": false,
  "privateOnly": false,
  "withCoordinatesOnly": false,
  "groupBy": "city",
  "minGroupSize": 5,
  "minAgencyListings": 1
}
```

# Actor output Schema

## `dataset` (type: `string`):

One row per property, carrying every field the portal publishes. Values are verbatim: ImmoWelt keeps deal_type='sale' where the others say 'buy', German property types stay in German, and a field a portal does not publish stays empty.

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

Key columns: portal, deal type, price, area, rooms, city, coordinates with their geo_precision label, agency and the listing URL.

## `csv` (type: `string`):

The same rows as a spreadsheet.

# 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 = {
    "outputType": "listings"
};

// Run the Actor and wait for it to finish
const run = await client.actor("acumix/real-estate-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 = { "outputType": "listings" }

# Run the Actor and wait for it to finish
run = client.actor("acumix/real-estate-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 '{
  "outputType": "listings"
}' |
apify call acumix/real-estate-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,acumix/real-estate-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/BcrB6czmcSMdEHzmW/builds/EyarzK3tneDggZ0UB/openapi.json
