# Doctolib · Practitioners by Specialty & City (`corent1robert/doctolib-practitioner-scraper`) Actor

Export public Doctolib practitioners in France, Germany and Italy. Pick a specialty and city — name, phone, address, RPPS. About $2 per 1,000 rows. No login. No API key.

- **URL**: https://apify.com/corent1robert/doctolib-practitioner-scraper.md
- **Developed by:** [Corentin Robert](https://apify.com/corent1robert) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 95.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 practitioner profiles

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#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

## Doctolib · Practitioners by Specialty & City

Pick a **specialty** and a **city**. Get one row per public Doctolib practitioner: **name**, **phone** when listed, **address**, specialty, RPPS / ADELI when shown, profile URL.

**No login. No API key. No Doctolib account.** About **$2 per 1,000** rows on Free. France, Germany, and Italy.

### Who is this for?

| You are… | Typical goal | Suggested setup |
|---|---|---|
| Medical / dental supplier | City call list | Specialty **Dentist** + **Paris** · Max rows **50** first |
| Healthcare SDR / outbound | Phones for a territory | Same · export **Outreach / CRM** |
| Agency / CRM ops | Refresh links you already stored | **Profile URLs** mode |
| Market research | Size a specialty in a city | Keep Search · raise Max rows or set **0** |
| DE / IT expansion | Same job outside France | Country **Germany** or **Italy** · matching city |

**What you get by default:** name (split first/last when listed), landline, street, city, specialty, **RPPS** on French directory pages, profile URL and photo. **Email is almost never public** — this Actor does not guess mailboxes. Languages, diplomas, fees, GPS and cabinet name stay `null` unless Doctolib already put them in the first HTML (most profiles hydrate those in the browser).

**Fill rate (cloud, 14 Paris dentists, phone required, August 2026):** 14/14 name · phone · address · city · photo · **13/14 RPPS and first/last name** · 13/14 specialty · 1/14 presentation · 0/14 languages, diplomas, fees, GPS, website. One profile was skipped (no public phone).

**When to paste profile URLs:** you already have Doctolib links in a spreadsheet and only need a refresh.

### Quick start

1. Keep **Search** mode.
2. Choose **Country**, a **specialty**, and a **city**.
3. Leave **Max rows** at **50** for a first look.
4. Click **Start**. Open the **Outreach / CRM** dataset view.

Set **Max rows** to **0** when you want the full city list (every public profile for that specialty in that city). Optional: paste a Doctolib search URL if the city is not in the list.

#### Example — all gynaecologists in Paris

Specialty **Gynaecologist**, city **Paris, France**, **Max rows = 0**. The Actor walks the public directory until it runs out of matching profiles (not a 50-row sample). Related French slugs are included (`gynecologue`, obstetrics, medical / surgical gynae). Expect hundreds of rows and about **$2 per 1,000** on Free. Turn **only rows with a phone** off if you also want profiles without a landline.

### Ready-made examples (published tasks)

| Example | Best for |
|---------|----------|
| [Export dentists in Paris from Doctolib](https://apify.com/corent1robert/doctolib-practitioner-scraper/examples/paris-dentists-doctolib) | France dental outreach |
| [Find GPs in Lyon on Doctolib](https://apify.com/corent1robert/doctolib-practitioner-scraper/examples/lyon-gps-doctolib) | City GP list |
| [Export dentists in Berlin from Doctolib](https://apify.com/corent1robert/doctolib-practitioner-scraper/examples/berlin-dentists-doctolib) | Germany (`doctolib.de`) |
| [Enrich Doctolib profile URLs you already have](https://apify.com/corent1robert/doctolib-practitioner-scraper/examples/enrich-doctolib-profile-urls) | CRM refresh |

These landing pages are on the Actor **Examples** tab after publication.

### What it extracts

| Field | What it is |
|-------|------------|
| `fullName` / `title` / `firstName` / `lastName` | Display name, `Dr` when listed, split identity |
| `specialityName` | Specialty label |
| `phone` / `phoneE164` | Public landline (national) and E.164 (`+33…`) |
| `fullAddress` | One line: street, postcode city, country |
| `address` / `zipcode` / `city` / `country` | Split location. `country` is ISO2 (`FR`, `DE`, `IT`) |
| `rpps` / `adeli` / `lanr` / `siren` | Legal IDs when published |
| `practiceName` | Cabinet / clinic name when listed |
| `latitude` / `longitude` / `website` | Rare on first HTML |
| `presentation` / `languagesSpoken` / `diplomas` / `experience` | Profile sections when present in HTML |
| `feesSummary` / `regulationSector` | Indicative fees and convention when listed |
| `sourceUrl` / `imageUrl` | Canonical profile and photo |

Empty values are `null`, never `"-"`.

### How much does it cost to scrape Doctolib?

Cloud runs use a **French (or DE/IT) residential proxy** by default. A cheap datacenter proxy is blocked by Doctolib’s directory. Proxy traffic is billed by Apify on top of PPE.

**HTTP-only** — default **1024 MB**, no browser. You pay per **exported practitioner row**. Skipped pages are not billed.

Store comps (August 2026): other Doctolib Actors range from about **$1.50 / 1k** to **$7–$40 / 1k**. This Actor is **$2 / 1k** on Free — same band as OneDoc.ch and local.ch in this toolkit.

| | Free | Bronze | Silver | Gold | Platinum | Diamond |
|---|---|---|---|---|---|---|
| Actor start | $0.00005 | $0.00005 | $0.00005 | $0.00005 | $0.00005 | $0.00005 |
| Practitioner profile | **$0.002** | $0.0018 | $0.0016 | $0.0014 | $0.0012 | $0.0012 |
| 50 profiles | **~$0.10** | ~$0.09 | ~$0.08 | ~$0.07 | ~$0.06 | ~$0.06 |
| 1,000 practitioners | **$2** | $1.80 | $1.60 | $1.40 | $1.20 | $1.20 |

#### Is scraping Doctolib free?

A 50-row test is about **$0.10** on Free. National (**All cities**) exports can be tens of thousands of rows — keep **Max rows** low on the first run.

### Input

| Field | Default | Notes |
|---|---|---|
| `mode` | `search` | City list (`search`) or CRM links (`practitionerUrls`) |
| `specialty` | `dentiste` | 40+ specialties in the list, or `custom` + `customSpecialtySlug` |
| `place` | `fr:paris` | `fr:paris`, `de:berlin`, `it:milano`, `fr:all`, or `custom` + `customCity` |
| `maxItems` | `0` in API · Console prefill **50** | `0` = no row ceiling |
| `requirePhone` | `true` | Skip profiles with no public phone |
| `searchUrls` | empty | Optional extra / custom search pages |
| `practitionerUrls` | empty | Only for **Profile URLs** mode |

**API-only** (accepted in JSON, not shown on the Console form):

| Field | Default | Notes |
|---|---|---|
| `verboseLogs` | `false` | Extra debug lines |
| `proxyConfiguration` | Residential, country matched to the city | Override via API |
| `maxConcurrency` | `8` | Parallel profile fetches |
| `datasetFormat` | `client` | `full` adds internal IDs |
| `specialtySlugs` | — | Extra directory slugs (legacy) |

#### Example — Paris dentists

```json
{
  "mode": "search",
  "specialty": "dentiste",
  "place": "fr:paris",
  "maxItems": 50,
  "requirePhone": true
}
```

#### Example — Berlin dentists

```json
{
  "mode": "search",
  "specialty": "dentiste",
  "place": "de:berlin",
  "maxItems": 50,
  "requirePhone": true
}
```

#### Example — profile URLs

```json
{
  "mode": "practitionerUrls",
  "practitionerUrls": [
    { "url": "https://www.doctolib.fr/dentiste/paris/leslie-sultan" }
  ]
}
```

### Output example

```json
{
  "fullName": "Dr Leslie Sultan",
  "title": "Dr",
  "firstName": "Leslie",
  "lastName": "Sultan",
  "specialityName": "Chirurgien-dentiste",
  "rpps": "10003489860",
  "phone": "01 42 60 43 11",
  "phoneE164": "+33142604311",
  "fullAddress": "12 Rue d'Alger, 75001 Paris, France",
  "address": "12 Rue d'Alger",
  "zipcode": "75001",
  "city": "Paris",
  "country": "FR",
  "sourceUrl": "https://www.doctolib.fr/dentiste/paris/leslie-sultan"
}
```

Phone and legal IDs are only present when Doctolib shows them on the public page.

### How it works

1. You pick specialty + city (or paste a search / profile URL).
2. The Actor matches public Doctolib **directory** profile URLs for that specialty and city.
3. Each profile page is parsed from **HTML** (no Doctolib account, no private API).
4. Rows are written as they complete.

### Is it legal to scrape Doctolib?

This Actor only reads **public** practitioner directory pages. Health-related personal data is sensitive: you must have a lawful basis (GDPR and local rules) for your use case. Respect Doctolib’s terms and keep concurrency reasonable. This is not legal advice.

### Also available

- **[OneDoc.ch · Swiss Practitioners by Specialty & City](https://apify.com/corent1robert/onedoc-dentist-scraper)** — Swiss doctors and dentists, phone and email when published.
- **[local.ch scraper](https://apify.com/corent1robert/local-ch-scraper)** — Swiss business directory leads.

### Local development

```bash
cd doctolib-practitioner-scraper
npm install
apify run
```

`storage/key_value_stores/default/INPUT.json` (or `.actor/INPUT.json`) must satisfy the input schema. Local datasets stay in `storage/` and are also written to `output.csv`. They are **not** uploaded to Apify Console.

### Support

Contact <corentin@outreacher.fr> if you need a custom scraper or a tailored export.

# Actor input Schema

## `mode` (type: `string`):

**City list** — specialty + city, then public profiles.

**CRM links** — enrich Doctolib URLs you already have (no directory search).

## `specialty` (type: `string`):

Who you want on the list. **Gynaecologist** also includes related French directory paths (obstetrics, medical and surgical gynae). Anything missing → **Other** and paste the Doctolib URL slug.

## `customSpecialtySlug` (type: `string`):

Only if Specialty is **Other**. Copy the word from the Doctolib URL, e.g. `implantologue`.

## `place` (type: `string`):

Where to collect. France first. **All of France** is a large export — keep Max rows at 50 for a first test.

## `customCity` (type: `string`):

Only if City is **Other**. Use the city word from the Doctolib URL, e.g. `boulogne-billancourt`. Also set Market below.

## `country` (type: `string`):

Ignored when you pick a named city above. Used with **Other city slug**.

## `requirePhone` (type: `boolean`):

Skip profiles with no landline on the public page. Recommended for cold call / CRM lists.

## `maxItems` (type: `integer`):

Stop after this many practitioners. Keep **50** for a first test. **0** = every matching profile in that city (example: all gynaecologists in Paris). You pay per exported row.

## `searchUrls` (type: `array`):

One URL per line, e.g. `https://www.doctolib.fr/dentiste/paris`. Added on top of specialty + city.

## `practitionerUrls` (type: `array`):

One practitioner page per line. Classic Apify format also works: `[{ "url": "https://..." }]`.

## Actor input object example

```json
{
  "mode": "search",
  "specialty": "dentiste",
  "place": "fr:paris",
  "country": "fr",
  "requirePhone": true,
  "maxItems": 50,
  "practitionerUrls": []
}
```

# Actor output Schema

## `outreachView` (type: `string`):

Name, phone (national + E.164), one-line address, RPPS — ready for CRM import

## `practitionersView` (type: `string`):

Public profile fields including presentation, RPPS and fees when listed

## `fullDataset` (type: `string`):

All rows

## `runLog` (type: `string`):

Live progress: discovery, counts, and duration

# 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 = {
    "mode": "search",
    "specialty": "dentiste",
    "place": "fr:paris",
    "requirePhone": true,
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("corent1robert/doctolib-practitioner-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 = {
    "mode": "search",
    "specialty": "dentiste",
    "place": "fr:paris",
    "requirePhone": True,
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("corent1robert/doctolib-practitioner-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 '{
  "mode": "search",
  "specialty": "dentiste",
  "place": "fr:paris",
  "requirePhone": true,
  "maxItems": 50
}' |
apify call corent1robert/doctolib-practitioner-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,corent1robert/doctolib-practitioner-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/BZgkTY5FfhT0chn5u/builds/CySfgUXaIBB4M9XU2/openapi.json
