# Willhaben Properties Scraper (Austria) (`scrapyx/willhaben-properties-scraper`) Actor

Scrapes property listings from Willhaben, Austria's largest classifieds site, across rentals, apartments for sale, houses for sale and land. Every row carries price, price per m², rooms, living area, address, GPS coordinates, agency and images.

- **URL**: https://apify.com/scrapyx/willhaben-properties-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (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.84 / 1,000 results

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?

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

## Willhaben Properties Scraper (Austria)

Scrapes property listings from **[Willhaben](https://www.willhaben.at)** —
Austria's largest classifieds site — across four categories: rental
apartments, apartments for sale, houses for sale, and land.

Public data only. No login, no cookies, no browser. No bot challenge on any
of 8 TLS profiles tested across search and detail pages.

### The one thing you need to know before using this

**Willhaben's own "houses for sale" URL silently ignores its region.**

Click through the site to houses for sale in a specific state and you land on
`/haus-kaufen/haus-angebote/wien`. Fetch that URL and it answers HTTP 200 with
the **entire country's** 16,964 houses — appending any region, including a
completely nonexistent one, changes nothing.

The correct region-scoped path drops `/haus-angebote` entirely:
`/haus-kaufen/wien` — verified to genuinely narrow (1,571 for Vienna) and to
404 cleanly on a bogus region. This actor always uses the working path; you
never see the trap.

`rent_apartment`, `buy_apartment` and `land` do **not** have this problem —
region-in-path is honoured on all three, checked individually.

### What you get

Three record types share one dataset, told apart by `recordType`.

#### `PROPERTY` — one row per listing

Upstream's advert object, passed through verbatim, plus:

- **`attrs`** — a flattened convenience dict of Willhaben's name/value
  attribute list (price, price/m², rooms, living area, address, GPS
  coordinates, district, agency name, publish date, image URLs — over 30
  fields per listing)
- **`detail`** and **`detailAttrs`** (with **Fetch full property details**
  on) — full description text, equipment list, contact details, and
  category-specific fields the search row does not carry

#### `SEARCH_SUMMARY` — one row per (category, region) query

Willhaben's own match total, requests spent, pages fetched, and the filters
actually sent.

#### `ERROR` — one row per input that could not be processed

Every input maps to at least one row, so nothing disappears silently.

### Input

| Field | What it does |
| --- | --- |
| **Search queries** | `[{"category": ..., "region": ...}, ...]`. Category: `rent_apartment`, `buy_apartment`, `buy_house`, `land`. Region: one of Austria's 9 states, or empty for the whole country. |
| **Sort order** | Willhaben's own 7 sort options — newest, nearest, price/area ascending or descending, relevance. |
| **Number of rooms** | Verified real. Exact match, not a minimum — Wien rentals: 4,808 → 1,340 at `3`. |
| **Minimum / maximum price** | Verified real, and compose correctly together. |
| **Fetch full property details** | One extra request per listing. Off by default. |
| **Max listings per query** | `0` for everything Willhaben will serve. |
| **Rows per request** | Up to 200 — Willhaben's own maximum; anything higher is silently clamped, not rejected. |
| **Max concurrent requests / Minimum seconds between requests** | Throughput controls. |

#### ⚠️ Filter names that look plausible but do nothing

Willhaben silently ignores unrecognised parameter *names* — a typo returns a
healthy 200 with the unfiltered baseline. Room-count filtering in particular
is not guessable from the site's UI text: `NO_OF_ROOMS`, `NO_OF_ROOMS_FROM`,
`ROOMS`, `ROOMS_FROM` and `roomCount` are **all inert**. The real name,
`NUMBER_OF_ROOMS`, was found by reading the attribute name Willhaben's own
listing objects use — which is exactly what this actor sends, so you never
have to guess it yourself.

### Known limits

- **Four property categories, not the whole site.** Willhaben is also a huge
  used-car marketplace (151,459 listings) and a general classifieds board
  (12.5M listings) on the same contract — deliberately out of scope; those
  are different data types for different audiences and belong in a sibling
  actor if ever built.
- **`rooms` is an exact match**, not a "3 or more" filter — same as
  Willhaben's own site behaviour.
- **Region is one of 9 Austrian states**, not a city or postcode. Willhaben's
  own URL structure does not offer a finer-grained region filter on these
  four category paths.

# Actor input Schema

## `queries` (type: `array`):

One {category, region} pair per entry, each with its own SEARCH\_SUMMARY row. `category` must be one of `rent_apartment`, `buy_apartment`, `buy_house`, `land`. `region` is one of Austria's 9 states (e.g. `wien`, `steiermark`) or empty for all of Austria.

Example:

```json
[{"category": "rent_apartment", "region": "wien"}, {"category": "buy_house", "region": "tirol"}]
```

## `sort` (type: `string`):

Each value here is one of Willhaben's own sortOrderList options, read from the page rather than guessed.

## `rooms` (type: `integer`):

Verified real: `3` narrowed Wien rentals from 4,810 to 1,340. This is an EXACT match, not a minimum — Willhaben's own filter behaves the same way. Leave at 0 to skip.

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

Verified real and composes correctly with priceTo. Leave at 0 to skip.

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

Verified real: `800` narrowed Wien rentals from 4,810 to 366. Leave at 0 to skip.

## `includePropertyDetails` (type: `boolean`):

Adds one request per listing and attaches a `detail` object with the full description text, equipment list, contact details, and category-specific attributes not present on the search row.

Off by default — search rows already carry price, price/m², rooms, living area, address, coordinates, agency and images.

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

Stop after this many listings per (category, region) query. Set to 0 for everything Willhaben will serve.

## `pageSize` (type: `integer`):

How many listings to pull per request. 200 is the maximum Willhaben honours — anything higher is silently clamped to 200, not rejected.

## `maxConcurrency` (type: `integer`):

Upper bound on requests in flight at once, across all queries and any detail fetches.

## `minRequestInterval` (type: `integer`):

Paces how often requests START, without tying up a concurrency slot. Leave at 0 to use the built-in default of 0.2s -- 0 does not mean 'no pacing'.

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

Residential pinned to Austria. No WAF or bot challenge was observed on any of 8 TLS profiles across search and detail surfaces, but this is an Austria-only portal and container egress is a different posture than a home connection.

## Actor input object example

```json
{
  "queries": [
    {
      "category": "rent_apartment",
      "region": "wien"
    }
  ],
  "sort": "",
  "rooms": 0,
  "priceFrom": 0,
  "priceTo": 0,
  "includePropertyDetails": false,
  "maxItems": 100,
  "pageSize": 90,
  "maxConcurrency": 4,
  "minRequestInterval": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AT"
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "queries": [
        {
            "category": "rent_apartment",
            "region": "wien"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/willhaben-properties-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 = { "queries": [{
            "category": "rent_apartment",
            "region": "wien",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/willhaben-properties-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 '{
  "queries": [
    {
      "category": "rent_apartment",
      "region": "wien"
    }
  ]
}' |
apify call scrapyx/willhaben-properties-scraper --silent --output-dataset

```

## MCP server setup

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