DEV Community

Devil Scrapes
Devil Scrapes

Posted on Originally published at devilscrapes.com

Scrape Car Listings Europe: 22 Marketplaces, One Schema

Originally published on the Devil Scrapes blog.

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

Country Marketplace Actor
France leboncoin.fr leboncoin France Car Scraper
Germany AutoScout24.de AutoScout24 Germany Car Scraper
Serbia Polovni Automobili Polovni Automobili Car Scraper
Finland Tori.fi Tori.fi Marketplace Scraper
Lithuania Autoplius.lt Autoplius Lithuania Car Scraper
Poland OTOMOTO.pl OTOMOTO Poland Car Scraper
Portugal Standvirtual.com Standvirtual Portugal Car Scraper
Bulgaria Mobile.bg Mobile.bg Bulgaria Car Scraper
Belgium 2dehands.be 2dehands Belgium Car Scraper
Netherlands Marktplaats.nl Marktplaats Netherlands Car Scraper
Austria willhaben.at willhaben Austria Car Scraper
Ukraine AUTO.RIA AUTO.RIA Ukraine Car Scraper
Iceland Bilasolur.is Bilasolur Iceland Car Scraper
Norway FINN.no FINN.no Car Scraper
Italy Subito.it Subito Italy Car Scraper
Ireland Carzone.ie Carzone Car Scraper
Spain coches.net coches.net Spain Car Scraper
Romania Autovit.ro Autovit Romania Car Scraper
Sweden Blocket.se Blocket Car Scraper
Estonia Auto24.ee Auto24 Estonia Car Scraper
Bulgaria Cars.bg Cars.bg Bulgaria Car Scraper
Croatia Njuškalo.hr Njuškalo Car Scraper

(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

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.


Devil Scrapes builds and maintains 200+ pay-per-result web scrapers on the Apify Store. Blocks, retries and proxies handled by us. Browse the full catalog or commission a custom Actor.

Actors mentioned in this post:

Top comments (0)