I built this actor; it's a paid tool on Apify with a free trial credit.
Kleinanzeigen (until 2023 "eBay Kleinanzeigen") is where Germany sells its used stuff: bikes, phones, furniture, cars, Lego, baby gear. If you flip items, price something before you list it, or want to know what a model really goes for in Munich versus Berlin, its ads are the closest thing to a live price list for the German second-hand market.
There's no public API for it. I'm an 18-year-old engineering student, and I built Kleinanzeigen Search Scraper to turn its search results into clean rows. This post covers calling it from Python and Node, what a row looks like, and a small project: a weekly median price for one product in a few cities.
How it works
Kleinanzeigen's result pages are server-rendered, and the listing data rides along as JSON props of a front-end component, so the actor reads it over plain HTTP with no browser. Every filter lives in the URL path: /s-preis:100:500/anbieter:privat/e-bike/k0c217l3331r10 means "e-bike, 100-500 EUR, private sellers, category 217 (bikes), location 3331 (Berlin), 10 km radius". Places like "München" or "80331" are turned into those location ids with the site's own place lookup.
One search shows at most 1,250 ads (50 pages of 25). When a search is bigger, the actor reads the counts in the sidebar and splits it: first by German state (the 16 state counts add up exactly to the total), then by sub-category, then by price band, and merges the slices without duplicates. A search for "dyson v11" across Germany came back with all 1,436 ads the site counted.
Many sellers on Kleinanzeigen are private people, so the actor never collects names, phone numbers or e-mails. A row says sellerType: "private" and that's it; only businesses get their public shop name, shop link and id. Phone numbers and e-mail addresses that people type into descriptions are masked.
Every row has the title, price, price type (fixed, "VB" negotiable, or free), the old price if it was reduced, postcode, place, date, category, seller type, shipping, "Direkt kaufen", photos and the URL. Turn on includeDetails and each ad also gets the full description, its attribute list (Zustand, Art, Typ ...), offer or wanted, shipping cost, an approximate map position and the seller's badges.
Python example
pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("rel8ble/kleinanzeigen-scraper").call(run_input={
"searchQueries": ["e-bike"],
"categories": ["217"],
"locations": ["München"],
"radiusKm": 20,
"priceMin": 500,
"priceMax": 2000,
"sellerType": "private",
"maxItems": 100,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["title"], "|", item["priceText"], "|", item["postalCode"], item["city"], "|", item["distanceKm"], "km |", item["url"])
Node example
npm install apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('rel8ble/kleinanzeigen-scraper').call({
startUrls: ['https://www.kleinanzeigen.de/s-fahrraeder/berlin/sortierung:preis/anbieter:privat/preis:10:100/fahrrad/k0c217l3331r10'],
maxItems: 50,
includeDetails: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, 'ads');
console.log(items[0].title, '-', items[0].condition, '-', items[0].shippingText);
What a row looks like
{
"id": "3543827725",
"url": "https://www.kleinanzeigen.de/s-anzeige/chirp-sign-slim-e-bike-ruecklicht-220-sicht-neu/3543827725-217-6681",
"title": "Chirp Sign Slim E-Bike Rücklicht – 220° Sicht – Neu",
"price": 15,
"priceText": "15 €",
"priceType": "fixed",
"postalCode": "89231",
"city": "Neu Ulm",
"state": "Bayern",
"postedAt": "2026-10-17T17:38:08.000Z",
"categoryName": "Fahrräder & Zubehör",
"sellerType": "private",
"shippingAvailable": true,
"buyNowEnabled": true,
"condition": "Neu",
"shippingCost": 0.99,
"sellerBadges": ["TOP Zufriedenheit", "Besonders zuverlässig"]
}
(The last three fields come with includeDetails; the full row has more, including the description, attributes and all photo URLs.)
Small project: a weekly median price in three cities
If you buy and resell, the question is "what does this actually go for here?". This script pulls every private offer for one product in three cities once a week and stores the median asking price per city. After a month you can see whether prices are drifting, and which city is cheapest to buy in.
import json, os, statistics
from collections import defaultdict
from datetime import date
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
HISTORY = "ps5_prices.json"
history = json.load(open(HISTORY)) if os.path.exists(HISTORY) else {}
run = client.actor("rel8ble/kleinanzeigen-scraper").call(run_input={
"searchQueries": ["playstation 5"],
"locations": ["Berlin", "Hamburg", "München"],
"radiusKm": 30,
"sellerType": "private",
"priceMin": 150, # drops games, controllers and "suche" posts
"maxItems": 500,
})
prices = defaultdict(list)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item["price"]:
prices[item["searchPlace"]].append(item["price"])
today = str(date.today())
for key, values in sorted(prices.items()):
median = statistics.median(values)
history.setdefault(key, {})[today] = median
print(f"{key}: {len(values)} ads, median {median:.0f} EUR")
json.dump(history, open(HISTORY, "w"), indent=1)
Put it on cron or an Apify schedule. 1,500 ads a week is $0.30, and the search part takes a couple of minutes.
Things to know
- Price: $0.20 per 1,000 ads, details included, no start fee. The Apify free plan's $5 monthly credit covers about 25,000 ads.
- The 1,250 limit is the site's. Deep search is on by default and only kicks in when you ask for more than 1,250 ads from one search; the run summary shows the total the site counts next to what was saved. Ads with no price at all ("VB" only) can't be reached through price bands, so very large searches can miss a few of those.
- Big searches take time: requests are paced politely, so tens of thousands of ads take tens of minutes. Use categories and places to narrow.
- Dates: recent ads show "Heute, 19:11" and become exact timestamps; older ads show only a date.
- No view counts: the site's public view counter adds a view every time it's read, so the actor leaves it alone rather than inflate sellers' numbers.
-
No personal data: no names, phones or e-mails; private sellers are just
private.
Questions or broken fields: the Issues tab on the actor page comes straight to me.
Kleinanzeigen Search Scraper on Apify
This article was drafted with AI and published by me.
Need a custom scraper or a ready dataset? Hire me on Fiverr: https://www.fiverr.com/rel8ble
Top comments (0)