How to get Kleinanzeigen listings in Python
By the end of this guide you have a CSV of Hamburg flats for rent from Kleinanzeigen, with cold rent, rooms and living space, from one script you can run again tomorrow. Everything below was run on 16 October 2026; the output table is what it returned.
Why scraping Kleinanzeigen yourself breaks
- Kleinanzeigen has no API to search or read property listings. Business customers can upload their own listings by OpenImmo over FTP, and only with a paid package.
- Rooms, living space and the rent split (Kaltmiete, Warmmiete, Nebenkosten) are on each listing's own page, so a search means one extra request per listing.
- The attributes come as German text, such as "2 Zimmer" and "1.290 €", which you have to parse yourself.
None of that is impossible, but each point is code you then maintain. The rest of this guide uses Listingrail, which does the reading and hands back clean JSON. The Kleinanzeigen API page has the official options with sources.
1. Get a key
One request with your email returns a key with 100 free listings. It is shown once, so keep it in an environment variable.
curl -X POST https://listingrail.com/v1/signup \
-H "content-type: application/json" \
-d '{"email": "you@example.com"}'
export LISTINGRAIL_KEY=re_...2. Search and save to CSV
Install requests (pip install requests), then run:
import csv
import os
import requests
r = requests.post(
"https://listingrail.com/v1/listings/search",
headers={"Authorization": f"Bearer {os.environ['LISTINGRAIL_KEY']}"},
json={"source":"kleinanzeigen","location":"hamburg","type":"rent","limit":50},
timeout=300,
)
r.raise_for_status()
listings = r.json()["listings"]
fields = ["price", "currency", "rooms", "area", "address", "url"]
with open("kleinanzeigen-hamburg.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(fields)
for home in listings:
writer.writerow([home[k] for k in fields])
print(len(listings), "listings saved to kleinanzeigen-hamburg.csv")The request body, on its own:
{
"source": "kleinanzeigen",
"location": "hamburg",
"type": "rent",
"limit": 50
}Each listing returned costs one credit. A search that finds nothing, or fails at Kleinanzeigen, costs nothing. A request can take up to a minute or two, because Kleinanzeigen is read at that moment.
3. What comes back
The first three rows of the run on 16 October 2026:
Every listing has the same keys on all 17 portals, so the code for Kleinanzeigen reads the others too. Fields Kleinanzeigen does not show come back as null, and the portal's own record sits in raw. With pandas:
import pandas as pd
df = pd.DataFrame(listings)
df["agent"] = df["agent"].str.get("company")
print(df[["price", "rooms", "area", "address", "agent"]].head())4. Get new listings and price cuts every day
Running the script on a schedule works, but then you compare the files yourself. A monitor stores the search, checks it once a day, and sends only what changed: new listings and price cuts. The free key holds one monitor.
requests.post(
"https://listingrail.com/v1/monitors",
headers={"Authorization": f"Bearer {os.environ['LISTINGRAIL_KEY']}"},
json={
"source": "kleinanzeigen",
"location": "hamburg",
"filters": {"type":"rent"},
"webhook_url": "https://example.com/new-listings",
},
timeout=30,
)Changes can also be read with GET /v1/monitors/{id}/changes or downloaded as CSV. Plans and limits are on the monitors page.
Kleinanzeigen quirks worth knowing
- Pass the city as it appears in Kleinanzeigen links, e.g. "hamburg", "berlin". maxPrice is sent with the search.
- price is the cold rent (Kaltmiete). The warm rent and extra costs are in raw.warm_rent_eur and raw.extra_costs_eur when the advertiser gave them.
- German listings count rooms, not bedrooms, so beds is null and rooms holds the Zimmer count. area (square metres) is null when the ad leaves it out.
- agent.name is the seller's display name, often a first name or null. listed_at was empty on every row of this run.
Questions
Does Kleinanzeigen have an API?
Not for reading listings. The official route is an OpenImmo upload for business customers with a paid package.
Is the price warm or cold rent?
Cold rent. raw.warm_rent_eur holds the warm rent when the ad states it.
Does it cover flats for sale?
Yes: use "type": "sale".