How to Scrape eBay Kleinanzeigen Germany (2026)

eBay Kleinanzeigen (rebranded to Kleinanzeigen.de in 2023) is Germany’s dominant classifieds marketplace, with over 35 million active listings spanning cars, electronics, real estate, and jobs. If you’re building price intelligence, lead-generation pipelines, or competitive datasets for the German market, scraping Kleinanzeigen is a core skill — but the site’s Cloudflare protection and session-based rendering make naive scrapers fail fast.

What You’re Actually Dealing With

Kleinanzeigen runs Cloudflare with JS challenge pages on most listing endpoints. The search page (/s-anzeigen/) is server-rendered HTML, which is the good news. Individual listing pages load fine with a proper browser fingerprint. The bad news: Cloudflare’s bot score kicks in after roughly 30-50 requests per session without rotation, and the site enforces rate limits at the category + IP level.

The anti-bot stack in 2026 looks like this:

  • Cloudflare Turnstile on the login and posting flows
  • TLS fingerprint checks (JA3/JA4) on the API layer
  • Behavioral mouse-movement scoring on paginated search
  • Rate limiting at ~50 req/min per IP before soft-blocking

Unlike scraping OLX classifieds across countries where country-specific subdomain isolation is the main challenge, Kleinanzeigen’s main obstacle is Cloudflare bypass at scale.

Choosing Your Scraping Approach

For most use cases, choose between three stacks:

ApproachToolsSpeedCostBot Risk
Headless browserPlaywright + stealthSlow (1-3 req/s)MediumLow
HTTP + CF bypasscurl-cffi + TLS spoofingFast (10-30 req/s)LowMedium
Scraping APIScraperAPI, Bright DataFastHighVery Low
Residential proxy + requestshttpx + rotating proxiesMediumMediumMedium

For datasets under 50,000 listings, curl-cffi with a residential proxy pool is the sweet spot. For anything larger or for real-time monitoring, a managed scraping API eliminates most of the maintenance burden.

The Playwright approach makes sense when you need full listing detail pages including seller phone numbers (shown after a click) or when you’re scraping the map-based search interface.

Extracting Listings with curl-cffi

curl-cffi spoofs TLS fingerprints at the connection level, which clears Cloudflare’s JA3 checks without running a full browser. Here’s a minimal search scraper:

from curl_cffi import requests as cffi_requests
from bs4 import BeautifulSoup
import time, random

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Chrome/124.0.0.0 Safari/537.36)",
    "Accept-Language": "de-DE,de;q=0.9",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}

def search_kleinanzeigen(query: str, location: str, pages: int = 5):
    session = cffi_requests.Session(impersonate="chrome124")
    results = []

    for page in range(1, pages + 1):
        url = f"https://www.kleinanzeigen.de/s-{location}/{query}/seite:{page}/k0"
        resp = session.get(url, headers=HEADERS, timeout=15)
        if resp.status_code != 200:
            print(f"Blocked on page {page}: {resp.status_code}")
            break
        soup = BeautifulSoup(resp.text, "lxml")
        cards = soup.select("article.aditem")
        for card in cards:
            title = card.select_one(".ellipsis")
            price = card.select_one(".aditem-main--middle--price-shipping--price")
            results.append({
                "title": title.text.strip() if title else None,
                "price": price.text.strip() if price else None,
                "url": "https://www.kleinanzeigen.de" + card.get("data-href", ""),
            })
        time.sleep(random.uniform(2.5, 5.0))

    return results

Key details:

  • Set Accept-Language: de-DE — Cloudflare’s bot scoring uses language header consistency as a signal
  • The data-href attribute on article.aditem is more stable than anchor tags inside cards
  • Sleep 2.5-5 seconds between pages, not 0.5 — the site tracks inter-request timing

Proxy Strategy for German Residential IPs

Kleinanzeigen’s rate limits are IP-scoped at the category level. One residential German IP can do roughly 200-400 listing fetches per day before triggering soft blocks. For scale, you need a pool.

Proxy type breakdown for this target:

  • German residential — highest success rate, needed for location-filtered searches; Bright Data and Oxylabs both have DE residential pools
  • German mobile — best for bypassing Turnstile-adjacent challenges; slower and more expensive but rarely flagged
  • Datacenter — blocked on first request by Cloudflare in most cases, not viable
  • ISP/static residential — good middle ground, lower per-request cost than mobile

If you’re running multi-country classifieds pipelines, note that the same proxy architecture applies to similar sites. Avito Russia classifieds and Gumtree UK and Australia both need residential IPs with geo-targeting — worth building a unified rotation layer rather than per-site proxy configs.

Rotate IP on every 10-15 requests, not every request. Cloudflare’s behavioral scoring tracks session patterns, and rapid IP switching looks more bot-like than slow rotation with human-paced delays.

Parsing Listing Detail Pages

Search result cards give you title, price, location, and post date. For full data you need the detail page:

Key fields and their selectors:

  1. Description: #viewad-description-text — full HTML, strip with .get_text(separator="\n")
  2. Seller type: #viewad-contact contains “Privat” or “Gewerblich” as a span
  3. Category breadcrumb: #breadcrumbs-desktop li — useful for taxonomy tagging
  4. Post ID: present in the URL as /s-anzeige/{title}/{id} and also in #viewad-ad-id-box
  5. Images: #viewad-image carousel, data-imgsrc attributes hold full-res URLs

For seller contact data (phone numbers), Kleinanzeigen gates the reveal behind a button click that fires an XHR to /s-anzeige-kontaktieren/{id}. This endpoint requires a valid session cookie and returns a JSON blob. Playwright is more reliable here than trying to replay the XHR manually.

If your use case is price intelligence rather than lead generation, you rarely need the seller contact step. The same is true when scraping Kijiji Canada classifieds — listing-level data usually covers the analytics case.

Handling Errors and Anti-Bot Signals

Common failure modes and responses:

  • 403 with CF-Ray header — TLS fingerprint failed, switch to curl-cffi with a different impersonate target (try chrome120 or safari17)
  • 429 with Retry-After — respect the header, rotate IP, and add 10-15s of extra delay
  • 200 but empty article.aditem list — Cloudflare JS challenge page rendered, not actual HTML; check resp.text for cf-spinner or jschl-answer
  • 302 redirect to /gesperrt — IP or account flagged, retire that IP immediately

For large-scale pipelines, use these as circuit breakers. Track success rate per IP and retire any IP dropping below 70% over a 30-minute window. This approach applies broadly — the eBay competitive intelligence guide covers the same circuit-breaker pattern for eBay.de listing scrapes, which face similar Cloudflare configurations.

Bottom Line

For most Kleinanzeigen scraping tasks in 2026, curl-cffi with a rotating German residential proxy pool handles search pages reliably at moderate scale. Add Playwright only when you need seller contact data or the map-search interface. Keep request pacing human-slow, rotate on a session basis rather than per-request, and build IP retirement logic from day one. DRT covers this category of regional classifieds infrastructure in depth — the patterns here transfer directly to other European and APAC classifieds targets.

Related guides on dataresearchtools.com

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top
message me on telegram

Resources

Proxy Signals Podcast
Operator-level insights on mobile proxies and access infrastructure.

Multi-Account Proxies: Setup, Types, Tools & Mistakes (2026)