# Kleinanzeigen Scraper: Ads, Prices & Sellers (`reventadata/kleinanzeigen-scraper`) Actor

Search kleinanzeigen.de (Germany, formerly eBay Kleinanzeigen) by keyword, category or place. Get ads with price, location, posting time, shipping and PRO-shop flags, wanted ads split out. Optional full ad pages add attributes and seller. No proxy needed.

- **URL**: https://apify.com/reventadata/kleinanzeigen-scraper.md
- **Developed by:** [reventadata](https://apify.com/reventadata) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Kleinanzeigen Scraper: Ads, Prices and Sellers on kleinanzeigen.de

> **Unofficial.** This Actor is not affiliated with, authorised by, endorsed by, or in any
> way officially connected to Kleinanzeigen (kleinanzeigen.de). "Kleinanzeigen" is a trademark
> of its respective owner and is used here only to describe what this tool reads. It reads the
> same public pages a browser does.

Search kleinanzeigen.de (formerly eBay Kleinanzeigen), Germany's classifieds portal, by
keyword, category or place, and get each matching ad as a clean row: price, location, posting time,
shipping, seller badges and, optionally, the full ad page with its structured attributes.

### What does it do?

- Walks the search result pages for each term (25 ads a page, up to 50 pages, so up to 1,250 ads
  per search term; this is the site's own ceiling).
- Returns one row per ad: price in whole euros (with the VB "negotiable" flag, free ads as 0 and
  the struck-through earlier price), postcode and town, posting time in Europe/Berlin, shipping
  and buy-now flags, PRO-shop badge and shop name, photo count and thumbnail.
- Marks wanted ads (*Gesuch*, someone looking to buy) and drops them by default.
- With **Fetch the full ad page** on, opens each kept ad and adds the complete text, every
  attribute the seller filled in (brand, model, condition, storage, battery state, frame size and
  so on, verbatim German), seller name and kind (private or commercial), member-since date, the
  seller's number of ads online, category path and the approximate coordinates the ad page
  publishes.

### Why use it?

- **No proxy needed.** Measured over 25 pages and a 50-page run without one; no proxy bill on top. The proxy option stays
  available.
- **Offers separated from wanted ads**, promoted placements flagged, PRO shops separable from
  private sellers.
- **Filters that work on every field.** Price range, shipping, seller kind and offer type are
  applied to the rows after fetching, so they apply to every category.
- **Honest empty runs.** A search with no hits or a changed page layout fails the run instead of
  returning an empty dataset that looks like success.

### How to use it

1. Give one or more search terms in `searchQueries`, or a `categoryId` (the number after the `c`
   in a category URL such as `/s-handy-telekom/c173`), or both.
2. Optionally narrow by place with `locationId` (the number after the `l` in a search URL such as
   `/s-berlin/iphone/k0l3331`).
3. Set the filters you need and run. Download the dataset as JSON, CSV, Excel or NDJSON.

#### Input

| Field | Type | Description |
|---|---|---|
| `searchQueries` | array | Search terms. Optional when `categoryId` is set. |
| `categoryId` | integer | Site category number; narrows every term, or lists the category on its own. |
| `locationId` | integer | Site place number; default is all of Germany. |
| `offerType` | string | `offers` (default), `wanted` or `all`. |
| `sellers` | string | `any` (default), `private` (no PRO badge) or `proShops`. |
| `minPrice`, `maxPrice` | integer | Inclusive euro bounds. Ads with no numeric price (VB only) are dropped when a bound is set. |
| `shippingOnly` | boolean | Keep only ads marked *Versand möglich*. |
| `excludePromoted` | boolean | Drop the undated promoted cards (up to 2 on a page). |
| `requireKeywordMatch` | boolean | Default on. Drops ads where a word of the term is in neither the title nor the card text. The site matches the full ad text, so a search for a bike also returns bike seats and repair offers; this drops many of them, and an ad that only mentions the term deeper in its text is dropped too. |
| `fetchDetails` | boolean | Open each kept ad for the full text, attributes and seller block. One extra request per row, so slower. |
| `maxItems` | integer | Row cap across all terms. |
| `maxPages` | integer | Pages per term, 1 to 50. |
| `proxyConfiguration` | object | Optional; not needed. |

#### Sample input

```json
{
    "searchQueries": ["fahrrad"],
    "maxItems": 50,
    "maxPages": 3,
    "offerType": "offers",
    "fetchDetails": true
}
```

#### Output

One row per ad. Fields marked *detail* are filled only when **Fetch the full ad page** is on, and
are `null` otherwise; fields the site does not show for an ad are `null` too (for example
`condition` exists only in categories that have a *Zustand* attribute).

| Field | Description |
|---|---|
| `id`, `title`, `url` | Ad number, title and canonical link. |
| `price`, `currency`, `priceNegotiable`, `isFree`, `previousPrice` | Whole euros; `null` price when only VB is shown; `0` and `isFree` for *Zu verschenken*; the earlier price when the seller lowered it. |
| `postcode`, `location`, `region` | Card postcode and town; `region` (state) is *detail*. |
| `postedAt`, `postedDate` | Posting time with Berlin offset when the card shows a clock time, and the date; promoted cards show neither. |
| `isPromoted` | Card shown without a date ahead of the dated results. Inferred to be a paid placement; the site does not label it. |
| `isWanted` | Wanted ad (*Gesuch*). |
| `shipping`, `buyNow`, `sellerWarranty` | *Versand möglich*, *Direkt kaufen*, *Verkäufergarantie* badges. |
| `isProShop`, `shopName`, `shopUrl` | PRO shop badge and shop. |
| `imageUrl`, `imageCount`, `snippet` | Thumbnail, photo count, the card's short text. |
| `description`, `condition`, `attributes` | *detail*: full text, the *Zustand* value, every attribute as written. |
| `sellerName`, `sellerId`, `sellerKind`, `sellerShopUrl`, `sellerSince`, `sellerAdCount` | *detail*: public seller profile fields as the page shows them. `sellerKind` is the site's own `privat` or `gewerblich`. |
| `categoryId`, `categoryPath`, `latitude`, `longitude` | *detail*: category and the approximate coordinates the ad page publishes for the ad's area. |
| `detailFetched`, `matchedKeyword`, `scrapedAt` | Whether the ad page was merged, the term that found the ad, UTC timestamp. |

#### Sample output

One row from a real run (the description is shortened here):

```json
{
    "id": "3519675257",
    "title": "Hercules, Modell Pippa, in der Größe 20 Zoll",
    "url": "https://www.kleinanzeigen.de/s-anzeige/hercules-modell-pippa-in-der-groesse-20-zoll/3519675257-217-3377",
    "price": 149,
    "currency": "EUR",
    "priceNegotiable": false,
    "isFree": false,
    "previousPrice": null,
    "postcode": "10961",
    "location": "Kreuzberg",
    "region": "Berlin",
    "postedAt": "2026-10-21T15:08:00+02:00",
    "postedDate": "2026-10-21",
    "isPromoted": false,
    "isWanted": false,
    "shipping": false,
    "buyNow": false,
    "sellerWarranty": false,
    "isProShop": true,
    "shopName": "Harald Bike´s",
    "shopUrl": "https://www.kleinanzeigen.de/pro/Haralds-Fahrradladen",
    "imageUrl": "https://img.kleinanzeigen.de/api/v1/prod-ads/images/63/630441b7-06db-4fac-b615-b9e05cbb81bf?rule=$_2.AUTO",
    "imageCount": 15,
    "snippet": "Hallo, es wird angeboten ein Fahrrad der Marke Hercules, Modell Pippa, in der Größe 20 Zoll. Es ist...",
    "description": "Hallo, es wird angeboten ein Fahrrad der Marke Hercules, Modell Pippa, in der Größe 20 Zoll. Es ist ein zuverlässiger Begleiter für den Alltag\n\n- Marke: Herc...",
    "condition": "Sehr Gut",
    "attributes": {
        "Art": "Kinder",
        "Typ": "Cityräder",
        "Zustand": "Sehr Gut"
    },
    "sellerName": "Harald",
    "sellerId": "70618590",
    "sellerKind": "gewerblich",
    "sellerShopUrl": "https://www.kleinanzeigen.de/pro/Haralds-Fahrradladen",
    "sellerSince": "13.12.2019",
    "sellerAdCount": 120,
    "categoryId": 217,
    "categoryPath": [
        "Auto, Rad & Boot",
        "Fahrräder & Zubehör"
    ],
    "latitude": "52.4906134",
    "longitude": "13.400943",
    "detailFetched": true,
    "matchedKeyword": "fahrrad",
    "scrapedAt": "2026-10-21T13:11:02.661698+00:00"
}
```

### Limits

- The site serves at most 50 pages per search (1,250 ads); narrow the search with a category or
  place to go deeper.
- A literal hyphen in a search term cannot be told apart from a space by the site.
- Price, seller-kind, shipping and offer-type filters run after fetching, so they do not reduce the
  number of pages read.
- `sellers: private` means "no PRO shop badge on the card". A commercial seller without a PRO shop
  is listed as private by that filter; use `fetchDetails` and read `sellerKind` for the site's own
  label.
- Phone numbers are not on the pages as served (the site masks them behind a click), so rows have no phone field; a
  number a seller wrote into the ad text ships inside `description`. Messaging is not read.
- The site's robots.txt disallows `/*/seite:6*` to `/*/seite:59*`, a rule that matches its older `/<category>/seite:N/`
  URLs. This Actor pages with `/s-seite:N/...`, which that rule does not match, and reads up to page 50. Set `maxPages`
  to 5 if you want to stay within the stricter reading.
- Rows can carry public profile fields of natural persons (a seller's display name, the site's
  seller ID, member-since date). Use them in line with the data protection law that applies to you.

### How fast is it?

Measured 2026-10-21: 694 rows from 50 search pages in 83 seconds. With `fetchDetails` on, 87 rows in 88 seconds,
about one second per row.

### How much does it cost to scrape Kleinanzeigen?

Pay-per-event: **$0.001 per result** saved to the dataset (**$1.00 per 1,000 results**), plus **$0.00005** charged once
when the run starts. No proxy, so no proxy cost. Filters run before a row is saved, so rows they drop are not charged.

### More scrapers

| Actor | What it scrapes |
|---|---|
| [eBay Spain Scraper](https://apify.com/reventadata/ebay-scraper) | eBay Spain active listings |
| [Vinted Spain Scraper](https://apify.com/reventadata/vinted-scraper) | Vinted listings by keyword or category |
| [Wallapop Scraper](https://apify.com/reventadata/wallapop-scraper) | Wallapop second-hand listings |
| [Gumtree UK Scraper](https://apify.com/reventadata/gumtree-scraper) | Gumtree UK classifieds |
| [OLX Romania Scraper](https://apify.com/reventadata/olx-ro-scraper) | OLX Romania classifieds |

# Actor input Schema

## `searchQueries` (type: `array`):

One search per term, across every category of kleinanzeigen.de (Germany). Optional when you give a category ID instead. Spaces separate words; a literal hyphen cannot be told apart from a space by the site.

## `categoryId` (type: `integer`):

Optional numeric category from the site's own URLs, the number after the c in a category page such as /s-handy-telekom/c173 (173 = Smartphones and phones). Narrows every search term to that category; with no search term it lists the category itself.

## `locationId` (type: `integer`):

Optional numeric place from the site's own URLs, the number after the l in a search such as /s-berlin/iphone/k0l3331 (3331 = Berlin). Without it the search covers all of Germany. Needs a search term or a category ID.

## `offerType` (type: `string`):

kleinanzeigen.de mixes offers with wanted ads (cards marked Gesuch, 'I am looking for'). Offers drops the wanted ads, which is almost always what you want; Wanted keeps only them.

## `sellers` (type: `string`):

Judged from the search card: PRO shops are the cards carrying the PRO shop badge, everything else counts as private. A commercial seller without a PRO shop is therefore listed as private here; turn on detail pages and read the sellerKind column for the site's own private or commercial label.

## `minPrice` (type: `integer`):

Optional, inclusive. Applied after fetching, because the site's own price filter path is disallowed in its robots.txt. Ads without a numeric price (negotiable-only 'VB') are dropped whenever a bound is set; free ads count as 0.

## `maxPrice` (type: `integer`):

Optional, inclusive. Applied after fetching, like the minimum.

## `shippingOnly` (type: `boolean`):

Keeps only cards marked Versand möglich (shipping possible).

## `excludePromoted` (type: `boolean`):

Drops the cards shown without a posting date ahead of the dated results on each page (up to 2 on a page). The site labels them nowhere; they are inferred to be paid placements. Off by default, so they are returned and flagged isPromoted.

## `requireKeywordMatch` (type: `boolean`):

Drops ads where a word of the search term is missing from both the title and the card's text snippet. The site also matches the full ad text, so a search for a phone returns cases and repair offers too. Ignored for a category-only run.

## `fetchDetails` (type: `boolean`):

Opens every kept ad for the complete description, the structured attributes (brand, condition, storage, battery state and so on), seller name and kind, member-since date, category path and the ad's approximate coordinates. One extra request per row, so runs are slower. Off by default.

## `maxItems` (type: `integer`):

Hard cap on rows pushed across all search terms.

## `maxPages` (type: `integer`):

25 ads a page. The site serves at most 50 pages (1,250 ads) per search and answers a request past the end with a redirect, so this is clamped to 50.

## `proxyConfiguration` (type: `object`):

Optional. Measured over 25 sequential search pages and 25 ad pages, kleinanzeigen.de needed no proxy at all. Left off by default; only turn it on if you see request failures.

## Actor input object example

```json
{
  "searchQueries": [
    "fahrrad"
  ],
  "offerType": "offers",
  "sellers": "any",
  "shippingOnly": false,
  "excludePromoted": false,
  "requireKeywordMatch": true,
  "fetchDetails": false,
  "maxItems": 25,
  "maxPages": 1,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Items scraped from the target website.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQueries": [
        "fahrrad"
    ],
    "maxItems": 25,
    "maxPages": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("reventadata/kleinanzeigen-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQueries": ["fahrrad"],
    "maxItems": 25,
    "maxPages": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("reventadata/kleinanzeigen-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQueries": [
    "fahrrad"
  ],
  "maxItems": 25,
  "maxPages": 1
}' |
apify call reventadata/kleinanzeigen-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,reventadata/kleinanzeigen-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/9soVKLiClQWivbbtw/builds/u3FqNrSlXquq6QWfm/openapi.json
