# Coches.net Car Listings & Dealer Scraper (`oswaldocarabano/coches-net-scraper`) Actor

Scrape public used-car listings and dealer profiles from coches.net (Spain). Includes the site's own market-average price and price rank. robots.txt compliant. No login, no session cookies.

- **URL**: https://apify.com/oswaldocarabano/coches-net-scraper.md
- **Developed by:** [Oswaldo Carabano](https://apify.com/oswaldocarabano) (community)
- **Categories:** E-commerce, Lead generation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Coches.net Car Listings & Dealer Scraper

Extract used-car listings and dealer profiles from **coches.net**, Spain's
largest vehicle marketplace — including the site's own **market-average price**
and **price rank**, which is what tells you whether a car is priced below the
market before you build any valuation model of your own.

**This actor stays inside what coches.net's `robots.txt` allows.** It never
requests a page the site asks crawlers not to request, it never signs in, and it
never uses session cookies. That is a deliberate design choice, and it is the
reason the actor keeps working: the site publishes a machine-readable set of
rules and this crawler downloads and obeys the live file on every single run.

***

### What you get

One row per car listing, 55 fields. Everything comes from the search results
themselves, so a single request returns 35 fully populated cars.

**The data is delivered in English.** Fuel type and offer type are translated
(`Diesel`, `Petrol`, `Hybrid`, `Plug-in hybrid`, `Electric`, `LPG`, `Used`,
`Km0`), and the exact Spanish string coches.net published is kept alongside in
`fuel_type_raw` and `offer_type_raw` so you can always verify it against the
source.

Province, city and region stay in Spanish on purpose: they are proper nouns.
"A Coruña" is not "The Corunna", and translating them would break any join
against another Spanish dataset. `environmental_label` also stays as `0`, `ECO`,
`C` or `B` — those are the official Spanish DGT badge codes, not words.

#### The fill rates are measured, not promised

Measured on **550 listings** across **35 makes** and **38 provinces**, both
dealer and private sellers, on 17 August 2026. Nothing is listed here that was
not counted.

| Field | Filled |
|---|---|
| `ad_id`, `url`, `title`, `make`, `model`, `year`, `km`, `price_eur` | **100 %** |
| `fuel_type`, `province`, `region`, `seller_type`, `photos`, `published_at` | **100 %** |
| `offer_type`, `is_urgent`, `includes_taxes`, `is_certified`, `has_warranty` | **100 %** |
| `city`, `city_id` | 98.2 % |
| `hp`, `body_type_id` | 97.5 % |
| `warranty_months` | 80.9 % |
| `environmental_label` (Spanish DGT badge) | 79.6 % |
| **`price_average_indicator_eur`** ⭐ | **77.6 %** |
| **`price_rank_indicator`** ⭐ | **77.6 %** |
| `seller_rating_avg`, `seller_rating_count` | 74.5 % (dealers only) |
| `financed_price_eur` | 68.5 % |
| `financing_lender`, `financing_tae_pct`, `financing_instalment_eur` | 54.2 % |
| **`seller_phone`** — dealer listings only | **100 % of dealers** |

Two fields you will **not** find here because they were measured and found too
sparse to promise: video links (20.9 %) and the "featured" flag (17.5 %).

#### `price_average_indicator_eur` — what it actually is

Coches.net computes its own estimate of what a given model is worth on the
Spanish market, and publishes it alongside the asking price. `price_rank_indicator`
places this particular listing against that estimate.

Together they let you do in one query what would otherwise need a pricing model:
find every car asking meaningfully less than its own marketplace's valuation.

Be aware of the honest limits: it is filled on 77.6 % of listings, and it is
**coches.net's** estimate, not an independent one. We pass it through unchanged
rather than dressing it up as our own valuation. Of the eleven coches.net
scrapers currently on Apify, one other exposes it; the rest do not.

***

### Personal data: private sellers' phone numbers are never returned

Roughly 40 % of listings on coches.net are posted by **private individuals**, and
the site exposes their mobile numbers in the page source.

**This actor never returns them.** When `seller_type` is `private`,
`seller_phone` is `null`. Always. There is no configuration option to turn this
off, and there will not be one.

The reason is Article 14 of the GDPR: when you collect someone's personal data
indirectly, you must inform them within one month. For the ~107,000 private
sellers on coches.net that is not achievable by anyone, us or you. A setting that
moves the problem to your account would not make it go away — it would just make
it yours.

**What you do get:** every vehicle field for private listings — price, mileage,
year, power, environmental badge, province, city, photos and the price rank — plus
**dealer phone numbers in full**, on 100 % of dealer listings. Dealer numbers are
published business contact details, and dealers are the segment that buys and
sells at volume.

If you need to reach a private seller, the listing URL is in every row and the
contact form is on the page.

Data removal requests: **privacy@actorstack.dev**.

***

### Cache: you always know how old your data is

Rows can be served from a shared cache, which is why runs are cheap and fast.
That is never hidden from you — every row carries:

| Field | Meaning |
|---|---|
| `from_cache` | whether this row was fetched fresh or served from cache |
| `fetched_at` | when the data actually came off coches.net |
| `scraped_at` | when your run produced the row |
| `data_age_hours` | how old the data is |

Set **`maxCacheAgeDays` to 0** to force a fresh fetch of everything.

***

### Input

Every field has a default, so a run started with an empty input `{}` works.

```json
{
  "maxResults": 1000,
  "makes": ["bmw", "audi"],
  "provinces": ["madrid"],
  "scrapeDealers": true,
  "maxDealers": 200,
  "maxCacheAgeDays": 7
}
```

| Field | Default | What it does |
|---|---|---|
| `maxResults` | 100 | How many listings to return. Hard cap 50,000 per run |
| `makes` | all 134 | Limit to specific makes, e.g. `bmw`, `mercedes-benz` |
| `provinces` | all 52 | Limit to specific provinces, e.g. `madrid`, `a_coruna` |
| `scrapeDealers` | `false` | Also collect dealer profiles into a `dealers` dataset |
| `maxDealers` | 0 | How many dealer profiles to return |
| `maxCacheAgeDays` | 7 | 0 forces a fresh fetch |
| `maxPagesPerUrl` | 6 | How deep to paginate per facet. 6 is what `robots.txt` allows |

***

### Dealers

Turn on `scrapeDealers` for a second dataset of dealer profiles. Measured on 60
dealers, these fields were present on **100 %**: `dealer_id`, `name`,
`external_contract_id`, `phone`, `province`, `province_id`, `postal_code`,
`pack`, `is_active`. Street address and stock page URL: 96.7 %.

***

### Billing

You are charged **per row delivered**, immediately as each row is produced —
never in a lump at the end, and **never for an error row**. If a URL fails, it
goes to the `errors` dataset with the reason, and you pay nothing for it.

Starting a run costs $0.00001, which is the platform minimum. **A run that finds
nothing costs you nothing.**

***

### How it works, and what it will not do

Coches.net renders its data server-side, so every listing page already contains
the full JSON payload. No browser is launched and no JavaScript is executed,
which is why runs are fast and cheap.

Discovery works by **expanding facets** — make, then province, then price band —
rather than paginating deep into search results. That is not a workaround: the
site's own search stops returning results past roughly page 300, so deep
pagination would miss about 96 % of the inventory regardless of what any crawler
attempted.

This actor does **not**: solve CAPTCHAs, spoof TLS or browser fingerprints, use
accounts or session cookies, or return private individuals' phone numbers. If
coches.net ever makes the public data unreachable without doing one of those, the
actor will report the failure rather than work around it.

***

### Limits worth knowing before you buy

- **Listing detail pages are not included.** The individual advert pages are
  behind stricter bot protection than the search results, and reaching them would
  require the fingerprint spoofing this actor refuses to do. Free-text
  descriptions, colour, transmission and equipment lists therefore are not
  available. Everything listed in the fill-rate table above comes from the search
  results and is unaffected.
- `price_average_indicator_eur` is filled on 77.6 % of listings, not all.
- Results per run are capped at 50,000.

***

*Not affiliated with, endorsed by, or connected to coches.net or Adevinta Spain.
This actor collects only publicly accessible information. You are responsible for
how you use the data, including compliance with the GDPR where it applies to you.*

# Actor input Schema

## `maxResults` (type: `integer`):

How many car listings to return. You are charged per listing delivered, never for error rows. Hard cap: 50,000 per run.

## `makes` (type: `array`):

Limit the run to these car makes, e.g. `bmw`, `seat`, `mercedes-benz`. Leave empty to sweep all 134 makes, smallest first. Names come from the site itself, lowercase and hyphenated.

## `provinces` (type: `array`):

Limit the run to these Spanish provinces, e.g. `madrid`, `barcelona`, `a_coruna`. Leave empty for all 52.

## `environmentalLabels` (type: `array`):

Only cars carrying the Spanish DGT environmental badge you pick. `0` is the zero-emissions badge (14,546 cars) and `ECO` covers hybrids and gas (29,254). Cannot be combined with a make filter: the site's URL scheme allows only one of the two.

## `maxPriceEur` (type: `integer`):

Only cars asking up to this much. Uses the price bands coches.net itself publishes, so pick a round figure: 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000, 15000 or 20000.

## `km0Only` (type: `boolean`):

Nearly-new cars self-registered by the dealer. A small, high-value segment: 3,098 cars across Spain.

## `automaticOnly` (type: `boolean`):

113,912 of the cars on the site are automatic.

## `financedOnly` (type: `boolean`):

69,747 listings advertise financing terms. Combine with the fill-rate note in the README: financing detail comes back on 66.5% of dealer listings.

## `privateOnly` (type: `boolean`):

Individuals rather than dealers. Note the trade-off, measured: private listings carry the market-average price on only 38.2% (dealers: 86.6%), and never carry financing, warranty or seller ratings. Phone numbers are never returned for private sellers — see the README.

## `colors` (type: `array`):

Only cars in these colours. Cannot be combined with a make or badge filter: the site's URL scheme allows only one of them.

## `scrapeDealers` (type: `boolean`):

Collect dealer profiles into a separate `dealers` dataset: name, phone, address, postcode, province and pack. Dealer phone numbers are business contacts and are always included.

## `maxDealers` (type: `integer`):

How many dealer profiles to return when the option above is on.

## `maxCacheAgeDays` (type: `integer`):

Accept cached rows up to this many days old. Set to 0 to force a fresh fetch of everything. Every row tells you which it was, through `from_cache` and `data_age_hours`.

## `maxPagesPerUrl` (type: `integer`):

How deep to paginate within one facet. The default of 6 is what coches.net's robots.txt allows, and this actor does not go beyond it.

## Actor input object example

```json
{
  "maxResults": 1000,
  "makes": [
    "bmw",
    "audi"
  ],
  "provinces": [
    "madrid"
  ],
  "environmentalLabels": [
    "ECO"
  ],
  "maxPriceEur": 10000,
  "km0Only": false,
  "automaticOnly": false,
  "financedOnly": false,
  "privateOnly": false,
  "colors": [
    "azul"
  ],
  "scrapeDealers": false,
  "maxDealers": 200,
  "maxCacheAgeDays": 7,
  "maxPagesPerUrl": 6
}
```

# Actor output Schema

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

One row per used-car listing, including coches.net's own market-average price and price rank.

## `dealers` (type: `string`):

Dealer profiles with phone, address, postcode and province. Populated only when the dealer option is on.

## `errors` (type: `string`):

One row per URL that could not be fetched. These rows are never charged.

# 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 = {
    "maxResults": 1000,
    "makes": [
        "bmw",
        "audi"
    ],
    "provinces": [
        "madrid"
    ],
    "environmentalLabels": [
        "ECO"
    ],
    "maxPriceEur": 10000,
    "colors": [
        "azul"
    ],
    "maxDealers": 200,
    "maxCacheAgeDays": 7,
    "maxPagesPerUrl": 6
};

// Run the Actor and wait for it to finish
const run = await client.actor("oswaldocarabano/coches-net-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 = {
    "maxResults": 1000,
    "makes": [
        "bmw",
        "audi",
    ],
    "provinces": ["madrid"],
    "environmentalLabels": ["ECO"],
    "maxPriceEur": 10000,
    "colors": ["azul"],
    "maxDealers": 200,
    "maxCacheAgeDays": 7,
    "maxPagesPerUrl": 6,
}

# Run the Actor and wait for it to finish
run = client.actor("oswaldocarabano/coches-net-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 '{
  "maxResults": 1000,
  "makes": [
    "bmw",
    "audi"
  ],
  "provinces": [
    "madrid"
  ],
  "environmentalLabels": [
    "ECO"
  ],
  "maxPriceEur": 10000,
  "colors": [
    "azul"
  ],
  "maxDealers": 200,
  "maxCacheAgeDays": 7,
  "maxPagesPerUrl": 6
}' |
apify call oswaldocarabano/coches-net-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,oswaldocarabano/coches-net-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/EFMeSQOTT5aP0H5KE/builds/9WbmgEu2BcbNsEj5S/openapi.json
