# Willhaben Scraper: Austria Property Listings & Price Drops (`datasmithlab/willhaben-property-scraper`) Actor

Austria flats and houses for rent and sale from Willhaben with price per m², rooms and outdoor space. Track new listings, price drops and removed homes between runs. Search by state, district or Vienna Bezirk; agency and photos.

- **URL**: https://apify.com/datasmithlab/willhaben-property-scraper.md
- **Developed by:** [Datasmithlab](https://apify.com/datasmithlab) (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.60 / 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.
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

## Willhaben Scraper: Austria Property Listings & Price Drops

Export flats and houses for rent and for sale from [willhaben.at](https://www.willhaben.at/iad/immobilien), Austria's biggest property marketplace, by **state, district or Vienna Bezirk**, or by pasting a search URL. Every row comes with price, living area in m², **price per m²**, rooms, outdoor space (balcony, terrace, garden), features, postcode, coordinates, agency and photos.

Turn on **tracking** and every later run returns only what changed: **new listings, price drops, price rises and removed homes**, with the previous price and the % change. Willhaben lists the newest ads first, so a daily scheduled run is a reliable "new flats in Graz under €900" alert.

No proxy, no login. For private owners the street address is not collected, street-and-number mentions in the title and teaser are masked, and the map position is rounded to about 100 m.

### What you get

- **Clean numbers.** `price: 1790`, `currency: "EUR"`, `pricePeriod: "month"` for rentals, `areaSqm: 87`, `pricePerSqm: 20.57` (rent per m² for rentals, price per m² for sales), `usableAreaSqm`, `outdoorAreaSqm` and `outdoorAreaTypes` (`Loggia`, `Balkon`, `Garten`...).
- **Rooms** as Austrians count them (`rooms`: Zimmer, living room included, half rooms such as `2.5` kept) and `floor` (`0` = ground floor).
- **Location.** `city` (for Vienna the Bezirk, e.g. `Wien, 22. Bezirk, Donaustadt`), `district`, `state`, `postcode`, `latitude`, `longitude`, and the street for agency listings that publish it.
- **Features** in Willhaben's own words: `Einbauküche`, `Fahrstuhl`, `Keller`, `Barrierefrei`, `Garage`...
- **Advertiser.** `sellerType` is `agency` or `private`. For agencies you also get `agencyName` and the agency's own reference (`agencyReference`).
- **Short description** (`summary`, the teaser Willhaben shows in search results), `imageUrls`, `hasVirtualTour`, `isNewBuildProject`.
- **Change tracking.** With *Only new, price-changed and removed listings* on, each row has `changeType`: `NEW`, `PRICE_DROP`, `PRICE_UP` or `REMOVED`, plus `previousPrice`, `priceChange`, `priceChangePct` and `firstSeenAt`. Unchanged listings are not returned and **not charged**.
- **Reports** in the run's key-value store: `SEARCHES` (location, listings available, pages read, status per search) and `HEALTH`. If Willhaben blocks the run or changes its page format, you get a clear message instead of an empty dataset.

### How to search

| You want | Set |
|---|---|
| Flats to rent in a city | **States, districts or Vienna Bezirke** `Graz` |
| One Vienna Bezirk | `Donaustadt`, `1070` or `Wien 7` |
| A whole state | `Tirol`, `Wien` |
| Houses for sale | **Rent or buy** `For sale`, **Property types** `Houses` |
| 3+ rooms under €1,200 | **Min rooms** `3`, **Max price** `1200` |
| Only private owners (no commission) | **Advertiser** `Private owners only` |
| A search you already set up on Willhaben | Paste it in **Willhaben search URLs** (its price/area filters are ignored: set them in the input, the log warns you) |
| A daily new-listing or price-drop alert | Turn on tracking and schedule the Actor daily |

Place names are the ones Willhaben uses in its location menu (states, political districts such as `Innsbruck-Land` or `Salzburg Stadt`, and Vienna's 23 Bezirke). If a name is not found, the `SEARCHES` report says so and suggests close names.

Price, room, floor-area and advertiser filters are applied by the Actor while reading (Willhaben's own URL filters are not used), so **you only pay for listings that match**. Narrow filters on a big area read more pages and take longer.

### Output example

```json
{
  "id": "willhaben:1351828722",
  "url": "https://www.willhaben.at/iad/immobilien/d/mietwohnungen/wien/wien-1220-donaustadt/erstbezugswohnung-2-zimmer-1351828722/",
  "title": "Erstbezugswohnung | 2 Zimmer mit Balkon in der Donaustadtstraße",
  "transaction": "rent",
  "propertyType": "Wohnung",
  "price": 1194.98,
  "currency": "EUR",
  "pricePeriod": "month",
  "pricePerSqm": 24.39,
  "areaSqm": 49,
  "outdoorAreaTypes": ["Balkon"],
  "rooms": 2,
  "city": "Wien, 22. Bezirk, Donaustadt",
  "state": "Wien",
  "postcode": "1220",
  "latitude": 48.215233,
  "longitude": 16.45,
  "features": ["Einbauküche", "Teilmöbliert / Möbliert", "Fahrstuhl"],
  "sellerType": "agency",
  "agencyName": "EKAZENT Management GmbH",
  "imageCount": 8,
  "publishedAt": "2026-10-08T11:39:25Z",
  "changeType": "NEW",
  "previousPrice": null,
  "firstSeenAt": "2026-10-08T12:00:30Z"
}
```

### Pricing

You pay a small fee per run start and per listing row. Rows filtered out, and unchanged listings in tracking mode, are free.

### Good to know

- **`publishedAt`** is when the ad was last published; advertisers can re-publish ("bump") an ad, which moves it back to the top.
- **Tracking memory** is kept per set of searches and filters. Change the places or filters and the next run starts fresh (everything is `NEW` once).
- **`REMOVED`** means the home is no longer in that search on Willhaben (usually rented, sold or withdrawn). It is reported only when every search was read to the end, at least as many homes were read as Willhaben counts, and that count did not change while reading. Ads are re-published ("bumped") all the time, so on big searches this check often fails and that run simply reports no removals; small areas get them reliably. Homes that only moved outside your filters are **not** marked as removed. If more than half of the remembered homes vanish at once, or any of the searches suddenly shows 0 results, nothing is reported as removed in that run and the homes stay in memory (put a search that is often empty in its own run).
- **Description.** Search results include a short teaser (`summary`), not the full text. Phone numbers and emails in titles and teasers are replaced with `[phone removed]` / `[email removed]`.
- **New-build projects** appear as one row (`isNewBuildProject: true`) with the price, size and rooms of the unit Willhaben shows in the results; the project's individual units are not listed separately. Room and price filters apply to that row.
- **If Willhaben blocks the run**, the search is reported as `blocked` and the run stops. Try again later. The Actor reads Willhaben's public search pages politely (about one request per second) and does not use proxies or any workaround.
- This Actor is independent and not affiliated with willhaben internet service GmbH.

Found a bug or need a field? Open an issue on this page. We read every one and reply.

# Actor input Schema

## `locations` (type: `array`):

One per line, as Willhaben names them: a state ('Wien', 'Tirol'), a district ('Graz', 'Innsbruck-Land', 'Salzburg Stadt') or a Vienna Bezirk ('Donaustadt', '1070', 'Wien 7').

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

Or paste property search pages from willhaben.at (e.g. /iad/immobilien/mietwohnungen/steiermark/graz/). URL filters such as price are not used; set them below instead.

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

Applies to the places above. Pasted URLs keep their own.

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

Flats, houses or both (one search each).

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

Rent: monthly price.

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

Rent: monthly price. Leave empty for no limit.

## `minRooms` (type: `integer`):

Austrian room count (Zimmer), living room included.

## `maxRooms` (type: `integer`):

Leave empty for any.

## `minAreaSqm` (type: `integer`):

Homes without a published floor area are left out when set.

## `maxAreaSqm` (type: `integer`):

Leave empty for no limit.

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

Only agencies, only private owners, or both.

## `onlyNewOrChanged` (type: `boolean`):

Remembers every listing per set of searches and filters. Later runs return only NEW listings, PRICE_DROP / PRICE_UP (with previous price and % change) and REMOVED listings (gone from the search). Schedule it daily for a price-drop alert.

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

Stop after this many rows (you are charged per row).

## `maxPagesPerSearch` (type: `integer`):

30 listings per page. Filters are applied after reading, so narrow filters may need many pages.

## Actor input object example

```json
{
  "locations": [
    "Graz"
  ],
  "transaction": "rent",
  "propertyTypes": [
    "apartment"
  ],
  "sellerType": "any",
  "onlyNewOrChanged": false,
  "maxItems": 200,
  "maxPagesPerSearch": 1000
}
```

# Actor output Schema

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

Listings (table view, JSON/CSV/Excel export).

## `allFields` (type: `string`):

Every field, including summary, features, photos and coordinates.

## `searches` (type: `string`):

For each search: location found, listings available, pages read, status.

## `health` (type: `string`):

Empty-field ratios, failed searches, skipped listings.

# 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 = {
    "locations": [
        "Graz"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datasmithlab/willhaben-property-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 = { "locations": ["Graz"] }

# Run the Actor and wait for it to finish
run = client.actor("datasmithlab/willhaben-property-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 '{
  "locations": [
    "Graz"
  ]
}' |
apify call datasmithlab/willhaben-property-scraper --silent --output-dataset

```

## MCP server setup

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