Fresha Scraper - Salons, Spas, Services & Reviews avatar

Fresha Scraper - Salons, Spas, Services & Reviews

Pricing

from $1.00 / 1,000 listing records

Go to Apify Store
Fresha Scraper - Salons, Spas, Services & Reviews

Fresha Scraper - Salons, Spas, Services & Reviews

Scrape Fresha salon and spa listings: business profiles, service menus with prices and durations, opening hours, team members, photos, ratings and client reviews. Search by keyword and location or paste venue and professional links. Resume, incremental updates and MCP export included.

Pricing

from $1.00 / 1,000 listing records

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

1

Bookmarked

2

Total users

1

Monthly active users

5 hours ago

Last modified

Categories

Share

Fresha Scraper: Salons, Spas, Services & Reviews

Fresha Scraper turns Fresha into a clean beauty-and-wellness data API. Get salon and spa listings with ratings, addresses and photos, full service menus with prices and durations, opening hours, team members, amenities and client reviews with salon replies. Search by keyword and city, or paste venue and professional profile links, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

Why This Scraper?

  • Two ways in. Search Fresha by keyword and location (hair in Sydney, massage in London, barber in New York), or paste venue, professional, SEO landing-page and book-now links for exact scraping. No URL building needed.
  • Deep service menus. Full service catalog with per-service prices, durations, variants and per-service ratings, plus package offers with included items.
  • Reviews with replies. Client reviews carry rating, text, date, author, review photos, the service and team member involved, and the venue reply - with the full 1-5 star distribution, Fresha's own review summary, and the most-reviewed services and team members per venue.
  • Lead-gen ready. Amenities, social links, website, phone, geo coordinates, servicing areas, open/closed status and a derived 0-100 lead score per listing.
  • Built for schedules. Incremental mode returns only new and changed listings on recurring runs, and a refused run fails loudly instead of returning an empty dataset.

Use Cases

  • Salon and spa lead generation: collect venues by city and service with phone numbers, addresses and contact details for outreach.
  • Market research and franchising: map competitor density, pricing ranges, opening hours and service breadth across cities.
  • Review intelligence: track ratings, star distributions, review trends and how venues reply to feedback.
  • Price benchmarking: compare service prices and durations across venues and markets.
  • AI agents and dashboards: feed structured listing and review data into your own tools.

Data You Get

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

FieldExample
recordType"venue" (also "professional")
name"Sample Hair Studio"
urlFresha link to the venue or professional profile
venueType"Hair Salon"
rating / reviewsCount5.0 / 625
ratingLevel / ratingTitle"HIGHLY_RECOMMENDED" / "Highly Recommended"
reviewRating1Total ... reviewRating5Total0 / 0 / 1 / 5 / 619
city / state / country / postalCode"Sydney" / "New South Wales" / "AU" / "2011"
street / fullAddress / mapsUrl"24-30 Sample Avenue" / full formatted address / Google Maps link
latitude / longitude-33.8688 / 151.2093
contactNumber"+61 400 000 000"
serviceCount / serviceCategoryCount55 / 9
servicesname, category, price, currency, duration, variants, per-service rating
packages / hasPackagespackage offers with included items / true
servicePriceMin / servicePriceMax / priceRange15 / 455 / "AUD 15 - 455"
workingHours / openingStatusper-day hours / "Closed" at scrape time
team / teamSizename, job title, rating, avatar, profile link / 4
amenities"Pet-friendly", "Wi-Fi", "Wheelchair accessible"
images / portfolioImagesvenue photo URLs / work portfolio photo URLs
instagramUsername / socialLinks / website / facebookUrl"samplehairstudio" / external links / venue website / Facebook page
hasGiftCards / hasVouchers / hasMemberships / hasFreshaPay / hasProductStoretrue / false / true / true / product store
reviewSummaryText / recentReviewersFresha's summary of what clients praise / recent reviewer names
reviews / topReviewedServices / reviewTeamMembersrating, text, date, author, reply, photos, service and team context (when enabled) / most-reviewed services and team
leadScore87.5 (derived 0-100 score)
searchQuery / searchLocation / searchSort / availabilityDate / discoveredFromUrlsearch context of the row / availability filter / harvest source (URL mode)

Professional records add headline, isVerified, appointmentsCompleted, clientsServed, favouritedBy, memberSince, languages, workingLocations and portfolioImages.

How to Use

  1. Pick a mode: Search by keyword & location or Scrape listing URLs.
  2. For search, fill in the keyword and city; for URLs, paste Fresha links - or leave the shipped example links in place for a first test run.
  3. Switch on Full profile enrichment and Collect client reviews as needed.
  4. Set Max items to control run size and cost, then click Start.
  5. Download the dataset as JSON, CSV or Excel, or read it through the API.

Search by keyword:

{
"mode": "SEARCH",
"searchQuery": "hair",
"searchLocation": "Sydney",
"maxItems": 20
}

Highest-rated massage venues only:

{
"mode": "SEARCH",
"searchQuery": "massage",
"searchLocation": "London",
"sort": "RATING",
"freshaVerifiedOnly": true,
"maxItems": 30
}

Paste links (venue pages, professional profiles, SEO landing pages and book-now links):

{
"mode": "LISTING_URLS",
"listingUrls": [
{ "url": "https://www.fresha.com/a/sample-hair-studio-sydney-example123" },
{ "url": "https://www.fresha.com/p/sample-stylist-1234567" },
{ "url": "https://www.fresha.com/lp/en/bt/spas/in/au-sydney" }
],
"fetchDetails": true,
"fetchReviews": true
}

SEO landing pages (/lp/...) are harvested for their venue links; book-now links resolve to their venue automatically.

Links of every supported shape can be mixed freely in listingUrls:

ShapeExampleBehavior
Venue pagehttps://www.fresha.com/a/rachel-french-hairdresser-sydney-24-30-springfield-avenue-u0by2m6fFull venue record.
Venue link with tracking parameterssame venue link + ?utm_source=...Parameters are ignored; matched as the same venue.
Professional profilehttps://www.fresha.com/p/suha-alsayah-5784524Professional record.
SEO landing pagehttps://www.fresha.com/lp/en/bt/spas/in/au-sydneyHarvested for the roughly 60 venue links it contains; badge placeholders filtered out; rows record the harvest source in discoveredFromUrl.
Localized venue link (short form)https://www.fresha.com/de/a/rachel-french-hairdresser-sydney-24-30-springfield-avenue-u0by2m6fParsed like the plain venue link. Region-qualified forms such as /de-DE/a/... also parse, but the site does not serve those pages, so use the short locale form.
Book-now linkhttps://www.fresha.com/book-now/...Resolves to its venue through the page's own embedded data.
Anything else (e.g. the home page)-Ignored. If nothing in the list matches a supported shape, the run logs a loud warning and ends cleanly with 0 records.

If you switch to URL mode and leave the list untouched, the shipped example links run - one per supported shape - so a first run works without editing anything.

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/fresha-salon-spa-scraper").call(run_input={"mode": "SEARCH", "searchQuery": "hair", "searchLocation": "Sydney", "maxItems": 10})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["name"], item["rating"], item["reviewsCount"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/fresha-salon-spa-scraper').call({ mode: 'SEARCH', searchQuery: 'hair', searchLocation: 'Sydney', maxItems: 10 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Search results come in pages of 20 listings, most relevant first (or by the sort you picked). A run stops once maxItems is reached, so a capped run returns the strongest matches for the keyword. To collect a whole city, raise maxItems or set it to 0, and optionally cap pages with maxPages.

Resume and recurring updates

  • Resume (resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor skips everything already collected there, so you don't pay twice.
  • Incremental mode (incrementalMode) is for scheduled runs over the same scope. Each listing is classified NEW, UPDATED (with changedFields), UNCHANGED (suppressed and not billed unless emitUnchanged is on), REAPPEARED (a listing previously marked EXPIRED that is found again, keeping its original firstSeenAt) or EXPIRED (only with emitExpired, and never billed past your maxItems). What counts as a change: every emitted field except a built-in ignore set and run bookkeeping (scrapedAt, changeType, changedFields, firstSeenAt, lastSeenAt) - e.g. the service menu, rating, review count, address and photos. Never compared, so they never mark a listing updated on their own: portfolio image galleries, recent reviewers, open/closed status and hours, free-running counters, and review summaries (top-reviewed services and team members); review order is ignored too - reviews compare as a set. Extend the ignore set with ignoreFieldsForChanges. stateKey names or shares the stored state. With incremental mode off, output is exactly as before.

Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. Notion gets a rich page-per-item export; other connectors get a best-effort write. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to the page where items should be created.

At most maxNotifyListings items (default 50, up to 1,000) are written to each connector per run; this cap never affects the dataset. The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modestringSEARCHSEARCH by keyword and location, or LISTING_URLS for pasted links.
searchQuerystringhairService or business keyword (search mode).
searchLocationstringSydneyCity or area, resolved to map coordinates automatically.
latitudestring(none)Optional map latitude; with longitude overrides the resolved location.
longitudestring(none)Optional map longitude; with latitude overrides the resolved location.
maxPagesintegerunlimitedResult pages (20 listings each) per search; blank or 0 = unlimited.
sortstringRECOMMENDEDRECOMMENDED, RATING or DISTANCE.
minPriceinteger(none)Minimum service price filter in the local currency.
maxPriceinteger(none)Maximum service price filter in the local currency.
hasDealsbooleanfalseOnly venues currently running deals.
hasGroupAppointmentsbooleanfalseOnly venues offering group appointments.
freshaVerifiedOnlybooleanfalseOnly Fresha Verified venues.
availabilityDatestring(none)Only venues with bookable availability on this date (YYYY-MM-DD).
listingUrlsarraysample linksFresha venue, professional, landing-page and book-now URLs (URL mode). Ships with one example link per supported shape; replace them with your own or leave them for a test run.
fetchDetailsbooleantrueFull profile: description, phone, hours, services, team, photos, amenities.
fetchReviewsbooleantrueClient reviews with replies, photos and the star distribution.
maxReviewsPerListinginteger20Stop collecting reviews per listing after this many; 0 = no limit.
reviewSortingstringLATESTLATEST, BEST, WORST or RELEVANCE.
maxItemsinteger20Stop after this many listings (0 = no limit).
proxyobjectApify datacenterConnection settings (see Plan Requirement).
resumeFromRunIdstring(none)Continue one interrupted run.
incrementalModebooleanfalseReturn only new and changed listings on scheduled runs.
stateKeystring(none)Name or share an incremental-mode state.
emitUnchangedbooleanfalseAlso return (and bill) unchanged listings.
emitExpiredbooleanfalseAlso return (and bill) expired listings.
ignoreFieldsForChangesarray(none)Extra fields that never mark a listing as updated.
mcpConnectorsarray(none)Optional: send a summary of each record to apps you authorized under Integrations.
notionParentPageUrlstring(none)Notion connector only: page under which records are created.
maxNotifyListingsinteger50Cap on items written to each connector per run.

Output Example

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

Venue listing:

{
"recordType": "venue",
"id": "235399",
"name": "Sample Hair Studio",
"url": "https://www.fresha.com/a/sample-hair-studio-sydney-example123",
"venueType": "Hair Salon",
"rating": 5.0,
"reviewsCount": 625,
"ratingLevel": "HIGHLY_RECOMMENDED",
"reviewRating5Total": 619,
"city": "Sydney",
"state": "New South Wales",
"country": "AU",
"postalCode": "2011",
"latitude": -33.8688,
"longitude": 151.2093,
"contactNumber": "+61 400 000 000",
"currency": "AUD",
"serviceCount": 55,
"servicePriceMin": 15,
"servicePriceMax": 455,
"priceRange": "AUD 15 - 455",
"workingHours": [{ "day": "Monday", "closed": false, "hours": ["9:00 AM - 6:00 PM"] }],
"amenities": ["Pet-friendly", "Wi-Fi"],
"team": [{ "name": "Rachel", "jobTitle": "Director", "rating": 5.0 }],
"services": [{ "name": "Cutting Packages", "price": 65, "currency": "AUD", "durationMin": 1800 }],
"reviews": [{ "rating": 5, "text": "Always great", "authorName": "Kate M", "replyText": "Thank you!" }],
"leadScore": 87.5,
"searchQuery": "hair",
"searchLocation": "Sydney",
"changeType": "NEW"
}

The Reviews output tab (one row per review) and the Full profiles tab (enriched listings) are available as dataset views.

Plan Requirement

The default connection (datacenter) works on every Apify plan and is the setting this actor ships with. For the most reliable results on large or frequent runs, switch the connection to Residential under Proxy & connection. If a connection is refused, the actor escalates automatically through backup connections, including residential ones; residential traffic is billed to your Apify plan when it engages. If every connection is refused, the run fails with a clear message instead of returning an empty result.

FAQ

How much does it cost?

You pay per listing returned, with optional profile enrichment and review collection billed only when you switch them on. The Pricing tab shows the current rates. Use Max items to cap the cost of any run.

This actor collects only publicly available business data: listing cards, service menus, opening hours and public reviews. You are responsible for how you use it: follow Fresha's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Business names, prices and public reviews are generally facts, but photos and texts may be subject to third-party rights.

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

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new and updated listings, and unchanged ones are not billed.

Why did a run return fewer listings than the search shows on the website?

The run stops at Max items, strongest matches first. Set it higher, or to 0, to collect more of the city. Reviews attached to a listing do not count toward maxItems.

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

If not a single page could be read during the run - every connection was refused or the data source did not answer - the run fails with a message describing the connection problem, so "no results" is never confused with "nothing could be read". On a free plan this can mean residential connections were unavailable; the failure message says which case applies and what to do. Run it again in a few minutes. If at least some pages were read, you get what was found: some listings, or - if the pages were read but none matched - an honest empty result.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

💬 Support & custom scrapers

  • 🐞 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 or message Telegram @abotapi.
  • ⭐ Enjoying it? A quick review on the actor page helps other users find it.