Immowelt Scraper — Germany Real Estate Listings | $1.19/1K
Pricing
from $0.97 / 1,000 immowelt scraper — germany real estate listings | $1.19/1ks
Immowelt Scraper — Germany Real Estate Listings | $1.19/1K
Scrape Immowelt.de (German real estate portal) from a search-results URL you paste. Returns title, price, rooms, living space m², location, provider/agent contact, images, and listing URL.
Pricing
from $0.97 / 1,000 immowelt scraper — germany real estate listings | $1.19/1ks
Rating
0.0
(0)
Developer
Vitalii Bondarev
Maintained by CommunityActor stats
0
Bookmarked
4
Total users
3
Monthly active users
10 days ago
Last modified
Categories
Share
Immowelt Listings Scraper
Scrape Immowelt.de (German real estate portal) from a search-results URL you paste — no need to configure filters in this actor, just set them on immowelt.de and copy the resulting URL.
Features
- Scrape any Immowelt search (buy or rent, any city/price/room filter you set on their site)
- Returns per-listing: price, rooms, living space m², location, provider/agent contact, images, and listing URL
- Uses Immowelt's own embedded Next.js
__NEXT_DATA__search-results JSON — no HTML scraping, no browser - Pay per result — you only pay for listing records actually extracted
- Automatic pagination up to
maxItems, with residential-proxy session rotation on a block
Input
| Field | Type | Default | Description |
|---|---|---|---|
searchUrl | string | (required) | An immowelt.de search-results URL. Set your filters on immowelt.de, then copy the URL from your browser. |
maxItems | integer | 100 | Max listings to return (0 = unlimited) |
proxyConfiguration | proxy | RESIDENTIAL DE | Apify Residential proxy, Germany |
Why a search URL instead of filter fields?
Traction-proven Store competitors for Immowelt (igolaizola/immowelt-scraper, azzouzana/immowelt-scraper, ninhothedev/immowelt-scraper) mostly accept either a location/locationID pair or a raw search URL. This actor takes the URL directly — simplest, safest contract, and it avoids guessing Immowelt's internal location-ID scheme wrong.
Output fields
| Field | Description |
|---|---|
listing_id | Immowelt internal listing/online ID |
url | Direct URL to the listing |
title / description | |
price / price_display / price_type | price_type is best-effort (kaufpreis/kaltmiete/warmmiete) |
rooms / living_space_m2 / plot_size_m2 | |
city / district / postal_code / street / country | |
latitude / longitude | |
provider_name / provider_phone / provider_type | Listing agent/agency contact |
images / image_count | |
energy_info / building_state | Best-effort; full energy certificate detail lives on the classified detail page |
publication_date / update_date | |
page_number / source / parse_confidence / warnings / scraped_at | Provenance/QA fields. source is next_data or ufrn_fetcher depending on which embedded blob supplied the record. |
Access path
Immowelt has no public search API. It's a Next.js SPA — the search-results page embeds the result set as JSON, primarily via <script id="__NEXT_DATA__" type="application/json"> (standard Next.js page-data, parsed directly, no unescaping) at documented path props.pageProps.classifiedsData. This actor fetches the page with curl_cffi (Chrome TLS impersonation, no browser). As a fallback (only tried when __NEXT_DATA__ yields nothing), it also decodes a second observed bootstrap blob, window["__UFRN_FETCHER__"] = JSON.parse("..."), which uses the same double-encoding scheme as the sibling seloger-listings-scraper actor's initialData — the decode routine is the same shape, ported here rather than shared, per this fleet's per-actor _shared/ convention.
Apify RESIDENTIAL (DE) proxy is recommended for production reliability; third-party vendors document general anti-scraping blocking on this target though the specific mechanism (Cloudflare/DataDome/other) wasn't confirmed against a live run for this build.
Honest caveats (read before relying on this in production)
- Field-name mapping is unverified against a live Immowelt response. The field names used here (
hardFacts.facts[],hardFacts.price,location.address,gallery.images,provider,metadata.publishDate, …) come from ScrapFly's public engineering write-up on scraping Immowelt.de, not from an authenticated cloud run against the live site (out of scope for this build).normalize.pyis defensive: every field falls back toNoneand deductsparse_confidencerather than raising. Spot-check the exact field set against a real cloud run (with RESIDENTIAL DE proxy) before publishing or relying on this for paid delivery. - The exact
pagePropswrapper key for the listings array is unconfirmed — the parser tries several candidate keys (classifiedsData,classifieds,searchResult,initialGeoSearchResult) then falls back to a tolerant structural walk (first list of dicts with anidplus a signal field), same pattern as the siblingseloger-listings-scraperand the fleet's existingimmoscout24-listingsactor. hardFacts.facts[]label matching (rooms/living-space/plot-size) is heuristic (substring match on German/English label text), not a confirmed fixed key list — a mislabeled fact silently stays unmapped rather than crashing, which trades some recall for robustness under an unconfirmed schema.- Full energy certificate detail, exact address (house number), and construction-year data live on the classified detail page (
props.pageProps.classifiedper the same source), not the search-results payload — this actor is search-summary-only; detail-page enrichment is a straightforward v2 addition once the search path is verified live. - Termination is by empty-page detection (30 results/page per source docs), not a parsed
totalPagesfield, since the exact count-field name is also unconfirmed.
Tests
Unit tests (tests/) run entirely offline against synthetic fixtures — no live network calls: cd actors/REAL_ESTATE/immowelt-listings-scraper && python3 -m pytest -q.
Usage statistics
This Actor creates a small, content-free summary at the end of each run. It is used only to monitor reliability and improve this Actor. A copy is saved as USAGE_STATS in your own Apify key-value store, so you can see the exact record created for your run.
Set disableUsageStats to true in the input to opt out. Nothing is sent then; your USAGE_STATS record only says that statistics were disabled.
Only these fields are recorded:
- schema version, Actor name and build number;
- UTC start and finish hour (not a precise timestamp);
- run duration, number of results and time to the first result, each as a coarse range;
- whether the result was empty, the end status, and an error type from a fixed list;
- memory setting and counts of charged events;
- names of the input fields you set, never their values;
- the selected option for input fields that offer a fixed list of choices (for example a sort order).
We do not collect input text, search terms, URLs, domains, usernames, email addresses, names, proxy credentials, tokens, scraped records, output items, raw error messages, stack traces, or hashes of any of those values. Records are kept for no longer than 13 months, used only as aggregated operational statistics, and never sold or shared.
Additional fields (Phase 2)
This Actor also records your Apify user ID, whether Apify marks the account as paying, the size range of list inputs, the selected country when the input offers a fixed list of countries, and one category from a fixed Actor taxonomy. We use these fields only for aggregate reliability, repeat-use and cross-Actor analysis; reports suppress any cell with fewer than five distinct users.
The same disableUsageStats: true input flag turns these fields off too. The user ID is removed after 13 months; we do not export, sell, share, or attempt to re-identify this data.
Run-outcome signals (v2)
To learn whether a run did what it was asked to do, the record also holds a few more coarse ranges and yes/no flags. None of them contains content:
- the result limit you asked for (a range, when the input has one) and what share of it was delivered;
- results delivered per input item you listed (a range);
- output quality as ranges: how fully the result fields were filled, the share of rows that look like errors, the share of duplicate rows, and how many different fields appeared. These are counted in memory while results are saved; no result content is kept;
- how the run was started (console, API, schedule, webhook, another Actor);
- how it ended: stopped by you, timed out, reached the requested limit, stopped by the charge limit, and how many times the platform moved the run;
- if this Actor reports it: how many items to process worked or failed (ranges) and one failure reason from a fixed list;
- a short code made from the names of the input fields you set, never their values.
Repeat-run fingerprint (v2)
When your Apify user ID is recorded (see above), the record also holds an 8-character one-way code made from your input (proxy settings left out) and this Actor's name. It only lets us see that the same account ran the same input again soon after an unsatisfying run; we never see the input itself. It is stored only in the database, never published, and reports use it in aggregate with the same five-user minimum. It is the one exception to the statement above that no hashes are collected, and disableUsageStats: true turns it off.