# willhaben Immobilien Scraper - Austria Real Estate (`aidnurs/willhaben-immo-scraper`) Actor

Scrape willhaben.at real estate (Austria): apartments, houses, land, commercial for rent or sale. Price, €/m², area, rooms, district, agency, photos; optional operating costs, energy certificate, year built. Only-new mode, duplicates free, private sellers protected.

- **URL**: https://apify.com/aidnurs/willhaben-immo-scraper.md
- **Developed by:** [Nursultan Imanov](https://apify.com/aidnurs) (community)
- **Categories:** Real estate, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 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

### willhaben Immobilien scraper and willhaben real estate API

**willhaben Immobilien Scraper** extracts real estate listings from [willhaben.at](https://www.willhaben.at/iad/immobilien), Austria's largest property marketplace. Apartments and houses for rent or sale, land, commercial and holiday property, new-build projects. Pick a property type, federal state and districts (or paste a search URL from your browser) and get clean, structured data: **price, price per m², living area, rooms, floor, postcode, district, coordinates, agency, publish date and photos**. Turn on details for **operating costs (Betriebskosten), deposit, commission, energy certificate (HWB, fGEE), year built, heating and the full description**.

Use it as a **willhaben real estate API** for market research, rent and price benchmarks, investment screening, lead lists of agencies, or your own alerts for new apartments in Vienna, Graz, Linz or anywhere in Austria.

### Why use this willhaben Immobilien scraper?

- 🏠 **Simple form, no URL needed.** Property type, Bundesland and districts (`Neubau`, `1070`, `Graz-Umgebung`, `Mödling`) instead of hand-built URLs. Or paste any willhaben search URL, it is accepted as is.
- 💶 **Price per m² for every listing**, plus living, usable and plot area, rooms and floor as numbers.
- 🎯 **Price, area, rooms, keyword and private/agency filters.** A price range sorts by price and stops early, so narrow searches stay fast and cheap.
- 🔔 **Only new listings since the last run.** Schedule the Actor and get just the listings you have not seen. Already-seen listings are free.
- 🔁 **No duplicates charged.** Every listing appears once per run.
- 📋 **Optional details:** operating costs, monthly costs, deposit, commission, energy certificate, year built, building type and condition, heating, availability, features, price reductions, agency website and office address.
- 🛡️ **GDPR-friendly by default.** Private sellers' names, phones, streets, house numbers and profile IDs are not saved, their coordinates are rounded to about 1 km, and phone numbers and personal e-mails in descriptions are masked. Turn on *Include personal data* only if you have a legal basis.
- ⚡ **Fast.** Pure HTTP, 200 listings per request. 600 listings with full details took about 4 minutes in our test.

### How much does it cost to scrape willhaben real estate?

You pay only for listings saved to the dataset (pay per event): **$1.00 per 1,000 listings**, plus **$1.00 per 1,000 listings** for *Include details*. Duplicates, already-seen listings in monitoring mode and empty searches cost nothing, and listings whose detail page could not be loaded are not charged for details. Cap spending with *Max listings* and the run's maximum cost.

### How to scrape willhaben Immobilien

1. Choose a **Property type**, for example *Apartment for rent (Mietwohnung)*.
2. Choose a **Federal state** and, optionally, **Districts** (`Neubau`, `1070`, `10`, `Leopoldstadt`, `Graz`, `St. Pölten`). Ambiguous names like `Land` are rejected with a hint instead of guessed.
3. Optionally set **Min/Max price**, **Min/Max area**, **Min rooms**, **Seller** and **Keywords**.
4. Set **Max listings** and click **Start**.
5. Download the results as JSON, CSV, Excel or HTML, or get them via the Apify API.

Prefer the website's filters? Set up the search on willhaben.at, copy the URL from the address bar and paste it into **Search URLs**.

### Input example

```json
{
  "propertyType": "rent-apartment",
  "state": "wien",
  "districts": ["1070", "Josefstadt"],
  "priceMax": 1500,
  "roomsMin": 2,
  "keywords": "balkon, terrasse, loggia",
  "maxItems": 200,
  "includeDetails": true,
  "onlyNew": false
}
```

### Output example

A real listing from a run with *Include details* (long texts and lists shortened):

```json
{
  "id": "1266867119",
  "url": "https://www.willhaben.at/iad/immobilien/d/mietwohnungen/wien/wien-1070-neubau/moderne-2-zimmer-wohnung-mit-kleiner-loggia-in-bester-lage-ab-01-12-1266867119/",
  "title": "moderne 2-Zimmer-Wohnung mit kleiner Loggia in bester Lage - ab 01.12.!",
  "price": 1239,
  "priceText": "€ 1.239",
  "pricePerSqm": 24.29,
  "livingArea": 51,
  "usableArea": 51,
  "areaTotal": 51,
  "rooms": 2,
  "floor": "1",
  "propertyType": "Wohnung",
  "category": "rent-apartment",
  "dealType": "rent",
  "isNewBuildProject": false,
  "freeAreaTypes": [
    "Loggia",
    "Garten"
  ],
  "freeAreaTotal": 2,
  "street": "Kaiserstraße",
  "postcode": "1070",
  "city": "Wien",
  "district": "07. Bezirk, Neubau",
  "state": "Wien",
  "country": "Österreich",
  "latitude": 48.202297,
  "longitude": 16.34043,
  "coordinatesApproximate": false,
  "sellerType": "agency",
  "agencyName": "Mittelsmann Philipp Sulek GmbH",
  "agencyId": "22625623",
  "publishedAt": "2026-10-08T02:33:15Z",
  "isBumped": true,
  "teaser": "Geschätzte InteressentInnen! Da die Wohnung aktuell vermietet ist, können Besichtigungen nur in Absprache mit dem Mieter stattfinden! Wir bitten um etwas Vorlau ...",
  "images": [
    "https://cache.willhaben.at/mmo/9/126/686/7119_748378377.jpg",
    "https://cache.willhaben.at/mmo/9/126/686/7119_-1255050598.jpg",
    "..."
  ],
  "advertiserReference": "26556",
  "description": "Geschätzte InteressentInnen!\n\nDa die Wohnung aktuell vermietet ist, können Besichtigungen nur in Absprache mit dem Mieter stattfinden!\n\n Wir bitten um etwas Vor ...",
  "descriptionSections": {
    "Ausstattung": "- Loggiaanzahl: 1\n- Abstellraum Anzahl: 1\n- Loggiafläche: 1.9 m2\n- Bad mit Wanne\n- Boden: Fliesenbod ...",
    "Lage": "Kaiserstraße nahe Neustiftgasse\n\nRegion: Neustiftgasse\n\nInfrastruktur / Entfernungen\n\nGesundheit\nArz ..."
  },
  "roomsExact": 2,
  "rentTotal": 1239,
  "rentNet": 1126.36,
  "operatingCosts": 123.86,
  "operatingCostsNet": 123.86,
  "vat": 112.64,
  "totalMonthlyCost": 1239,
  "deposit": "3717",
  "commission": "Gemäß Erstauftraggeberprinzip bezahlt der Abgeber die Provision.",
  "yearBuilt": 2017,
  "buildingType": "Neubau",
  "buildingCondition": "Sehr gut/gut",
  "heating": "Fußbodenheizung",
  "flooring": "Parkett",
  "availableFrom": "01.12.2026",
  "rentalTerm": "befristet 5 Jahr(e)",
  "energyHwb": 28.91,
  "energyHwbClass": "B",
  "energyFgee": 0.76,
  "energyFgeeClass": "A",
  "features": [
    "Keller",
    "Garage",
    "Barrierefrei",
    "Fahrstuhl",
    "Parkplatz",
    "Abstellraum",
    "..."
  ],
  "firstPublishedAt": "2026-09-10T04:33:15+0200",
  "updatedAt": "2026-10-08T04:35:04+0200",
  "imageCount": 14,
  "agencyWebsite": "www.sulek-immobilien.at",
  "agencyStreet": "Fasangasse 30/10",
  "agencyPostcode": "1030",
  "agencyCity": "Wien, 03. Bezirk, Landstraße",
  "detailsScraped": true,
  "source": "willhaben.at",
  "searchUrl": "https://www.willhaben.at/iad/immobilien/mietwohnungen/wien/wien-1070-neubau/?sort=1&rows=40&page=1",
  "scrapedAt": "2026-10-12T20:59:58Z"
}
```

### Fields you get

| Field | Description |
|---|---|
| `id`, `url`, `title`, `images` | willhaben listing ID, link, title, photo URLs |
| `price`, `priceText`, `pricePerSqm` | Monthly rent or purchase price in EUR, as shown, and per m² |
| `livingArea`, `usableArea`, `areaTotal`, `rooms`, `floor` | Size and layout |
| `propertyType`, `category`, `dealType`, `isNewBuildProject` | Wohnung, Reihenhaus, Baugrundstück ...; rent or buy |
| `freeAreaTypes`, `freeAreaTotal` | Balcony, loggia, terrace, garden and their total m² |
| `street`, `postcode`, `city`, `district`, `state`, `country` | Location (street only for agencies, or with personal data on) |
| `latitude`, `longitude`, `coordinatesApproximate` | Exact for agencies, rounded to ~1 km for private sellers |
| `sellerType`, `agencyName`, `agencyId`, `advertiserReference` | Private seller or agency; agency name, ID and reference number |
| `publishedAt`, `isBumped`, `teaser` | Last (re)publish time, pushed-up flag, first lines of the description |
| `source`, `searchUrl`, `scrapedAt` | willhaben.at, the search page the listing came from, time of scraping |
| **With details:** `detailsScraped`, `description`, `descriptionSections` | Whether the detail page loaded; full description and sections (Lage, Ausstattung, Preis ...) |
| `roomsExact`, `firstPublishedAt`, `updatedAt`, `imageCount` | Exact room count, first publish and last change time, number of photos |
| `rentTotal`, `rentNet`, `rentGross`, `operatingCosts`, `operatingCostsNet`, `heatingCosts`, `vat`, `totalMonthlyCost` | Rent breakdown and monthly costs |
| `deposit`, `commission`, `oldPrice`, `priceReducedAt` | Deposit, commission, price drops |
| `yearBuilt`, `buildingType`, `buildingCondition`, `heating`, `flooring`, `availableFrom`, `rentalTerm` | Building and availability |
| `energyHwb`, `energyHwbClass`, `energyFgee`, `energyFgeeClass` | Energy certificate |
| `features`, `plotArea`, `project*` | Equipment, plot size, new-build project status and unit ranges |
| `agencyWebsite`, `agencyStreet`, `agencyPostcode`, `agencyCity` | Agency office (company data) |
| **With personal data:** `contactName`, `contactPhone`, `contactPhones`, `contactEmail`, `agencyPhone` | Contact person and phones where published |

### Known gaps

- `street` is saved for agencies only (private sellers' streets are personal data) and only when the advertiser published it.
- `usableArea`, `floor`, `freeAreaTotal` and many detail fields are empty when the advertiser did not fill them in on willhaben.

### FAQ

**Is it legal to scrape willhaben.at?**
This Actor collects only publicly available real estate listings, the same pages any visitor sees. It never logs in, never uses accounts or cookies of real users, and never solves or bypasses CAPTCHAs. It requests only pages that willhaben's robots.txt leaves open to crawlers; filters it may not send are applied by the Actor itself.

**Personal data is off by default.** For private sellers the output has no name, no phone, no street and no profile ID, house numbers in their texts are masked, and coordinates are rounded to about 1 km. Phone numbers and personal e-mails inside descriptions are masked (role mailboxes like `office@` stay), and agency contact persons and direct phones are not saved. Names that an advertiser writes into the free text of a description can still appear as published.

If you turn on **Include personal data**, you get everything the site shows publicly, including the contact person's name and phone where available, the street and exact coordinates. Personal data is protected by the GDPR in the EU and by similar laws elsewhere. Only enable it if you have a legal basis (for example, a documented legitimate interest), keep only what you need, and do not use it for unsolicited marketing. If you are unsure, consult a lawyer.

You are responsible for how you use the data and for complying with applicable laws and willhaben.at's terms. This Actor is not affiliated with or endorsed by willhaben.at. Read more: [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/)

Found something that should not be here? Open an issue on the Actor's Issues tab and we will look at it promptly.

**Can I filter by keyword, price or area like on the website?**
Yes. Keywords are matched in the title and the first lines of the description (`balkon, terrasse` = either word, `altbau lift` = both, `-befristet` = exclude). Price, area, rooms and private/agency filters are applied by the Actor. With a price filter the search is sorted by price (ascending with a max price, descending with only a min price) and stops as soon as prices leave the range.

**Can I paste a URL from willhaben.at?**
Yes. Paste any real estate search URL. Parameters that crawlers may not send (price, area, keyword, private/agency, sub-type, features, last 48 hours) are removed from the request and applied as filters by the Actor. A keyword from the URL is matched in the title and the first lines of the description, so the result can be a bit smaller than on the website. A URL with a filter the Actor cannot apply is skipped with an error rather than searched wider.

**How do I get only new listings every day?**
Turn on *Only new listings since last run*, save the task and add a schedule. Use a different *Monitoring key* for each saved search. The run stops as soon as it reaches listings older than the previous run.

**Do I need a proxy?**
No. The Actor starts without a proxy and switches to an Austrian residential proxy by itself if willhaben rate-limits it, at no extra cost to you.

**Something broke or a field is missing?**
Open an issue on the Issues tab. We usually respond within 24 hours.

# Actor input Schema

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

What to search for. Ignored when Search URLs are given.

## `state` (type: `string`):

Bundesland. Leave empty for all of Austria.

## `districts` (type: `array`):

Optional. One or more districts: a name ("Neubau", "Graz-Umgebung", "Mödling", "St. Pölten"), a Vienna postcode or district number ("1070", "10"), any other 4-digit postcode ("8010", filtered by postcode), or a willhaben areaId ("117229"). Ambiguous names ("Land") are rejected with a hint. Leave empty for the whole federal state.

## `keywords` (type: `string`):

Optional. Words that must appear in the title or the first lines of the description. Commas mean OR, spaces mean AND, a minus excludes: "balkon, terrasse", "altbau lift", "-befristet". Umlauts work either way (Küche = kueche).

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

Monthly rent for rentals, purchase price for sales.

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

Monthly rent for rentals, purchase price for sales. With a price filter the Actor sorts by price and stops early, so it stays fast and cheap.

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

Living area; usable area or plot area when there is no living area.

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

Living area; usable area or plot area when there is no living area.

## `roomsMin` (type: `integer`):

Minimum number of rooms.

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

Private sellers, real estate agencies (incl. developers) or both.

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

Order of results. Newest first is used automatically for 'Only new listings'.

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

Maximum number of listings to save. You only pay for listings saved to the dataset.

## `includeDetails` (type: `boolean`):

Open every listing to add the full description, operating costs (Betriebskosten), deposit, commission, energy certificate (HWB, fGEE), year built, heating, condition, availability, features and the agency's website and address. Charged as an extra event per listing; about 10× slower.

## `includePersonalData` (type: `boolean`):

Off by default: for private sellers the output has no name, no phone, no street and no profile ID, house numbers in their texts are masked, and their coordinates are rounded to about 1 km. Phone numbers and personal e-mails inside descriptions are masked (\[phone removed], \[email removed]; role mailboxes like office@ stay). Agency contact persons and phones are not saved.<br><br>Turn it on to get everything the site shows publicly: the contact name and phone of private sellers and agency brokers (contact fields need 'Include details'), the street, exact coordinates and unmasked descriptions.<br><br>⚠️ This returns personal data. You're responsible for having a legal basis to collect and use it under privacy laws (e.g. GDPR).

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

Paste willhaben real estate search URLs straight from your browser (https://www.willhaben.at/iad/immobilien/...). When given, the search fields above are ignored, but Keywords and the filters still apply. Filters that willhaben's robots.txt does not allow crawlers to send (price, area, keyword, private/agency, property sub-type, features, last 48 hours) are removed from the request and applied by the Actor itself. A URL with a filter the Actor cannot apply is skipped with an error.

## `onlyNew` (type: `boolean`):

Remember listing IDs between runs and return only listings you have not received yet. Ideal for scheduled runs and alerts. Already-seen listings are never charged; the run stops as soon as it reaches listings older than the previous run.

## `stateKey` (type: `string`):

Name of the memory used by 'Only new listings'. Use a different key for each saved search.

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

Leave as is. The Actor runs without a proxy and switches to an Austrian residential proxy by itself if willhaben rate-limits it, at no extra cost to you. Only custom proxy URLs (your own proxies) are used from this field.

## Actor input object example

```json
{
  "propertyType": "rent-apartment",
  "state": "wien",
  "sellerType": "all",
  "sortBy": "newest",
  "maxItems": 20,
  "includeDetails": false,
  "includePersonalData": false,
  "onlyNew": false,
  "stateKey": "default",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `listings` (type: `string`):

Scraped real estate listings (table view).

## `listingsFull` (type: `string`):

All fields, including details when enabled.

# 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 = {
    "propertyType": "rent-apartment",
    "state": "wien",
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("aidnurs/willhaben-immo-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 = {
    "propertyType": "rent-apartment",
    "state": "wien",
    "maxItems": 20,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("aidnurs/willhaben-immo-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 '{
  "propertyType": "rent-apartment",
  "state": "wien",
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call aidnurs/willhaben-immo-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,aidnurs/willhaben-immo-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/MuiWDyRMhgf0d7u0H/builds/WSygz3B7QILjEgras/openapi.json
