# Planity Scraper - Beauty Salons, Contacts & Reviews (`abotapi/planity-scraper`) Actor

Scrape Planity (planity.com) beauty salons across France, including hairdressers, barbers, nail salons and spas. Extract business names, addresses, phone numbers, opening hours, ratings, customer reviews, photos and booking availability signals for lead generation and market research.

- **URL**: https://apify.com/abotapi/planity-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 salon results

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?

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

## Planity Scraper

Collect salon and wellness venue listings from Planity (planity.com) across France: hairdressers, barbers, nail studios, beauty salons, spas, massage, tattoo, fitness and more. Search by city, district or postal code, or paste Planity links, and get one clean record per venue.

### Why This Scraper?

- **Rich records.** Name, full address, coordinates, phone, opening hours (also by day), photos, ratings and review excerpts in every row.
- **Services and team.** With details on, each salon carries its full service menu with prices and durations, its team, an about text, sub-ratings (welcome, hygiene, ambiance, service) and access information.
- **Two ways in, every link shape.** Search by plain city name, arrondissement or postal code, or paste listing, page, district, service-page or salon links.
- **Booking signals.** Online booking availability, appointment and customer counters, and price display settings.
- **Category coverage.** Fourteen venue categories, from hairdressers to fitness.
- **Change tracking.** Schedule recurring runs and receive only new or updated venues.
- **Safe caps.** One clear item cap, with resume support for interrupted runs.

### Use Cases

- **Lead generation:** build contact lists of venues by city and category.
- **Market research:** compare venue density, ratings and review volume across districts.
- **Market monitoring:** track new venues and profile changes over time.
- **Location analytics:** map venues using exact coordinates.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `name` | `Example Salon` |
| `url` | `planity.com/example-salon-00000-paris` |
| `main_type` | `hair_care` |
| `street` | `1, Rue Exemple` |
| `city` | `Paris` |
| `postal_code` | `00000` |
| `phone` | `+33000000000` |
| `latitude` | `48.85` |
| `longitude` | `2.35` |
| `opening_hours` | `Mo 10:00-19:00;Tu 10:00-19:00` |
| `rating` | `4.8` |
| `ratings_count` | `100` |
| `reviews_detail` | `Review excerpts (author, date, text, rating)` |
| `reviewsCount_pushed` | `10` |
| `services` | `Service menu with price and duration` |
| `team` | `Team members` |
| `rating_details` | `Welcome, hygiene, ambiance, service ratings` |
| `opening_hours_by_day` | `Hours per weekday` |
| `photos` | `Photo URLs` |
| `pictures` | `Photo list` |
| `has_availabilities` | `1` |
| `description` | `Venue presentation` |
| `language` | `fr` |
| `object_id` | `0000--ExampleId` |

### How to Use

1. Pick a mode: search by location, or paste links.
2. Set the category, filters and the maximum number of items.
3. Run the actor.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search by keyword:**

```json
{ "mode": "search", "locations": ["Lyon"], "category": "barbier", "maxItems": 50 }
```

**Paste links:**

```json
{ "mode": "url", "urls": ["https://www.planity.com/coiffeur/paris-75", "https://www.planity.com/coiffeur/paris-75/page-2", "https://www.planity.com/coiffeur/paris-75/75011-paris-11eme", "https://www.planity.com/coiffeur/coupe-femme/paris-75", "https://www.planity.com/y-coiffure-75004-paris"], "maxItems": 100 }
```

Links that are not a listing or a salon (home page, a category page without a place, other Planity sites) are skipped with a warning.

**Recurring monitoring:**

```json
{ "mode": "search", "locations": ["Paris"], "incrementalMode": true, "maxItems": 500 }
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<ACTOR_ID>").call(run_input={"mode": "search", "locations": ["Paris"], "maxItems": 20})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["name"], item["city"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('<ACTOR_ID>').call({ mode: 'search', locations: ['Paris'], maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length);
```

#### Tips

- Use a district such as Lyon 6eme or a postal code to narrow a large city.
- Turn off Add salon details for the quickest runs when you only need the core fields.
- Set Max reviews per salon to 0 to skip review excerpts.

### Send results into your apps (MCP connectors)

Optionally send a condensed summary of the results into Notion, Linear, Airtable or Apify through Model Context Protocol connectors. Authorize a connector once in your Apify integrations settings, then select it in the input. The full records always stay in the dataset.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | search | Search by location or paste links. |
| `locations` | array | Paris | Plain names: cities, arrondissements (Paris 15) or postal codes. |
| `category` | string | coiffeur | Venue category. |
| `searchText` | string | empty | Optional name filter. |
| `minRating` | number | empty | Minimum rating from 0 to 5. |
| `sortBy` | string | relevance | Relevance, best rated, price low to high or high to low. |
| `urls` | array | sample links | Planity listing or venue links. |
| `fetchDetails` | boolean | true | Adds venue page details: services, team, photos, about, sub-ratings, review excerpts. |
| `maxReviews` | integer | 20 | Review excerpts kept per salon; 0 skips them. |
| `maxPages` | integer | empty | Pages per search; does not cap the run on its own. |
| `maxItems` | integer | 20 | Maximum results; 0 means unlimited. |
| `resumeFromRunId` | string | empty | Skip venues already collected by an earlier run. |
| `incrementalMode` | boolean | false | Return only new or updated venues. |
| `stateKey` | string | empty | Name a monitoring campaign. |
| `ignoreFieldsForChanges` | array | empty | Extra fields that never mark a venue as updated. |
| `proxy` | object | Apify Residential proxy | Connection settings. Residential is the default and recommended for the most reliable results. |
| `mcpConnectors` | array | empty | Connectors to send results to. |
| `notionParentPageUrl` | string | empty | Notion parent page. |
| `maxNotifyListings` | integer | 50 | Items exported per connector. |

#### Connection and plans

Residential is the default and is recommended for the most reliable results; it is billed by traffic on your account. The run tries a cheaper connection first and keeps it while pages load. You can pick the datacenter group in the proxy settings.

If the default connection is refused, the run switches to Apify residential and then to a backup connection on its own, and says so in the log. Apify free plans do not include residential proxies: on a free plan the run continues on a backup connection, and the log shows a short notice. Some pages may still be unavailable there. For residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs.

If you choose residential, the run first tries the cheaper default connection and keeps using it for as long as the site answers.

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "name": "Example Salon",
  "url": "https://www.planity.com/example-salon-00000-paris",
  "main_type": "hair_care",
  "street": "1, Rue Exemple",
  "city": "Paris",
  "postal_code": "00000",
  "phone": "+33000000000",
  "latitude": 48.85,
  "longitude": 2.35,
  "opening_hours": "Mo 10:00-19:00;Tu 10:00-19:00",
  "rating": 4.8,
  "ratings_count": 100,
  "has_availabilities": 1,
  "language": "fr"
}
```

### Plan Requirement

The actor runs on any Apify plan that can run actors. Billing follows the pricing shown on the Store page.

### FAQ

#### How much does a run cost?

You pay per run start, per venue returned and, when details are on, per enriched venue. Review excerpts are counted in blocks of 10: every started block of 10 delivered reviews (Max reviews per salon, 20 by default) adds one more detail unit to that venue, and a venue with no details or no reviews adds none. Set Max items and Max reviews per salon to control the total.

#### Is it legal to scrape Planity?

The actor reads publicly visible venue information. You are responsible for using the data in line with applicable law and the site terms, including data protection rules.

#### Can I schedule runs?

Yes. Use Incremental mode on a schedule to receive only changes.

#### Why did a run return nothing?

The location may not match a known place (an entry that is not an exact city, district or postal-code match is skipped with a warning, never guessed), or the filters may be too strict. Try a larger city or relax the filters.

#### Can I use it with AI tools?

Yes, through the Apify MCP server or the connectors above.

### 🔗 More scrapers you might like

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🧩 <a href="https://apify.com/abotapi/fresha-salon-spa-scraper"><b>Fresha Scraper</b></a><br>Scrape Fresha salon and spa listings: salon profiles, service menus with prices and...</td><td>🧩 <a href="https://apify.com/abotapi/booksy-beauty-business-scraper"><b>Booksy Scraper</b></a><br>Scrape Booksy beauty businesses across the US, UK and Poland by category, location or...</td></tr>
<tr><td>🛒 <a href="https://apify.com/abotapi/wolt-restaurants-scraper"><b>Wolt Scraper</b></a><br>Scrape Wolt restaurants and full menus by city or URL. Extract 60+ fields including...</td><td>🧩 <a href="https://apify.com/abotapi/fever-com-scraper"><b>Fever Scraper</b></a><br>Scrape Fever events by city, category, keyword or URL. Extract from-prices, venues, dates...</td></tr>
<tr><td>📇 <a href="https://apify.com/abotapi/pagesjaunes-fr-scraper"><b>PagesJaunes Scraper</b></a><br>Scrape PagesJaunes.fr (French Yellow Pages) directory listings across France. Extract...</td><td>📇 <a href="https://apify.com/abotapi/yellow-nz-scraper"><b>Yellow Pages NZ</b></a><br>Scrapes directory listings from Yellow.co.nz (New Zealand Yellow Pages). Extract...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/planity-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

Search by location and category, or paste Planity listing and salon links. Fields of the other mode are ignored.

## `locations` (type: `array`):

Plain names, no ids needed: a city (Paris, Lyon), an arrondissement (Paris 15, Lyon 6eme) or a postal code (33000). One search per entry. An entry that matches no Planity place exactly is skipped with a warning, never guessed.

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

Type of establishment to list.

## `searchText` (type: `string`):

Optional text to match against salon names and descriptions.

## `minRating` (type: `number`):

Only keep salons rated at least this value (0 to 5). Leave empty for no filter.

## `sortBy` (type: `string`):

Order of the results. Relevance ranks by distance to the place centre; the other options order the whole place (best rated, or by price level).

## `urls` (type: `array`):

Planity links, one per line. Accepted: a listing (planity.com/coiffeur/paris-75), a listing page (…/page-2), a district listing (…/coiffeur/paris-75/75011-paris-11eme), a service page (…/coiffeur/coupe-femme/paris-75, lists the whole category for that place) or a single salon link. Query strings and a /fr-FR/ prefix are ignored; home pages and category pages without a place are skipped with a warning. Filter fields are ignored in this mode.

## `fetchDetails` (type: `boolean`):

Adds the salon page data: services with prices and durations, team, photos, about text, opening hours by day, sub-ratings, access info, SIRET and review excerpts. Billed as one detail-enrichment unit per enriched result, plus one more unit per started block of 10 delivered reviews (Max reviews per salon).

## `maxReviews` (type: `integer`):

How many review excerpts to keep on each salon row (0 skips them). Applies when Add salon details is on. Every started block of 10 delivered reviews adds one detail-enrichment unit.

## `maxPages` (type: `integer`):

Does not cap the run: it has no default and defers to Max items, which is the only default cap. Leave empty or 0 to walk every result page (20 results per page); set it only to stop a search earlier.

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

The one default cap on the run (20). Maximum results to return across all searches. 0 means unlimited.

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

Proxy settings. Residential is the default and recommended for the most reliable results; it is billed by traffic on your account. If the selected connection is refused, the run switches to a backup connection on its own.

## `resumeFromRunId` (type: `string`):

Run ID (or dataset ID) of a previous run of this Actor. Salons already collected there are skipped, so this run returns only the delta. Leave empty for a normal run. For recurring monitoring of the same search, use Incremental mode instead.

## `incrementalMode` (type: `boolean`):

For recurring monitoring. A salon is returned as NEW the first time, and as UPDATED when a tracked field changes (name, description, phone, address, opening hours, categories, photos, services and prices, team, booking settings). Unchanged salons are not returned or billed. Rating and review counters are not compared. State is kept per search setup, or per State key when you set one.

## `stateKey` (type: `string`):

Optional name for a monitoring campaign. Runs sharing a key share their incremental state. Leave empty to scope it automatically to the search.

## `ignoreFieldsForChanges` (type: `array`):

Extra field names that must not mark a venue as UPDATED in incremental mode. Rating, review and booking counters are already ignored.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify Settings, Integrations, then select it here. The connector receives a condensed summary per item; the full record stays in the dataset. Leave empty to skip.

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "locations": [
    "Paris"
  ],
  "category": "coiffeur",
  "sortBy": "relevance",
  "urls": [
    "https://www.planity.com/coiffeur/paris-75",
    "https://www.planity.com/coiffeur/paris-75/page-2",
    "https://www.planity.com/coiffeur/paris-75/75011-paris-11eme",
    "https://www.planity.com/coiffeur/coupe-femme/paris-75",
    "https://www.planity.com/barbier/lyon-69",
    "https://www.planity.com/y-coiffure-75004-paris"
  ],
  "fetchDetails": true,
  "maxReviews": 20,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

# 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",
    "locations": [
        "Paris"
    ],
    "category": "coiffeur",
    "urls": [
        "https://www.planity.com/coiffeur/paris-75",
        "https://www.planity.com/coiffeur/paris-75/page-2",
        "https://www.planity.com/coiffeur/paris-75/75011-paris-11eme",
        "https://www.planity.com/coiffeur/coupe-femme/paris-75",
        "https://www.planity.com/barbier/lyon-69",
        "https://www.planity.com/y-coiffure-75004-paris"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "incrementalMode": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/planity-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",
    "locations": ["Paris"],
    "category": "coiffeur",
    "urls": [
        "https://www.planity.com/coiffeur/paris-75",
        "https://www.planity.com/coiffeur/paris-75/page-2",
        "https://www.planity.com/coiffeur/paris-75/75011-paris-11eme",
        "https://www.planity.com/coiffeur/coupe-femme/paris-75",
        "https://www.planity.com/barbier/lyon-69",
        "https://www.planity.com/y-coiffure-75004-paris",
    ],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "incrementalMode": False,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/planity-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",
  "locations": [
    "Paris"
  ],
  "category": "coiffeur",
  "urls": [
    "https://www.planity.com/coiffeur/paris-75",
    "https://www.planity.com/coiffeur/paris-75/page-2",
    "https://www.planity.com/coiffeur/paris-75/75011-paris-11eme",
    "https://www.planity.com/coiffeur/coupe-femme/paris-75",
    "https://www.planity.com/barbier/lyon-69",
    "https://www.planity.com/y-coiffure-75004-paris"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false
}' |
apify call abotapi/planity-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/planity-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/n8VvfoBx01CtKTAhP/builds/cse0kaiya4GYTJGuS/openapi.json
