· 6 min read · by the Devil Scrapes team
Scrape Car Listings Europe: 22 Marketplaces, One Schema
Scrape car listings across Europe from 22 country-specific marketplaces — one shared row schema, a country-to-Actor table, and real buyer use cases.
If you want to scrape car listings across Europe, the problem isn’t finding a scraper for one marketplace — it’s that “the European used-car market” is actually 20-plus independent national marketplaces, each with its own layout, its own currency, its own language, and its own defenses. We built a country-specific Actor for each one and normalized the output into one shared row schema, so pulling German and Serbian listings side by side doesn’t mean writing two parsers and reconciling two field naming conventions yourself.
The buyer problem this actually solves
Nobody wakes up wanting “an AutoScout24 scraper” as an end in itself. The people who reach for one of these Actors are usually doing one of three things:
- Cross-border price arbitrage — the same 2021 BMW 3 Series sells for a meaningfully different price in Germany versus Poland versus Romania, once you normalize for mileage and trim. Spotting that gap requires comparable data from multiple countries at once, not a single-market snapshot.
- Dealer and fleet intelligence — dealer groups and export brokers track inventory turnover, pricing moves, and stock mix across competitor lots in several countries, which means pulling the same fields from several different sites on a recurring schedule.
- Market research and pricing models — building a used-car valuation model that works across the EU needs training data with consistent fields (make, model, year, mileage, fuel type, price) across enough countries that a single national quirk doesn’t skew the model.
All three jobs fail the moment your data pipeline has to special-case each country’s field names. That’s the actual product here: not 22 scrapers, one schema.
The shared schema
Every Actor in the fleet returns the same core fields, adapted to what each marketplace actually publishes. Compare leboncoin (France) and AutoScout24 (Germany) — different sites, different languages, same shape:
| Field | leboncoin (FR) | AutoScout24 (DE) |
|---|---|---|
listing_id | "3117822207" | "a15406b4-db66-..." |
make / model | "MORRIS" / "Autre" | "Opel" / "Corsa" |
year | 1968 | 2013 |
price / currency | 34900 / "EUR" | 3490 / "EUR" |
mileage_km | 101890 | 101125 |
fuel_type | "Essence" (local label) | "Benzin" (local label) |
transmission | "Manuelle" | "Schaltgetriebe" |
engine_power_hp | null (not always published) | 101 |
body_type | "Citadine" | "Limousine" |
seller_type | "private" / "dealer" (normalized) | "private" / "dealer" (normalized) |
photo_urls | array of full-size image URLs | array of full-size image URLs |
scraped_at | ISO-8601 timestamp | ISO-8601 timestamp |
Two things stay deliberately un-normalized, because normalizing them would lose information: currency (most of these markets price in EUR, but a few — Iceland’s ISK, Norway’s NOK, Sweden’s SEK, Ukraine’s USD/UAH — genuinely aren’t) and raw field labels for fuel type and transmission, which we preserve verbatim in the source language alongside the normalized seller_type field, since collapsing “Essence” and “Benzin” into a single enum would need a lookup table you may want to own yourself. Everything structural — field names, types, the presence of mileage_km rather than miles, ISO timestamps — is identical across all 22 Actors, so a downstream pipeline written against one country’s output works against any other with zero changes.
Country to Actor
(Bulgaria has two: Mobile.bg and Cars.bg are separate marketplaces with separate Actors, both listed above.)
What we handle so the schema stays clean
Every listing site in this fleet is a JavaScript-heavy classifieds marketplace with its own rate-limiting behavior, and several run detail-page enrichment as a second hop per listing. We rotate curl-cffi browser TLS fingerprints across requests, route through Apify Proxy with a fresh session on every block, and retry on 408/429/5xx with exponential backoff, honoring Retry-After. A search that returns genuinely zero matching listings finishes SUCCEEDED with an empty dataset and a status message describing what was searched — we fail loud only when the target itself couldn’t be reached or parsed.
Pricing
Every Actor in the fleet uses the same Pay-Per-Event structure: a $0.20 actor-start fee plus $2.00 per 1,000 listings returned. A 500-listing pull from any single country costs $1.20; a 1,000-listing run costs $2.20. There’s no bundled multi-country pricing tier — each Actor bills independently on Apify.
Run it on Apify
- leboncoin France Car Scraper
- AutoScout24 Germany Car Scraper
- Polovni Automobili Car Scraper (Serbia)
Every new Apify account gets free trial credit, no card required — enough to pull a first batch from any country in the table above before deciding whether to add more.
FAQ
Do all 22 Actors return exactly the same field names?
The core fields — listing_id, make, model, year, price, currency, mileage_km, fuel_type, transmission, seller_type, photo_urls, scraped_at — are identical across every Actor in the fleet. A handful of countries add fields the others don’t publish (Iceland’s engine size in cc, Germany’s separate engine_power_kw, Ukraine’s VIN field), which show up as extra columns rather than breaking the shared shape.
Why isn’t price normalized to a single currency across countries?
Because normalizing it would require us to pick an exchange-rate source and a timestamp for the conversion, and that decision belongs to whoever is building the comparison — a snapshot rate baked into our dataset would silently go stale. We return the price in the currency the listing was actually posted in, plus the ISO currency code, so you can convert with whatever rate source your use case actually needs.
Can I search by make and model, or only by URL?
Both, depending on the country. Most Actors accept either a direct search-results URL (paste it from the marketplace and get the same filtered results back structured) or make/model/price-range filters as separate input fields. Check the individual Actor’s Input table for which mode it supports.
Is detail-page enrichment (full description, extra photos) always included?
It’s usually an optional input flag rather than the default, since enrichment means an extra request per listing and roughly doubles the run’s page-fetch cost. Turn it on when you need the full listing description or full-resolution photo set; leave it off for a fast, list-page-only pull.
What happens if a country’s marketplace changes its page layout?
Every Actor in the fleet is checked daily by our monitoring; when a target changes its markup, we triage and ship a fix — the same maintenance model that covers all 202 of our live Actors, not something specific to the car fleet.