Immowelt.de Scraper - Properties, Prices & Agent Contacts
Pricing
from $0.80 / 1,000 results
Immowelt.de Scraper - Properties, Prices & Agent Contacts
Scrape detailed property listings from Immowelt.de into clean structured data. Extract prices, GPS coordinates, rooms, living area, photos, energy class, property features, agent contact details, and more.
Pricing
from $0.80 / 1,000 results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
1
Bookmarked
5
Total users
0
Monthly active users
3 days ago
Last modified
Categories
Security
Limited permissionsShare
Immowelt Scraper: German Property Listings with Price, Agent & GPS Data
Immowelt Scraper turns immowelt.de into structured property data. Get price, price per m², rooms, living area, floor, energy class, full address, GPS coordinates, agent name, company, phone, website, rating, and a full image gallery for Buy and Rent listings across Germany. Search by state and city, paste a search URL you already refined in your browser, or monitor a saved search on a schedule and get back only what changed. Export to JSON, CSV or Excel, or pull results straight into your app through the API.
Why This Scraper?
- 40+ fields per listing. Price, price per m², address, GPS coordinates, rooms, living area, floor, energy class, image gallery, agent name, company, phone, website, rating and more.
- GPS coordinates on nearly every listing. Latitude and longitude are added automatically from Immowelt's own map data, on by default; very large searches (beyond roughly 2000 results) may have null coordinates on later listings.
- Two ways to search. Pick a German state and city from dropdowns, or paste any Immowelt search URL you already refined in your browser.
- Rich filter set. Price, rooms, living area, construction year and sort order, passed straight through to Immowelt so results match what the site itself would show.
- Both Buy and Rent. Apartments, houses, plots and offices.
- Auto pagination. Walks forward through every result page up to the limit you set, 30 listings per page.
- Built for recurring monitoring. Incremental mode classifies each listing as new, updated, unchanged or gone since your last scheduled run, so a repeat search returns only what changed.
Use Cases
- Real estate investors: track price per m² across cities and property types to find undervalued listings.
- PropTech and market research teams: build a live feed of Buy and Rent listings with coordinates for mapping and comparison tools.
- Relocation and rental agencies: monitor a specific city or filter combination for new listings the moment they appear, using a schedule with Incremental mode.
- Lead generation: collect agent names, companies, phone numbers and websites from active listings for outreach.
- Data teams: build historical datasets of price, size, energy class and location for pricing and valuation models.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
id | "26QPUH3FZ3H7" |
url | Immowelt expose link for the listing |
listingType | "Buy" (also "Rent") |
propertyType | "APARTMENT" (also "HOUSE", "PLOT", "OFFICE") |
title | "Studio zum Kauf" |
priceValue / priceDisplay | 545000 / "545.000 €" |
pricePerSqm | "12.520 €/m²" |
rooms | 1 |
livingSpace | 43.5 |
floor | "EG" |
energyClass | "C" |
city / district / postcode | "München" / "Altstadt-Lehel" / "80538" |
latitude / longitude | 48.1344 / 11.5843 (added when GPS coordinates is on) |
agentName / agentCompany | "Frau Anna Beispiel" / "Beispiel Immobilien GmbH" |
agentPhones / agentWebsite | ["+4989000000"] / agency website URL |
agentRating / agentReviews | 4.3 / 47 |
imageCount | 13 |
images | array of full-size image URLs |
hasFloorplan / has3DTour | true / true |
isNew / hasBrokerageFee | false / false |
origin | "strict" (also "enlarged", "suggested", a filter-radius fallback) |
creationDate / updateDate | ISO timestamps from Immowelt |
changeType | "NEW" / "UPDATED" / "UNCHANGED" / "REAPPEARED" / "EXPIRED". Only present when incrementalMode is on |
scrapedAt | ISO timestamp of this scrape |
Every listing also carries legacyId (Immowelt's internal id used to build the URL), plotArea (houses and plots only), addressPublished (whether Immowelt is showing the exact street), agentType (AGENCY or PRIVATE), agentAddress, and imagesAnnotated (each image URL paired with its room classification, such as kitchen or bathroom). Turning on Visit each listing's detail page additionally fills constructionYear, energyValue and the long description; without it these three stay null. In incremental mode, changedFields lists which fields changed since the last run, and firstSeenAt / lastSeenAt track when the listing first appeared and was last seen.
How to Use
- Pick a Search mode: Filter mode (state plus city from dropdowns) or URL mode (paste Immowelt search URLs).
- Fill in the fields for that mode: for Filter mode, one or more locations, Buy or Rent, property type, and any price, room, living space or construction year filters; for URL mode, paste one or more search URLs.
- Set Max total listings (and optionally Max pages per search) to control run size and cost, then click Start.
- Download the dataset as JSON, CSV or Excel, or read it through the API.
Search by location:
{"mode": "filter","locations": [{ "state": "berlin", "city": "Berlin" }],"listingType": "Buy","propertyType": "Apartment","maxListings": 100}
Search with filters:
{"mode": "filter","locations": [{ "state": "bayern", "city": "München" }],"listingType": "Buy","propertyType": "Apartment","priceMin": 400000,"priceMax": 800000,"roomsMin": 2,"roomsMax": 3,"livingSpaceMin": 50,"livingSpaceMax": 100,"sortBy": "PriceAsc","maxListings": 200}
Direct URL with auto pagination:
{"mode": "url","urls": ["https://www.immowelt.de/classified-search?distributionTypes=Rent&estateTypes=Apartment&locations=AD08DE1113&numberOfRoomsMin=2&spaceMin=60"],"maxPages": 20}
Recurring monitoring of a saved search:
{"mode": "filter","locations": [{ "state": "berlin", "city": "Berlin" }],"listingType": "Buy","propertyType": "Apartment","maxListings": 300,"incrementalMode": true}
The first run of this last example returns every listing as NEW. Schedule it and every later run instead returns only NEW and UPDATED listings for that same search, with UNCHANGED ones suppressed from the dataset by default.
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/immowelt-de-scraper").call(run_input={"mode": "filter","locations": [{"state": "berlin", "city": "Berlin"}],"listingType": "Buy","propertyType": "Apartment","maxListings": 100,})for listing in client.dataset(run["defaultDatasetId"]).iterate_items():print(listing["title"], listing["priceDisplay"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/immowelt-de-scraper').call({mode: 'filter',locations: [{ state: 'berlin', city: 'Berlin' }],listingType: 'Buy',propertyType: 'Apartment',maxListings: 100,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
URL mode: supported URL shapes
URL mode with the urls field left untouched now scrapes the three example URLs below (one per supported shape) instead of doing nothing. Replace or clear the list to scrape your own searches.
| Shape | Example | Behaviour |
|---|---|---|
Search path (/suche/<kaufen or mieten>/<type>/<state or city>/<place>/<id>) | https://www.immowelt.de/suche/kaufen/wohnung/berlin/berlin-10115/ad08de8634 | Supported. The page is read and its search is carried over as a query search: place, every listed offer type (including auctions), property types and every other filter the page echoes back (project types, sub-types, furnished, features, build state, energy class, price, rooms, space and year ranges). All result pages are walked. The run compares the result count of the query search with the count shown on your page and logs a warning when they differ; paste the /classified-search URL from your browser to scrape an exact set. Sorting and any option the page does not echo are not carried over. |
Older plural slug (/suche/<city>/wohnungen/mieten) | https://www.immowelt.de/suche/berlin/wohnungen/mieten | Supported. The site redirects it to the search path above and the redirect is followed. |
Query search (/classified-search?...) | https://www.immowelt.de/classified-search?distributionTypes=Buy&estateTypes=Apartment&locations=AD08DE8634 | Supported, including &page=N. |
Single listing (/expose/<id>) | https://www.immowelt.de/expose/<id> | Skipped with a warning. Turn on listing details to get full listing pages from a search. |
Mobile host (m.immowelt.de) | https://m.immowelt.de/ | Skipped with a warning. The mobile host has no pages. |
If none of the URLs can be used, or no Filter-mode city name matches, the run fails with a clear message instead of finishing empty.
Locations and disambiguation
The state is what tells same-named towns apart: Germany has several towns sharing a name, so { "state": "bayern", "city": "Neustadt" } and { "state": "sachsen", "city": "Neustadt" } resolve to two different towns and return two different result sets. When a state has more than one town matching what you typed, the closest match is used and the full resolved name is written to the run log, so you can see exactly which one you got.
Max pages, max listings, and resuming a full-catalogue walk
Max pages per search and Max total listings cap a single run. Leave Max pages per search empty to walk every result page; Max total listings defaults to a hard cap of 20, and 0 removes it (still bounded by Max pages if that is set).
For an unbounded pull, two things protect a long run:
resumeFromRunIdlets you continue in a brand-new run: paste the ID of a previous run (or its dataset), and this run skips everyidalready saved there, appending only new listings.- A checkpoint is saved automatically as the run progresses. If the run is interrupted by an Apify platform migration, or you click Resurrect on a failed run, it picks back up from where it left off in the same run, without re-saving already-collected listings. No input is needed for this; it is automatic.
Resume and incremental mode
These are two different features that both sound like "continue a search":
- Resume (
resumeFromRunId) continues ONE interrupted crawl. Paste a previous run or dataset ID and this run only adds listings it has not already saved there. - Incremental mode (
incrementalMode) is for a search you run again and again on a schedule. The actor remembers its own baseline in its own storage, keyed bystateKey(or auto-derived from the search scope), and classifies every scraped listing asNEW,UPDATED(withchangedFields), orUNCHANGED(suppressed from the dataset and not billed unlessemitUnchangedis on). Every field of the listing row is compared exceptscrapedAt,isNew(a freshness flag that flips by itself over time) and any field you list inignoreFieldsForChanges; ignored fields stay in the output and are left out of the comparison andchangedFields. Nested fields use dots (imagesAnnotated.description), and changing the list never causes a one-time wave ofUPDATEDrows. A listing is only classifiedEXPIREDonce a run reaches the natural end of that search's results withemitExpiredon; a run capped by Max total listings or Max pages, one that starts from a pasted URL withpage=Nabove 1, or one continuing viaresumeFromRunId, skipsEXPIREDdetection, since it never proved the listing is actually gone.REAPPEAREDonly happens to a listing that was previously markedEXPIREDon an earlier complete scan and is then found again; it is not something that happens "by default" on an ordinary recurring run where nothing was ever marked expired. - Combining
incrementalModewithresumeFromRunIdis only supported to bootstrap the very first incremental run from a prior full-catalogue crawl. Once a baseline exists for a state key, the run fails fast asking you to removeresumeFromRunIdor pick a differentstateKey.
Send results into your apps (MCP connectors)
Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify > Settings > Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto the page where items should be created.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | filter | Filter mode (state plus city dropdowns) or URL mode (paste search URLs). |
locations | array | no default (prefill: state berlin, city Berlin) | Locations to search. Filter mode only. |
listingType | string | Buy | Buy or Rent. In Filter mode this builds the search. In URL mode it never filters: the value is read from each pasted URL when the URL states one, and this field is only the fallback label for URLs that do not. |
propertyType | string | Apartment | Apartment, House, Plot, or Office. Filter mode only. |
priceMin | integer | no default | Minimum price in EUR. Filter mode only. Leave empty for no minimum. |
priceMax | integer | no default | Maximum price in EUR. Filter mode only. Leave empty for no maximum. |
roomsMin | integer | no default | Minimum number of rooms. Filter mode only. |
roomsMax | integer | no default | Maximum number of rooms. Filter mode only. |
livingSpaceMin | integer | no default | Minimum living area in m². Filter mode only. |
livingSpaceMax | integer | no default | Maximum living area in m². Filter mode only. |
constructionYearMin | integer | no default | Earliest construction year. Filter mode only. |
constructionYearMax | integer | no default | Latest construction year. Filter mode only. |
sortBy | string | Default | Sort order for results. Filter mode only; in URL mode the sort is taken from your URL. |
urls | array | three example search URLs (one per supported shape) | One or more Immowelt search URLs. URL mode only. If a URL contains page=N, scraping starts at page N and walks forward. Leaving the field untouched in URL mode scrapes these three examples. |
maxPages | integer | no default (empty = walk every result page) | How many result pages to walk per location or URL, 30 listings per page. Applies to both modes. |
maxListings | integer | 20 | Hard cap across the whole run. 0 = unlimited, still bounded by Max pages if set. |
includeCoordinates | boolean | true | Add latitude and longitude to each listing. Minimal overhead: about one extra request per search page. |
fetchDetails | boolean | false | Visit each listing's detail page for the long description and a couple of extra fields; multiplies proxy bandwidth roughly tenfold, so leave it off unless you need the long description. Each expose page that loads bills the detail-enrichment event (Pricing tab). |
proxy | object | no default (prefill: Apify RESIDENTIAL, country DE) | Connection settings. A German residential proxy gives the cleanest data. |
mcpConnectors | array | no default | Optional: send a summary of each listing to apps you authorized under Integrations. |
notionParentPageUrl | string | no default | Notion connector only: page under which item pages are created. |
maxNotifyListings | integer | 50 | Cap on items written to each connector per run. Does not affect the dataset. |
resumeFromRunId | string | no default | A previous run ID or dataset ID to continue a full-catalogue walk across separate runs. |
incrementalMode | boolean | false | Recurring monitoring of the same search: classify every listing as NEW/UPDATED/UNCHANGED/REAPPEARED/EXPIRED against this actor's own remembered baseline. |
ignoreFieldsForChanges | array | [] | Incremental mode only: field names (dots for nested) that never count as a change. scrapedAt and isNew are always ignored. |
stateKey | string | no default | Manually name the incremental baseline. Leave empty to auto-derive one from the search scope. |
emitUnchanged | boolean | false | Also save UNCHANGED listings to the dataset. Returns and bills every listing every run. |
emitExpired | boolean | false | Also save an EXPIRED row for a previously tracked listing that no longer appears, once a run reaches a complete scan. |
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"id": "EXAMPLE0001","legacyId": "00000000-0000-0000-0000-000000000000","url": "https://www.immowelt.de/expose/00000000-0000-0000-0000-000000000000","listingType": "Buy","origin": "strict","propertyType": "APARTMENT","title": "Studio zum Kauf","priceValue": 545000,"priceCurrency": "EUR","priceDisplay": "545.000 €","pricePerSqm": "12.520 €/m²","rooms": 1,"livingSpace": 43.5,"plotArea": null,"floor": "EG","constructionYear": null,"energyClass": "C","energyValue": null,"country": "DEU","city": "München","district": "Altstadt-Lehel","postcode": "80538","street": null,"latitude": 48.1344,"longitude": 11.5843,"addressPublished": false,"agentName": "Frau Anna Beispiel","agentCompany": "Beispiel Immobilien GmbH","agentType": "AGENCY","agentPhones": ["+4989000000"],"agentWebsite": "https://www.example-immobilien.de/","agentProfileUrl": "https://www.immowelt.de/profil/0000000000000000000000000000","agentRating": 4.3,"agentReviews": 47,"agentAddress": "Beispielstraße 1, 80331 München","imageCount": 2,"images": ["https://mms.immowelt.de/3/c/e/b/sample1.jpg", "https://mms.immowelt.de/e/a/d/d/sample2.jpg"],"hasFloorplan": true,"has3DTour": true,"hasBrokerageFee": false,"isExclusive": false,"isNew": false,"creationDate": "2026-04-13T10:41:40.410Z","updateDate": "2026-04-13T10:41:54.893Z","description": null,"scrapedAt": "2026-04-21T08:45:02.602Z"}
Plan Requirement
The default connection setting is Apify Residential, country DE, and it is the recommended one: immowelt.de protects its query-search pages (/classified-search) and single listing pages (/expose/...) with a bot check, and those need a residential connection. The older /suche/... search pages are open and can also be read on a non-residential connection, but do not rely on that for the other shapes. If your Apify plan does not include residential proxy access, the run logs "Apify free plans do not include residential proxies" and continues on a backup connection; pages the backup connection cannot load stay unavailable, and the run fails rather than finishing empty (see the FAQ). You can also uncheck "Use Apify proxy" under Proxy & connection and paste your own proxy URLs (they are used as given, with no backup connection). The Pricing tab shows the current rates.
FAQ
How much does it cost?
You pay per listing returned. The Pricing tab shows the current rates. Use Max total listings to cap the cost of any run, and leave fetchDetails off unless you need the long description, since it multiplies the work per listing.
With fetchDetails on, every listing whose expose page loads also bills the actor's detail-enrichment event — its price is on the Pricing tab. Listings whose expose page could not be read ship with the search-page fields and are never billed for it, and rows suppressed as unchanged by an incremental (scheduled) run are not billed for it either.
Is it legal to scrape immowelt.de?
This actor collects only publicly available property listing data. You are responsible for how you use it: follow immowelt.de's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing facts such as price, rooms and address are generally not protected, but photos and agent branding may be subject to third-party rights.
Can I get only new or changed listings on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new and updated listings for that search, and unchanged ones are suppressed from the dataset (and not billed) unless you turn emitUnchanged on.
What's the difference between Resume and Incremental mode?
They solve different problems. Resume (resumeFromRunId) continues ONE interrupted full-catalogue crawl: paste the ID of an earlier run and this run only adds listings it has not already saved there. Incremental mode (incrementalMode) is for a search you run again and again on a schedule: it remembers its own baseline and reports only what is new, updated, or gone since the previous scheduled run. Use one or the other, not usually both; combining them is only supported to bootstrap the very first incremental run from a prior full crawl.
What happens when immowelt.de blocks every request?
A run that could not load a single results page ends as Failed with a status message, never as Succeeded with an empty dataset. The messages are:
- Account without proxy access: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required."
- Free plan, backup connection could not load the pages: "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Immowelt pages."
- Any other case: "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."
If the plan has no residential access the log also shows a boxed warning ("The selected connection is unavailable to this run ... Continuing on a backup connection; some pages may still be unavailable."), and when the backup connection serves a page it logs "Served over the backup connection (the default connection was unavailable)." If some pages load and others are blocked, the actor keeps what it collected and logs which pages were skipped. A search that loads fine but matches nothing new finishes as Succeeded with no rows.
Which dataset views are available?
Three: Listings (overview), Details & agent, and Changes (incremental), which shows changeType and changedFields. A summary of the run is also saved under the OUTPUT key of the run's key-value store.
What does incremental mode ignore?
scrapedAt and isNew never count as a change. Add more field names (dots for nested) to ignoreFieldsForChanges to ignore other noisy fields; the baseline is then compared on the remaining fields.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.
🔗 Want more real estate data?
Pair this actor with these related scrapers from the same team:
| 🏠 ImmobilienScout24.de Scraper Extract property listings from immobilienscout24.de, Germany's #1 real estate platform... | 🏠 SeLoger Scraper Scrape SeLoger.com properties for sale and rent. Search by location with price, room and... |
| 🏠 Fotocasa.es Scrape Spain property listings from fotocasa.es. Get price, surface, rooms, baths... | 🏠 Pap.fr Extract property listings from pap.fr. Get fully-detailed listings with price, address... |
| 🏠 Funda in Business Scraper Scrape Funda in Business commercial listings: offices, retail, industrial, catering and... | 🏠 Otodom.pl Scraper Scrape Otodom.pl properties for sale and rent, including apartments, houses, plots... |
💬 Support
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.