Immowelt Scraper — Germany Real Estate Listings | $1.19/1K avatar

Immowelt Scraper — Germany Real Estate Listings | $1.19/1K

Pricing

from $0.97 / 1,000 immowelt scraper — germany real estate listings | $1.19/1ks

Go to Apify Store
Immowelt Scraper — Germany Real Estate Listings | $1.19/1K

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

Vitalii Bondarev

Maintained by Community

Actor stats

0

Bookmarked

4

Total users

3

Monthly active users

10 days ago

Last modified

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

FieldTypeDefaultDescription
searchUrlstring(required)An immowelt.de search-results URL. Set your filters on immowelt.de, then copy the URL from your browser.
maxItemsinteger100Max listings to return (0 = unlimited)
proxyConfigurationproxyRESIDENTIAL DEApify 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

FieldDescription
listing_idImmowelt internal listing/online ID
urlDirect URL to the listing
title / description
price / price_display / price_typeprice_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_typeListing agent/agency contact
images / image_count
energy_info / building_stateBest-effort; full energy certificate detail lives on the classified detail page
publication_date / update_date
page_number / source / parse_confidence / warnings / scraped_atProvenance/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.py is defensive: every field falls back to None and deducts parse_confidence rather 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 pageProps wrapper 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 an id plus a signal field), same pattern as the sibling seloger-listings-scraper and the fleet's existing immoscout24-listings actor.
  • 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.classified per 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 totalPages field, 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.