# PagesJaunes Scraper — French Business Directory, per result (`diopside/pagesjaunes-businesses`) Actor

Business listings from pagesjaunes.fr: name, address, activities, rating, plus phone, SIRET, NAF code, GPS and opening hours from the professional's own page. Drop-in compatible with memo23/pagesjaunes-scraper-cheerio.

- **URL**: https://apify.com/diopside/pagesjaunes-businesses.md
- **Developed by:** [DIOPSIDE AI](https://apify.com/diopside) (community)
- **Categories:** Business, Lead generation, Other
- **Security:** Limited permissions
- **Stats:** 4 total users, 2 monthly users, 68.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 business with details

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

## PagesJaunes Scraper — French business directory, per result

**Drop-in compatible with `memo23/pagesjaunes-scraper-cheerio` and `saswave/pagesjaunes-scraper` — same
input fields, same output column names, cheaper per result, and pagination that actually paginates.**

Search [pagesjaunes.fr](https://www.pagesjaunes.fr) by activity and town and get every business as a
row: name, address, activity, services, ratings — plus phone, website, GPS coordinates, SIRET, SIREN,
NAF code, legal form, incorporation date, opening hours and payment methods taken from each
professional's own page.

No login, no API key, no cookies to supply. You pay per business, not per page or per hour.

### Why this one

**1. It does not silently re-scrape page 1.** PagesJaunes has two search surfaces. The query route
(`/annuaire/chercherlespros?quoiqui=…&ou=…`) honours `?page=N`; the SEO route
(`/annuaire/paris-75/restaurant`) does **not** — asking it for `?page=3` returns page 1 again, with
16 of the 20 results identical and the footer still reading "Page 1 / 500". A scraper that builds its
own page URLs therefore bills you repeatedly for the same twenty businesses and calls the run a
success. This actor follows PagesJaunes' own "Suivant" link instead (which carries a per-search
`contexte` token that cannot be constructed), and refuses to push a page whose footer did not
advance — it stops the target and says so in the run status message.

**2. Official registry data, not just contact details.** Every enriched record carries the SIRET,
SIREN, NAF code, legal form, establishment type (head office or branch) and incorporation date that
PagesJaunes sources from INSEE's Sirene base — so a lead list is also a company list you can join to
French open data.

**3. A Google rating never lands in the PagesJaunes rating column.** PagesJaunes shows a third-party
score next to its own for about half of listings. Blending the two quietly changes what your
pipeline is averaging, so they stay in separate columns (`basicInfo_place_rating` /
`partnerRating`).

**4. Failures are reported, never hidden.** A blocked page is retried on a fresh exit IP; a target
that still fails is named in the status message while the businesses already collected are kept.
A run only fails when it produced nothing.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array | — | pagesjaunes.fr URLs. A search listing (`/annuaire/paris-75/restaurant` or `/annuaire/chercherlespros?quoiqui=plombier&ou=Paris`) or one professional's page (`/pros/64217247`). |
| `searchTerm` | string | — | Activity or trade (`plombier`, `restaurant`). Used when no `startUrls` are given. |
| `location` | string | — | Town, department or postcode (`Paris`, `Lyon`, `69003`). |
| `maxItems` | integer | `100` | Stop after this many businesses. `0` scrapes every page. |
| `extractDetails` | boolean | `true` | Open each business's `/pros/` page for phone, website, GPS, SIRET, NAF, opening hours and payment methods. |
| `proxyConfiguration` | object | RESIDENTIAL / FR | See *Proxy* below. |
| `maxConcurrency` | integer | `4` | Pages fetched at the same time. |
| `maxRequestsPerMinute` | integer | `90` | Request rate ceiling. |
| `maxRequestRetries` | integer | `4` | Retries on a fresh exit IP before a target is given up on. |

`url`, `pros_urls` (saswave) and `proxy` (memo23) are accepted as aliases, so an input written for
either incumbent runs here unchanged.

### Output

One record per business. Column names in the first group match
`memo23/pagesjaunes-scraper-cheerio` exactly; the rest are additions.

```json
{
  "code_etablissement": "64217247",
  "denomination": "SOS Plomberie 92",
  "telephone": "+33 7 52 63 50 52",
  "email": null,
  "site_web": "https://sosplomberie92.fr/",
  "adresse_full_long": "10 Marceau 92130 Issy les Moulineaux",
  "street": "10 Marceau",
  "postalCode": "92130",
  "city": "Issy les Moulineaux",
  "country": "FR",
  "latitude": 48.82664,
  "longitude": 2.27191,
  "siret": "48390376100060",
  "siren": "483903761",
  "codeNaf": "4322A",
  "formeJuridique": "Entrepreneur individuel",
  "typologieEtablissement": "Siège",
  "creationEntreprise": "10 octobre 2005",
  "activite_libelle": "dépannage plomberie",
  "prestations": ["dépannage de chauffe-eau", "détection de fuite d'eau"],
  "description": "15 ans d'expérience dans le métier. Devis gratuit, transparent.",
  "basicInfo_place_rating": 5,
  "basicInfo_place_nb_review": 6,
  "partnerRating": 5,
  "partnerReviewCount": 26,
  "partnerSource": "Google",
  "openingHours": [{ "day": "Monday", "opens": "08:00", "closes": "20:00" }],
  "paymentAccepted": ["CB", "Espèces", "Chèque"],
  "images": ["https://www.pagesjaunes.fr/media/newdam/…/photo.jpg"],
  "url": "https://www.pagesjaunes.fr/pros/64217247",
  "searchUrl": "https://www.pagesjaunes.fr/annuaire/chercherlespros?quoiqui=plombier&ou=Paris",
  "searchPage": 1,
  "position": 1,
  "hasDetails": true,
  "scrapedAt": "2026-09-20T08:26:03+00:00"
}
```

Every record carries every column, so a CSV export never shifts. `hasDetails` tells you whether a
null phone means "the business publishes none" or "not looked up".

**`email` is always null.** PagesJaunes does not publish contact emails, and this actor does not go
hunting for them on the businesses' own websites. The column exists so a dataset from another
PagesJaunes actor keeps the same shape.

### Sizes and speed

A search returns 20 businesses per page. `plombier` in Paris is 24 pages (~480 businesses);
`restaurant` in Paris is capped at 500 pages (~10,000). With `extractDetails` on, expect roughly
25 businesses per 25 seconds at the default concurrency; with it off, 20 per request.

### Proxy

**French residential IPs are required.** PagesJaunes sits behind a Cloudflare *managed challenge*
that scores the exit IP, not just the TLS fingerprint. Measured on the Apify platform on
2026-09-20, three attempts against each of three routes per tier:

| Tier | Result |
|---|---|
| No proxy (raw Apify egress) | 0/9 — 403 + Cloudflare interstitial on every request |
| Apify datacenter, country FR | 0/9 — no French datacenter IPs available |
| Apify **RESIDENTIAL, country FR** | 6/9, and all three misses were the proxy failing to allocate an exit IP, not Cloudflare |

The default `proxyConfiguration` is RESIDENTIAL/FR and you should leave it there. Running with the
proxy off is not a cheaper mode — it is a failed run, and the actor says so before it starts.

### Pricing

| Event | Price | What you get |
|---|---|---|
| Actor start | $0.00005 | Once per run, per GB of memory. |
| **Business** | **$0.0009** | One search-result business: name, address, activity, services, description, both ratings, URL. |
| **Business with details** | **$0.0014** | The above plus phone, website, GPS, SIRET, SIREN, NAF code, legal form, incorporation date, opening hours, payment methods, photos. |

Two prices because the two records cost very different amounts to produce: a listing record is one
twentieth of a search page, an enriched one adds a whole extra page of residential traffic. Set
`extractDetails: false` for a fast name-and-address sweep at the lower price.

### Use cases

- **Local lead lists** — every plumber, restaurant or bakery in a town with phone, website and
  opening hours, ready for a CRM import.
- **Company data enrichment** — join your French customer list to SIRET/SIREN/NAF straight from the
  directory, without a separate registry lookup.
- **Local SEO and competitive audits** — track who ranks where for an activity in a town over time;
  `searchPage` and `position` are on every record.
- **Store locators and maps** — GPS coordinates and structured addresses for every business in a
  category.

### Switching from another PagesJaunes actor

Change the actor id. Your input keeps working: `startUrls` (memo23), `url` and `pros_urls`
(saswave) and `proxy` (memo23) are all accepted, and `maxItems`, `maxConcurrency`,
`minConcurrency` and `maxRequestRetries` mean what they did before.

Two differences to know about:

- `email` is always null here (see above). If you depended on the incumbent's email enrichment,
  this actor does not replace it.
- Set `proxyConfiguration` to RESIDENTIAL/FR — or just delete your proxy setting and take the
  default. An input that said `{"useApifyProxy": false}` will not get through Cloudflare.

### Notes

Only public directory pages are read, over the routes `robots.txt` allows: search listings and each
professional's own `/pros/` page. The phone number comes from the schema.org block PagesJaunes
publishes on that page for search engines — not from the `/annuaire/ajax/` endpoint its
"Afficher le N°" button calls, which `robots.txt` disallows. Nothing from PagesBlanches (the
residential directory) is scraped.

# Actor input Schema

## `startUrls` (type: `array`):

pagesjaunes.fr URLs to scrape. Either a search listing (`https://www.pagesjaunes.fr/annuaire/paris-75/restaurant` or `https://www.pagesjaunes.fr/annuaire/chercherlespros?quoiqui=plombier&ou=Paris`) or a single professional's page (`https://www.pagesjaunes.fr/pros/64217247`). Leave empty and use Search term + Location instead if you prefer.

## `searchTerm` (type: `string`):

Activity, trade or business name to search for, exactly as you would type it on pagesjaunes.fr — e.g. `plombier`, `restaurant`, `boulangerie`. Ignored when Start URLs are given.

## `location` (type: `string`):

Town, department or postcode — e.g. `Paris`, `Lyon`, `69003`. Ignored when Start URLs are given.

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

Stop after this many businesses per run. Set to 0 to scrape every page of every start URL.

## `extractDetails` (type: `boolean`):

Open each business's own `/pros/` page to add phone, website, GPS coordinates, SIRET, SIREN, NAF code, legal form, opening hours and accepted payment methods. Costs one extra request per business and is billed as an enriched result. Turn off for a fast, cheap name + address + rating sweep.

## `proxyConfiguration` (type: `object`):

pagesjaunes.fr is behind a Cloudflare managed challenge that scores the exit IP, so French residential IPs are required — measured on the Apify platform, the raw egress IP and the datacenter pool passed 0 of 9 requests while RESIDENTIAL/FR passed every request that got an exit IP. Leave this at the default unless you are supplying your own French residential proxies.

## `maxConcurrency` (type: `integer`):

Maximum number of pages fetched at the same time.

## `minConcurrency` (type: `integer`):

Accepted for compatibility with other PagesJaunes actors. This actor paces itself with Max requests per minute instead, so the value is not used.

## `maxRequestsPerMinute` (type: `integer`):

Request rate ceiling. Lower it if you see challenges in the log.

## `maxRequestRetries` (type: `integer`):

How many times a blocked or failed page is retried on a fresh exit IP before the target is given up on.

## `url` (type: `array`):

Accepted so an input written for saswave/pagesjaunes-scraper runs unchanged. Same meaning as Start URLs.

## `pros_urls` (type: `array`):

Accepted so an input written for saswave/pagesjaunes-scraper runs unchanged. Same meaning as Start URLs.

## `proxy` (type: `object`):

Accepted so an input written for memo23/pagesjaunes-scraper-cheerio runs unchanged. Used only when Proxy configuration is not set.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.pagesjaunes.fr/annuaire/chercherlespros?quoiqui=plombier&ou=Paris"
  ],
  "maxItems": 100,
  "extractDetails": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "FR"
  },
  "maxConcurrency": 4,
  "minConcurrency": 1,
  "maxRequestsPerMinute": 90,
  "maxRequestRetries": 4
}
```

# Actor output Schema

## `businesses` (type: `string`):

All business records. Append ?format=csv for CSV.

## `datasetUrl` (type: `string`):

The default dataset.

# 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 = {
    "startUrls": [
        "https://www.pagesjaunes.fr/annuaire/chercherlespros?quoiqui=plombier&ou=Paris"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "FR"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("diopside/pagesjaunes-businesses").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 = {
    "startUrls": ["https://www.pagesjaunes.fr/annuaire/chercherlespros?quoiqui=plombier&ou=Paris"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "FR",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("diopside/pagesjaunes-businesses").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 '{
  "startUrls": [
    "https://www.pagesjaunes.fr/annuaire/chercherlespros?quoiqui=plombier&ou=Paris"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "FR"
  }
}' |
apify call diopside/pagesjaunes-businesses --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,diopside/pagesjaunes-businesses"
        }
    }
}
```

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/ggYEF2cabticZ7D2v/builds/hNftnWrMdd86JfNPw/openapi.json
