# leboncoin Scraper (`s-r/leboncoin-scraper`) Actor

Scrape French classified ads from leboncoin: price and currency, seller type and company number, location down to coordinates, every photograph, and the seller's full structured attribute set. Promoted adverts are labelled, not passed off as matches.

- **URL**: https://apify.com/s-r/leboncoin-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** E-commerce, Business
- **Stats:** 1 total users, 0 monthly users, 90.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 actor run starteds

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

## leboncoin Scraper

A leboncoin scraper that returns French classified ads with the price and its currency, the seller type and company number, the exact location down to coordinates, every photograph, and the full structured attribute set the seller filled in. Promoted adverts the site injects into each page are labelled rather than passed off as matches.

France's dominant classifieds site, from cars to phones to property, on one input. No login, no API key, no browser.

### What you get

- **The two injected adverts per page named, not silently mixed in.** leboncoin drops two promoted ads into every result page, and on a search for "iphone" they are cars. They are excluded by default and, if you ask for them, arrive flagged as `is_injected_ad`. This is the single most common way a leboncoin dataset ends up with an unexplained €37.599 row in a phone price distribution
- **Deduplicated across pages.** Page 1 and page 2 of the same search overlap, because those promoted rows repeat. Rows are returned once
- **The full attribute bag the seller filled in**, keyed by the site's own field names, whatever the category. On a car that is make, model, trim, registration year, mileage, fuel, gearbox, power, import status and the vehicle history report link. On a flat it is the surface area, the room count and the energy rating
- **Mileage with its unit and price with its currency on every row.** Vehicle rows carry `vehicle_mileage` and `vehicle_mileage_unit` as separate fields, so the number joins cleanly against any other market without a units audit first
- **Professional sellers identified properly**, with the shop id and the French company registration number, so a dealer's whole inventory can be grouped and cross-checked against the company register
- **Location to coordinates**: town, postcode, department with its id, region with its id, latitude and longitude
- **Every photograph at full size**, not just the thumbnail
- **The filters the site says it applied**, reported in the run summary next to the ones you asked for, so a filter that quietly did nothing is visible instead of invisible
- **A refused run and an empty search are never the same answer.** One exits successfully saying nothing matched; the other says the site did not serve the page

### Why scrape leboncoin

leboncoin is where France buys and sells: **755.936 cars**, 877.772 properties for sale, 509.679 phones and connected devices, and over 23 million home and garden listings as this was written. For price research, sourcing, or tracking a dealer's stock in France, there is no second choice.

It also publishes more structure than most classifieds sites. Sellers fill in typed fields per category, and those fields come back with the row rather than having to be mined out of free text. A car ad arrives with its mileage as a number, its gearbox as a value and its registration year as a year, which is the difference between a dataset and a pile of French prose.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `query` | string | no | – | What to search for, as you would type it on the site |
| `category` | select | no | `all` | Cars, property, phones, fashion and 16 more |
| `url` | string | no | – | A leboncoin search URL to read instead of the fields below |
| `price_min` / `price_max` | integer | no | – | Price bounds in euros |
| `seller_type` | select | no | `any` | Private sellers, professionals, or both |
| `locations` | string | no | – | `d_75` for a department, `r_12` for a region |
| `sort` | select | no | `relevance` | Newest, oldest, price up, price down |
| `include_injected_ads` | boolean | no | `false` | Return the promoted rows too, flagged |
| `limit` | integer | no | `50` | Ads to return, 1 to 3500 |
| `retries` | integer | no | `5` | Retry attempts per page |

### Output

```json
{
  "position": 1,
  "list_id": "3270620909",
  "url": "https://www.leboncoin.fr/ad/voitures/3270620909",
  "title": "Golf GTD",
  "price": 14900.0,
  "price_currency": "EUR",
  "price_was": null,
  "category_id": "2",
  "category_name": "Voitures",
  "published_at": "2026-09-15 10:22:41",
  "is_injected_ad": false,
  "seller_type": "private",
  "seller_name": null,
  "seller_siren": null,
  "city": "Dax",
  "zipcode": "40100",
  "department": "Landes",
  "department_id": "40",
  "region": "Nouvelle-Aquitaine",
  "region_id": "2",
  "country": "FR",
  "latitude": 43.7102,
  "longitude": -1.0533,
  "images_count": 9,
  "vehicle_brand": "Volkswagen",
  "vehicle_model": "Golf",
  "vehicle_registration_year": "2016",
  "vehicle_mileage": 160000,
  "vehicle_mileage_unit": "km",
  "vehicle_fuel": "Diesel",
  "vehicle_gearbox": "Automatique",
  "vehicle_power_hp": "184 Ch",
  "attributes": { "brand": "Volkswagen", "regdate": "2016", "mileage": "160000 km", "...": "..." }
}
```

### Use cases

**Used-car pricing in France.** Set `category` to cars, add a price band, and every row comes back with make, model, registration year, mileage, fuel and gearbox as typed fields. That is a valuation dataset straight out of the run, and `seller_type` separates the private market from the trade market, which price differently for the same car.

**Tracking a dealership or a shop.** `seller_store_id` groups a professional's entire inventory and `seller_siren` ties it to the company register. Run it on a schedule and what arrives, what leaves and what is being discounted is visible without a single manual page view.

**Regional price differences.** `locations` takes a department or a region, and every row carries its own department, region and coordinates. Running the same query across departments gives you the geographic price surface for anything on the site.

**Property research.** Property ads carry their surface area, room count and energy rating in the attribute bag, with coordinates on the row. That is enough to build a per-square-metre map of a commune without a second data source.

**Clean price distributions.** Because the injected promoted rows are excluded by default, the prices you get are the prices of things that matched your search. On a phone search that removes two cars per page, which is the difference between a median that means something and one that does not.

### How it compares

| | this actor | `clearpath/leboncoin-api` | `silentflow/leboncoin-scraper-ppr` |
|---|---|---|---|
| Per 1.000 ads | **$1,10** | $1,49 | $1,50 |
| Actor-start fee | **$0,001** | $0,009 | $0,00005 |
| Injected adverts labelled | **yes** | not stated | not stated |
| Deduplicated across pages | **yes** | not stated | not stated |
| Full attribute bag per ad | **yes** | yes | not stated |
| Company registration number | **yes** | not stated | not stated |
| Coordinates on the row | **yes** | not stated | not stated |
| Applied-filter echo in the summary | **yes** | no | no |
| Detail pages and seller profiles | no | **yes** | no |

Honest about the other side: `clearpath` has 67 monthly users to our zero, a 28-input schema, and it also fetches ad detail pages and seller profiles, which this actor does not. If you need the full advert body and the seller's other listings, that is a real capability gap. What it does not tell you is which of its rows are promoted adverts rather than search results.

### Pricing

Two events. `ad` costs $0,0011 per advert returned, which is **$1,10 per 1.000**. A $0,001 start fee covers the run itself. All pricing is pay-per-event, so you pay for rows you actually receive, and a run the site does not serve bills the start fee only. No per-compute-unit charges.

### Limits and gotchas

- **3.500 ads per query.** The site itself caps a search at 100 pages of 35, and the run summary reports both how many ads matched in total and how many pages were read. For a bigger harvest, split the query by department or by price band.
- **`locations` needs a department or a region.** `d_75` and `r_12` work. A bare town name with a postcode is accepted by the site, echoed back as applied, and matches nothing; the actor rejects that shape rather than handing you an empty dataset.
- **Some filters the site accepts do nothing.** The shipping filter is one of them: it is echoed back as applied and leaves the result count unchanged. It is not offered here. The summary always reports the filter set the site said it applied, so you can check any filter you reach through a pasted URL.
- **A price of €1.000.000 is a real advert.** Sort by price descending on any category and you will find placeholder and joke prices near the top. They are the seller's numbers, not a parse error.
- **The attribute bag differs by category.** A car and a sofa do not carry the same keys. The vehicle fields are promoted onto the row only when the ad actually has them, so a phone never comes back with an empty mileage column that reads as an unknown odometer.
- **Prices are euros.** `price_currency` says so on every row rather than leaving you to infer it from the domain.
- **Ad bodies in the search result are short.** leboncoin returns a truncated body on result pages. For the full advert text you need the ad page, which this actor does not fetch.

### FAQ

**Can I scrape leboncoin without an API key?**
Yes. There is no credential field in the input.

**What is `is_injected_ad`?**
leboncoin injects two promoted adverts into every result page, and they frequently belong to a different category from the one you searched. This field marks them. They are left out by default, so the ads you receive are ads that matched.

**Why do I get fewer rows than pages times 35?**
Because pages overlap. The promoted rows repeat across pages, and rows are returned once each. The run summary reports how many pages were read and how many ads came back.

**Can I search one department only?**
Yes, with `locations` set to `d_` followed by the department number, for example `d_75` for Paris. A region works the same way with `r_`.

**How do I follow one professional seller?**
Group by `seller_store_id`, which is stable, and cross-check `seller_siren` against the French company register.

**What happens if a run returns nothing?**
The summary says which of three things happened: nothing matched, the location expression matched nothing, or the site did not serve the page. They are never reported the same way.

### Related Actors

- [Marktplaats Scraper](https://apify.com/s-r/marktplaats-scraper): the same job for the Netherlands
- [OLX Scraper](https://apify.com/s-r/olx-scraper): classifieds across Central and Eastern Europe
- [mobile.de Scraper](https://apify.com/s-r/mobile-de-scraper): the German vehicle marketplace

# Actor input Schema

## `query` (type: `string`):

What to search for, exactly as you would type it on the site, for example "iphone 15" or "golf gtd". Leave it empty to browse a whole category.

## `category` (type: `string`):

Restrict the search to one section of the site. Narrowing the category is also the cleanest way to keep off-topic results out of a broad keyword search. Every option here was checked by running it and reading the category back off the returned ads.

## `url` (type: `string`):

A leboncoin search URL to read instead of the fields below. Apply the filters you want on the site and paste the resulting address here; anything the site can filter on works this way.

## `price_min` (type: `integer`):

Lower price bound in euros. Every price on leboncoin is in euros.

## `price_max` (type: `integer`):

Upper price bound in euros.

## `seller_type` (type: `string`):

Private sellers, professional sellers, or both. Professionals carry a company registration number on the row.

## `locations` (type: `string`):

Where to search. Use a department as d\_75 or a region as r\_12, or copy a location expression out of a leboncoin search URL. A bare town name with a postcode is accepted by the site and matches nothing, so it is rejected here instead.

## `sort` (type: `string`):

Result order. Every option here was checked by reading the returned ads back, not just by the site accepting the parameter.

## `include_injected_ads` (type: `boolean`):

leboncoin drops two promoted adverts into every result page and they often have nothing to do with the search. They are excluded by default; switch this on to receive them, flagged in the is\_injected\_ad field.

## `limit` (type: `integer`):

How many ads to return, 1 to 3500. A page carries 35 and the site caps a search at 100 pages, so 3500 is the real ceiling for one query.

## `retries` (type: `integer`):

Retry attempts per page, each one a differently shaped client on a fresh route. Raise it if a run reports pages that were not served.

## Actor input object example

```json
{
  "query": "iphone 15",
  "category": "all",
  "url": "https://www.leboncoin.fr/recherche?text=golf&category=2&price=5000-15000",
  "seller_type": "any",
  "locations": "d_75",
  "sort": "relevance",
  "include_injected_ads": false,
  "limit": 50,
  "retries": 5
}
```

# Actor output Schema

## `results` (type: `string`):

One row per classified ad.

## `summary` (type: `string`):

Total matches the site reports, the professional and private split, the filters the site says it applied, how many injected adverts were seen, and whether the site served the search at all.

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

Per-page failures with a code and a redacted message.

# 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 = {
    "query": "iphone 15",
    "category": "all",
    "seller_type": "any",
    "sort": "relevance",
    "limit": 50,
    "retries": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/leboncoin-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 = {
    "query": "iphone 15",
    "category": "all",
    "seller_type": "any",
    "sort": "relevance",
    "limit": 50,
    "retries": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/leboncoin-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 '{
  "query": "iphone 15",
  "category": "all",
  "seller_type": "any",
  "sort": "relevance",
  "limit": 50,
  "retries": 5
}' |
apify call s-r/leboncoin-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/leboncoin-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/EtvxWSQK4hDyd6wBY/builds/uu3gaxe1NkqsIK6a3/openapi.json
