Planity Scraper - Beauty Salons, Services, Prices & Reviews avatar

Planity Scraper - Beauty Salons, Services, Prices & Reviews

Pricing

from $1.60 / 1,000 salon records

Go to Apify Store
Planity Scraper - Beauty Salons, Services, Prices & Reviews

Planity Scraper - Beauty Salons, Services, Prices & Reviews

Scrape Planity salons across France, Germany and Belgium by city, service or URL. Extract service menus, prices, durations, photos, opening hours, staff, ratings and client reviews. Supports salon and search URLs, resume, incremental updates and MCP export.

Pricing

from $1.60 / 1,000 salon records

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 hours ago

Last modified

Categories

Features

Limited permissions

Share

Planity Scraper: Salons, Services, Prices & Reviews

Scrape Planity, Europe's beauty and wellness booking platform: hair salons, barbers, nail studios, spas and beauty institutes across France, Germany and Belgium. Each salon record carries its contact block, rating summary, photo gallery, opening hours, written description and nested client reviews, plus the full service menu with prices and durations from the salon's own page. Search by plain city and service names, or paste salon and results links, and keep scheduled runs incremental so only new or changed salons come back.

Why This Scraper?

  • Three ways in. Plain-language city and service search ("Paris" + "Manucure"), pasted salon or results links, or a mix of both on any of the three countries.
  • Service menus with real prices. Every bookable service variant ships with its price, duration, deposit mode and cancellation fees, read from the salon's own page.
  • Nested reviews. Client reviews (rating, date, text) attach to the salon record as a nested list, up to 20 per salon; an Output tab unwinds them to one row per review.
  • Full photo galleries and team lists. Salon photo sets and staff names, roles and photos ship on profile-enriched rows.
  • Recurring monitoring built in. Incremental mode classifies NEW / UPDATED / UNCHANGED / EXPIRED against a saved baseline; booking-availability churn never bills a phantom update, service-price moves do.
  • Three countries, one actor. France, Germany and Belgium share one input: pick the country for search mode, or let each pasted link pick its own from its locale prefix.
  • Send results anywhere. MCP connectors pipe collected salons into Notion, Airtable, Linear or any MCP-connected app.

Use Cases

  • Beauty suppliers and brands: build and refresh lists of salons per city and service, with phone numbers and review volumes for territory planning.
  • Price monitoring: track service menus of competitor salons on a schedule and get UPDATED rows only when a price or duration actually moved.
  • Market research: compare rating distributions, review counts and photo depth across cities and service categories.
  • Review analysis: collect client review text per salon for sentiment or quality scoring.
  • Lead generation: export enriched salon profiles (contacts, hours, staff, legal identifiers where published) into your CRM.

Data You Get

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

FieldExample
nameSample Salon
urlhttps://www.planity.com/sample-salon-75001-paris
salonId00000000-0000-0000-0000-000000000000
categories["Hair salon"]
address1, Sample Street
cityParis
postalCode75001
phoneNumber+330000000000
emailsalon@example.com
rating4.85
reviewsCount478
ratingAmbiance4.87
serviceCount42
priceFrom25
priceTo180
imageUrlhttps://mms.cdn.global.prodsharedsvc.planityapp.com/planity/image/upload/t_d_main,f_auto/00000000
imageUrls["https://mms.cdn.global.prodsharedsvc.planityapp.com/planity/image/upload/t_d_main,f_auto/00000000"]
photosCount11
openingHoursTextMonday: 10:00-19:00; ...; Sunday: closed
openHours[{"day": "Monday", "status": "open", "spans": [{"openFrom": "10:00", "openTill": "19:00"}]}]
acceptsGiftVoucherfalse
acceptsGiftVoucherfalse
staff[{"name": "Sample Name", "title": "Colour specialist", "pictureUrl": "https://..."}]
staffCount6

Each salon row is one dataset record; client reviews nest inside it (the Client reviews tab unwinds them), and the Service menus tab unwinds every bookable service variant to one row.

How to Use

  1. Pick a mode: search by city and service, or paste links.
  2. Fill the mode's fields: cities (plus optional service, keyword, sort and minimum rating) for search mode; salon or results links for URL mode.
  3. Set Max items to bound the run; keep Fetch salon profiles on for service menus and reviews.
  4. Run the actor and export the dataset, or connect an MCP connector to pipe results into your apps.

Search Paris barbers:

{
"mode": "search",
"market": "fr",
"cities": ["Paris"],
"service": "Barbier",
"maxItems": 20
}

Track service prices in Lyon incrementally:

{
"mode": "search",
"cities": ["Lyon"],
"service": "Coiffeur",
"maxItems": 100,
"fetchDetails": true,
"incrementalMode": true,
"stateKey": "lyon-coiffeur-watch"
}

Read specific salons (any country, locale prefix decides):

{
"mode": "urls",
"urls": [
"https://www.planity.com/y-coiffure-75004-paris",
"https://www.planity.com/de-DE/ck-hair-atelier-58511-ludenscheid"
],
"fetchDetails": true,
"maxReviews": 20
}

Walk a results link:

{
"mode": "urls",
"urls": ["https://www.planity.com/coiffeur/paris-75"],
"maxItems": 200
}

Run it from your code

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("abotapi/planity-salon-scraper").call(run_input={
"mode": "search",
"cities": ["Paris"],
"service": "Barbier",
"maxItems": 20,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["name"], item.get("priceFrom"))
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('abotapi/planity-salon-scraper').call({
mode: 'search',
cities: ['Paris'],
service: 'Barbier',
maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((i) => [i.name, i.priceFrom]));

You can also connect the actor through Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Card vs profile fields

Every row ships the catalogue card: name, address, city, phone, rating and review counts, opening hours, photo gallery, the written description, booking-feature flags and the salon's recent reviews. With Fetch salon profiles on (the default), the salon's own page adds the service menu with prices and durations, staff, sub-ratings, access notes, opening-hours exceptions and legal identifiers where the salon publishes them.

Resume and recurring updates

Incremental mode saves a baseline (keyed to the cities, service, country, links and toggles you ran with) and classifies each salon on the next run: NEW, UPDATED, UNCHANGED or EXPIRED. Fields that move without the salon changing (booking-slot availability, appointment counters, the review list itself) are excluded from change detection; review counters and ratings stay tracked, and any service-price or duration move bills the row UPDATED. Unchanged salons are suppressed (and never charged) unless Emit unchanged records is on. Resume from run ID treats a previous run's records as already collected.

Send results into your apps (MCP connectors)

Optionally send collected salons into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector under Apify, then select it in the mcpConnectors input: Notion gets a rich page-per-salon export; other connectors get a best-effort write/digest. Set notionParentPageUrl for Notion and maxNotifyListings to cap the export per connector. Leave the input empty to skip: the export never changes the dataset output, and the connector receives a condensed, human-readable summary per salon (the complete record always stays in the Apify dataset). Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

Input Parameters

ParameterTypeDefaultDescription
modeselectsearchSearch by city and service, or paste links.
marketselectfrCountry catalogue (France, Germany, Belgium), used by both modes: search mode runs on it, and a pasted link without a locale prefix falls back to it (a /de-DE/-style prefix picks its own country).
citiesstringList["Paris"]Search mode only. Plain city names (Paris, Lyon, Berlin, Bruxelles).
servicetextCoiffeurSearch mode only. Plain service name narrowing the feed server-side (Coiffeur, Barbier, Manucure, Spa, Massage). Empty searches every service. Pasted results links carry their own service in the link path.
searchQuerytext(empty)Keyword matched against salon names and descriptions; both modes (search and pasted results-page links).
sortByselectrelevanceResults order: recommended, rating, price low-first, price high-first; both modes (search and pasted results-page links).
minRatingnumber(empty)Keep only salons rated at least this many stars; both modes (pasted salon links are always read in full).
urlsstringList(7 example links)Salon links (read in full), service-and-city results links (walked forward) or bare salon slugs; one example of every supported shape.
fetchDetailscheckboxtrueRead each salon's own page for the service menu, staff, sub-ratings, hours and legal identifiers.
fetchReviewscheckboxtrueAttach client reviews to each salon as a nested list. Reviews ride pages this run already reads; no extra requests.
maxReviewsnumber20Upper bound on reviews per salon (the pages carry up to 20). 0 skips review collection.
maxItemsnumber20Hard cap on salon records for the whole run.
maxPagesnumber0Bounds results pages per city walk; 0 defers to Max items.
proxyConfigurationproxyResidential, FRResidential exits serve every salon page reliably; the exit country follows the searched country unless pinned here.
resumeFromRunIdtext(empty)A previous run's ID whose records are treated as already collected.
incrementalModecheckboxfalseClassify NEW / UPDATED / UNCHANGED / EXPIRED against the saved baseline.
stateKeytext(empty)Baseline name; empty derives it from the run's scope.
emitUnchangedcheckboxfalseAlso push salons whose tracked fields did not change.
emitExpiredcheckboxfalseAfter a complete scan, push baseline salons this run did not see.
ignoreFieldsForChangesstringList(empty)Extra fields excluded from change detection beyond the built-in volatile set.
mcpConnectorsstringList(empty)Connector IDs to export collected salons to.
notionParentPageUrltext(empty)Parent page URL for the Notion connector.
maxNotifyListingsnumber50Upper bound on records handed to each connector.

Output Example

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

{
"recordId": "planity:fr:00000000-0000-0000-0000-000000000000",
"kind": "salon",
"salonId": "00000000-0000-0000-0000-000000000000",
"slug": "sample-salon-75001-paris",
"name": "Sample Salon",
"url": "https://www.planity.com/sample-salon-75001-paris",
"market": "fr",
"country": "France",
"currency": "EUR",
"categories": ["Hair salon"],
"address": "1, Sample Street",
"city": "Paris",
"postalCode": "75001",
"latitude": 48.86,
"longitude": 2.35,
"phoneNumber": "+330000000000",
"rating": 4.85,
"reviewsCount": 478,
"openingHoursText": "Monday: 10:00-19:00; Tuesday: 10:00-19:00; Wednesday: 10:00-19:00; Thursday: 10:00-20:00; Friday: 10:00-21:00; Saturday: 10:00-19:00; Sunday: closed",
"hasAvailabilities": true,
"imageUrl": "https://mms.cdn.global.prodsharedsvc.planityapp.com/planity/image/upload/t_d_main,f_auto/00000000-0000-0000-0000-000000000000",
"imageUrls": ["https://mms.cdn.global.prodsharedsvc.planityapp.com/planity/image/upload/t_d_main,f_auto/00000000-0000-0000-0000-000000000000"],
"photosCount": 11,
"services": [
{
"groupName": "Cut",
"name": "Sample cut and style",
"durationMin": 30,
"price": 35,
"bookable": true
}
],
"serviceCount": 42,
"priceFrom": 25,
"priceTo": 180,
"staff": [{"name": "Sample Name", "title": "Colour specialist"}],
"staffCount": 6,
"reviews": [
{"reviewRating": 5, "reviewDate": "2026-01-01", "reviewText": "Sample review text"}
],
"reviewsDelivered": 20,
"reviewsCollected": true,
"detailFetched": true,
"sourceMode": "search",
"scrapedAt": "2026-01-01T00:00:00+00:00"
}

Plan Requirement

This actor is pay-per-event: a start event per run, a per-record event for each salon returned, and enrichment events when profile pages were read or reviews collected. See the Pricing tab for the current per-event rates; Max items bounds how many records a run can return.

FAQ

How much does it cost to run?

You pay per event: one start event per run, one record event per salon returned, and the enrichment events only for salons whose profile page was actually read or whose reviews were actually collected. Suppressed unchanged salons in incremental mode bill nothing. See the Pricing tab for the current rates, and keep Max items in mind when scheduling.

This actor collects only publicly available salon information. You remain responsible for using the data in line with the site's terms of use, privacy law such as GDPR, and your own applicable regulations; personal data (staff names, review authors) carries a lawful-basis requirement on your side.

Can I get only new or changed salons on a schedule?

Yes: run with incrementalMode on and a fixed stateKey. The first run bootstraps the baseline; later runs push only NEW and UPDATED salons (plus EXPIRED tombstones when Emit expired is on and the scan was complete). A service-price move bills the row UPDATED with changedFields naming the moved fields.

Why did a run return fewer results than the city's total?

Max items bounds the run, and each city walk gets an equal share; salons also publish at different depths (some hide prices or durations, and preview listings without live menus are excluded). Raise Max items or walk again to page deeper.

Why did my run fail instead of returning an empty dataset?

When every request this run made was refused, the run fails loudly with a connection-problem message instead of returning an empty dataset: an empty result should mean an empty search, never a blocked page. Re-run shortly; the connections recover on their own.

Can I use it with AI agents or MCP?

Yes. The actor ships an MCP connector surface: select a connector in mcpConnectors and each collected salon is also piped to that app as a condensed summary. The full records stay in the dataset for API consumption by any agent.

🔗 More scrapers you might like

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

🧩 Booksy Scraper
Scrape Booksy beauty businesses across the US, UK and Poland by category, location or...
🧩 Fresha Scraper
Scrape Fresha salon and spa listings: business profiles, service menus with prices and...
🛒 Wolt Scraper
Scrape Wolt restaurants and full menus by city or URL. Extract 60+ fields including...
🛒 DoorDash Scraper
Extract structured doordash.com data at scale. Search by keyword or paste store URLs to...
📇 GoWork Scraper
Extract company profiles from GoWork France and Germany, including contact details...
📇 PagesJaunes Scraper
Scrape PagesJaunes.fr (French Yellow Pages) business listings across France. Extract...

👉 Browse all abotapi scrapers

💬 Support

  • 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. 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.