# Homegate & ImmoScout24.ch Scraper – Swiss Real Estate (`ninhothedev/swiss-real-estate-scraper`) Actor

Scrape Swiss property listings from Homegate.ch, ImmoScout24.ch and Flatfox: rent & buy, CHF prices, rooms, m², address, GPS, features, agency, photos. Deduplicated across portals.

- **URL**: https://apify.com/ninhothedev/swiss-real-estate-scraper.md
- **Developed by:** [ninhothedev](https://apify.com/ninhothedev) (community)
- **Categories:** Real estate, Lead generation
- **Stats:** 3 total users, 2 monthly users, 85.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.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

### What does Swiss Real Estate Scraper do?

Scrape **Swiss property listings** from **Homegate.ch**, **ImmoScout24.ch** and **Flatfox.ch** in one run: apartments, houses, multi-family houses, parking, commercial space and building plots, for **rent or sale**, anywhere in Switzerland. You get CHF prices, rooms, living area, CHF/m², full address with canton and GPS coordinates, features, photos, description and publication date in one flat table. Listings with matching shared SMG identifiers are deduplicated when enabled. Similar properties without a shared identifier can remain separate. Export to JSON, CSV or Excel, or connect through the API. No login or API key needed.

### What data can you extract?

| Field | Example |
|---|---|
| `id` / `portal` / `url` | 4003557220 / homegate / https://www.homegate.ch/rent/4003557220 |
| `homegate_url`, `immoscout24_url`, `flatfox_url` | links to the same listing on each portal |
| `title` | Zentrale Wohnung beim Kunsthaus mit Terrasse |
| `offer_type` / `property_type` | rent / apartment |
| `price` / `price_unit` / `currency` | 2530 / month / CHF |
| `rent_net` / `rent_extra` | 2280 / 250 (net rent and charges) |
| `price_per_sqm` | 46 |
| `rooms` / `living_area_sqm` / `lot_area_sqm` | 2.5 / 55 / – |
| `floor` / `year_built` / `year_renovated` | 1 / 1985 / 2025 |
| `available_from` | 2026-11-01 |
| `street` / `zip_code` / `city` / `canton` | Wolfbachstrasse 18 / 8032 / Zürich / ZH |
| `lat` / `lng` | 47.3704 / 8.5513 |
| `has_balcony`, `has_elevator`, `has_parking`, `pets_allowed`, `features` | true, true, true, true, \[balcony, elevator, …] |
| `agency_name` / `agency_phone` / `contact_name` | Peter Stalder Immobilien AG / +41 61 226 64 00 (when the portal shows them) |
| `description`, `images`, `main_image`, `virtual_tour_url` | full text and photo URLs |
| `published_at` / `updated_at` | 2026-10-15T17:26:12Z |
| `duplicate_key` / `address_key` | keys for matching the same property across portals |
| `egid` / `reference_id` | federal building ID and agency reference (with details) |

### How to use

1. Enter one or more **locations**: a city (Zürich, Genève, Lugano), a ZIP code (8004), a canton (ZH, Kanton Zug) or paste search URLs from homegate.ch or immoscout24.ch.
2. Choose **rent or buy**, the **property type**, optional price, room and area filters, and the **portals**.
3. Click **Start**, then download the results as JSON, CSV or Excel, or read them through the API.

```json
{
  "locations": ["Zürich"],
  "offerType": "rent",
  "propertyType": "all",
  "portals": ["homegate", "flatfox"],
  "maxItems": 20,
  "includeDetails": false
}
```

**Tips**

- `propertyType: all` uses the broad Homegate/ImmoScout24 real-estate category, which can include commercial listings; Flatfox searches apartments and houses. Choose an explicit category when comparing portals.
- `maxItems` is global across locations and portals. Earlier streams can fill it before later portals are visited. Source ordering is not a globally merged newest-first timeline. For comparable portal coverage, use separate bounded runs.
- SMG searches are limited to at most 50 pages. Flatfox requests at most 400 candidates per category and then applies location/room/area filters. A successful run is not a full market inventory.
- Set `includeDetails: true` to open each listing page for agency name and phone, net rent and charges, availability date, EGID and reference number. This is slower; see the FAQ.

### Output example

Real record from a test run (description and arrays shortened):

```json
{
  "id": "4003557220",
  "portal": "homegate",
  "url": "https://www.homegate.ch/rent/4003557220",
  "homegate_url": "https://www.homegate.ch/rent/4003557220",
  "immoscout24_url": "https://www.immoscout24.ch/mieten/4003557220",
  "flatfox_url": null,
  "published_on": [
    "immoscout24",
    "homegate",
    "alleimmobilien",
    "home"
  ],
  "title": "Zentrale Wohnung beim Kunsthaus mit Terrasse",
  "offer_type": "rent",
  "property_type": "apartment",
  "property_subtype": "APARTMENT, TERRACE_FLAT",
  "price": 2530,
  "price_unit": "month",
  "currency": "CHF",
  "rent_net": null,
  "rent_extra": null,
  "price_per_sqm": 46,
  "rooms": 2.5,
  "living_area_sqm": 55,
  "usable_area_sqm": null,
  "lot_area_sqm": null,
  "floor": 1,
  "number_of_floors": null,
  "year_built": 1985,
  "year_renovated": 2025,
  "available_from": null,
  "street": "Wolfbachstrasse 18",
  "zip_code": "8032",
  "city": "Zürich",
  "canton": "ZH",
  "address": "Wolfbachstrasse 18, 8032 Zürich",
  "lat": 47.370411942307,
  "lng": 8.551291464946,
  "has_balcony": true,
  "has_elevator": true,
  "has_parking": true,
  "has_garage": true,
  "is_new_building": true,
  "pets_allowed": true,
  "wheelchair_accessible": true,
  "has_view": false,
  "features": [
    "balcony",
    "child_friendly",
    "elevator",
    "garage",
    "new_building"
  ],
  "agency_name": null,
  "agency_phone": null,
  "agency_website": null,
  "agency_address": null,
  "agency_id": null,
  "contact_name": null,
  "contact_phone": null,
  "reference_id": null,
  "description": "Ruhige, helle, zentrale Wohnung mit Parkettböden. Für 1 bis 2 Personen geeignet.Kernsaniert, energiearm. Badewanne, neue getrennt begehbare Küche mit Backofen, Einbaukasten. Kellerraum und Fahrradabstellmöglichkeiten. Pa...",
  "main_image": "https://media2.homegate.ch/listings/v2/hgonif/4003557220/image/78f42edb536ae961de2e7650aa6317e4.jpg",
  "images": [
    "https://media2.homegate.ch/listings/v2/hgonif/4003557220/image/78f42edb536ae961de2e7650aa6317e4.jpg",
    "https://media2.homegate.ch/listings/v2/hgonif/4003557220/image/50d39be59b2422aba95d849e4c9b2d18.jpg"
  ],
  "image_count": 5,
  "virtual_tour_url": null,
  "listing_tier": "STANDARD",
  "published_at": "2026-10-15T17:26:12.678Z",
  "updated_at": null,
  "duplicate_key": "smg-4003557220",
  "address_key": "8032|wolfbachstr18|2.5|55|rent",
  "egid": null,
  "search_location": "Zürich",
  "detail_scraped": false,
  "scraped_at": "2026-10-15T19:11:50+00:00"
}
```

### Pricing

The base price is **$3.00 per 1,000 delivered listings**, plus **$0.00005 per start event**. One start event applies per allocated GB, with a minimum of one. At 1,024 MB, 20 listings cost **$0.06005** in events; 30 separate 100-listing runs cost **$9.0015**, if each returns 100 billable listings. These are arithmetic examples, not demand, availability or margin forecasts. Proxy and compute usage are included in the current event price. Check the Pricing tab for current rates.

The Actor checks the remaining SDK result allowance before location lookup, before fetching another stream and before detail enrichment. It trims work to that allowance and counts only accepted rows. A start event can apply even if no results are returned. Use the platform's maximum charge setting as well as `maxItems`.

### Coverage and restarting a run

Inspect `RUN_SUMMARY` alongside the dataset. `delivered` counts saved rows including existing rows on restart; `resumedItems` and `newItems` distinguish them. `stopReason` identifies `max_items`, `budget_limit`, `source_exhausted` or a runtime/delivery error. `unfinishedStreams` identifies streams not exhausted; the global limit may stop before any request to a later portal. `status: partial` is expected when a result/budget limit leaves streams unfinished, or when a source/detail error occurs. `incomplete` marks an interrupted runtime or failed delivery; storage errors fail the run even if earlier results exist. `complete` means all requested streams finished within the source limits, not exhaustive market coverage.

Keep the input unchanged when restarting. Existing dataset identities prevent repeated output/charges and count toward the global limit. Source pages can be requested again because page checkpoints are not retained. Changed input or an older dataset without an input checkpoint is rejected: start a fresh run. Fresh scheduled runs can overlap; deduplicate by `duplicate_key` in your destination before treating a row as a new listing.

Detail enrichment first probes access sequentially. After two initial failures without a successful detail response, it stops requesting more details and keeps the search data, with errors and `detailEnrichmentDisabled` in the report. This contains failed-request costs; it does not solve upstream blocking or guarantee any detail fields.

### Use cases

- **Real estate investors**: track asking prices, CHF/m² and new supply by canton, city or ZIP code.
- **Agencies and property managers**: monitor competitor listings, pricing and time on market.
- **Relocation and corporate housing firms**: get daily feeds of rentals that match a client's brief.
- **Proptech and valuation models**: training data with GPS, size, year built, features and EGID.
- **Market research and media**: rent indices and supply statistics for Swiss regions.
- **Lead generation**: agencies and private landlords who are actively advertising (with details enabled).

### Integrations

Use the results anywhere through the Apify API, webhooks, **Make**, **Zapier**, **n8n**, **Google Sheets**, Airbyte or Slack. Schedule runs from the Apify Console.

Python example for `apify-client==3.2.0`; keep the token in an environment variable. This bounded example has a $0.10 event limit and four-minute runtime limit:

```python
import os
from datetime import timedelta
from decimal import Decimal
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("ninhothedev/swiss-real-estate-scraper").call(
    run_input={"locations": ["Zürich"], "offerType": "rent", "propertyType": "all",
               "portals": ["homegate", "flatfox"], "maxItems": 20, "includeDetails": False},
    memory_mbytes=1024,
    run_timeout=timedelta(seconds=240),
    max_total_charge_usd=Decimal("0.10"),
)
if run is None or run.status != "SUCCEEDED":
    raise RuntimeError("Inspect the run log before using partial results")
record = client.key_value_store(run.default_key_value_store_id).get_record("RUN_SUMMARY")
summary = record["value"] if record else {}
if summary.get("errors") or summary.get("stopReason") not in {"max_items", "source_exhausted"}:
    raise RuntimeError("Inspect RUN_SUMMARY for errors or a budget stop")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item["portal"], item["price"], item["rooms"], item["url"])
```

The example accepts an expected global-limit stop; inspect `unfinishedStreams` when portal-by-portal completeness matters.

### FAQ

**Is it legal to scrape Homegate and ImmoScout24.ch?** The Actor collects only publicly visible listing data. Personal data, such as contact names and phone numbers, may be protected under the Swiss FADP and the GDPR. Use it only for legitimate purposes, and consult a lawyer if you are unsure.

**Why are Homegate and ImmoScout24.ch combined?** Both portals belong to SMG Swiss Marketplace Group and share one listing database, with identical listing IDs. The Actor uses these IDs to remove duplicates exactly. Every record tells you which portals carry the listing (`published_on`) and links to each of them. Flatfox rows are matched to SMG only when Flatfox supplies an `smg_id`; address similarity alone is not used for automatic deduplication.

**Do I need a proxy?** The portals use DataDome bot protection, so the default is Swiss residential proxies. The Actor first tries fast HTTP requests and switches to a real Chrome browser when it is challenged. If a portal blocks a search, the run continues with the other portals and logs the problem in the `RUN_SUMMARY` record.

**Detail data is missing.** Listing pages are protected more strictly than search pages. When they are blocked, the Actor keeps the search-page data, which already includes the description, photos, features and GPS. It then skips the remaining detail requests instead of failing the run.

**How many listings can I get?** The global `maxItems`, per-search page limit and each source response all constrain delivery. Adding locations expands the search but does not guarantee full coverage.

**Support?** Open an issue on the Actor's Issues tab. Feature requests are welcome.

### Related actors by ninhothedev

- [Immowelt Scraper – Germany Real Estate](https://apify.com/ninhothedev/immowelt-scraper)
- [ImmoScout24 Scraper (Germany)](https://apify.com/ninhothedev/immoscout24-scraper)

# Actor input Schema

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

Swiss cities, ZIP codes (PLZ/NPA), cantons (name or code like ZH, GE) or geo ids. Examples: Zürich, 8004, Lausanne, Kanton Zug, ZH. Each location is searched separately.

## `offerType` (type: `string`):

Rental or sale listings.

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

Select a property category. All uses the broad Homegate/ImmoScout24 real-estate category (which can include commercial listings), but apartments and houses on Flatfox. Choose an explicit category for comparable coverage.

## `portals` (type: `array`):

Which portals to search. Homegate and ImmoScout24.ch share listing IDs, so duplicates are removed exactly. ImmoScout24.ch shares most listings with Homegate (same backend) and is often bot-protected; Homegate results include the ImmoScout24.ch link where available.

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

Global maximum across all locations and portals, including saved rows on restart. An earlier portal can fill this limit before later portals are queried. The remaining paid-result allowance can stop the run sooner.

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

Monthly rent or purchase price.

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

Monthly rent or purchase price.

## `roomsMin` (type: `number`):

Swiss room count, e.g. 2.5

## `roomsMax` (type: `number`):

Swiss room count, e.g. 4.5

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

Living area (lot area for plots, usable area for commercial).

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

Living area (lot area for plots, usable area for commercial).

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

Ordering within each source stream; results are not globally sorted across portals or locations. Fresh scheduled runs can contain previously seen listings.

## `searchUrls` (type: `array`):

Optional. Paste search result URLs from homegate.ch or immoscout24.ch (all filters kept) or single listing URLs (homegate.ch/rent/123..., immoscout24.ch/mieten/123..., flatfox.ch/...). Used in addition to locations.

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

Request additional Homegate/ImmoScout24 detail fields when available. Slower; after two initial failed detail probes without a success, keep search data and skip further details. Inspect RUN_SUMMARY for errors and enrichment coverage.

## `dedupeAcrossPortals` (type: `boolean`):

Merge matching shared SMG listing IDs across Homegate and ImmoScout24. Flatfox matches only when it supplies an smg_id. Different IDs for the same property are not automatically merged.

## `descriptionLanguage` (type: `string`):

Preferred language for title/description if the listing provides it.

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

Maximum Homegate/ImmoScout24 search pages, capped at 50. Flatfox instead considers at most 400 candidates per category before filtering. Split broad searches into smaller areas; full inventory coverage is not guaranteed.

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

Swiss residential proxies are recommended (portals use DataDome bot protection).

## Actor input object example

```json
{
  "locations": [
    "Zürich"
  ],
  "offerType": "rent",
  "propertyType": "all",
  "portals": [
    "homegate",
    "flatfox"
  ],
  "maxItems": 20,
  "sortBy": "newest",
  "includeDetails": false,
  "dedupeAcrossPortals": true,
  "descriptionLanguage": "auto",
  "maxPagesPerSearch": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CH"
  }
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "locations": [
        "Zürich"
    ],
    "portals": [
        "homegate",
        "flatfox"
    ],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "CH"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ninhothedev/swiss-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 = {
    "locations": ["Zürich"],
    "portals": [
        "homegate",
        "flatfox",
    ],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "CH",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("ninhothedev/swiss-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 '{
  "locations": [
    "Zürich"
  ],
  "portals": [
    "homegate",
    "flatfox"
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CH"
  }
}' |
apify call ninhothedev/swiss-real-estate-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ninhothedev/swiss-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/idKLsXSh3EhnpmzYb/builds/SIFwmPKAfKj8Nf9ML/openapi.json
