Fresha Scraper - Salons, Spas, Services & Reviews
Pricing
from $1.00 / 1,000 listing records
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
Maintained by CommunityActor 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.
| Field | Example |
|---|---|
recordType | "venue" (also "professional") |
name | "Sample Hair Studio" |
url | Fresha link to the venue or professional profile |
venueType | "Hair Salon" |
rating / reviewsCount | 5.0 / 625 |
ratingLevel / ratingTitle | "HIGHLY_RECOMMENDED" / "Highly Recommended" |
reviewRating1Total ... reviewRating5Total | 0 / 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 / serviceCategoryCount | 55 / 9 |
services | name, category, price, currency, duration, variants, per-service rating |
packages / hasPackages | package offers with included items / true |
servicePriceMin / servicePriceMax / priceRange | 15 / 455 / "AUD 15 - 455" |
workingHours / openingStatus | per-day hours / "Closed" at scrape time |
team / teamSize | name, job title, rating, avatar, profile link / 4 |
amenities | "Pet-friendly", "Wi-Fi", "Wheelchair accessible" |
images / portfolioImages | venue photo URLs / work portfolio photo URLs |
instagramUsername / socialLinks / website / facebookUrl | "samplehairstudio" / external links / venue website / Facebook page |
hasGiftCards / hasVouchers / hasMemberships / hasFreshaPay / hasProductStore | true / false / true / true / product store |
reviewSummaryText / recentReviewers | Fresha's summary of what clients praise / recent reviewer names |
reviews / topReviewedServices / reviewTeamMembers | rating, text, date, author, reply, photos, service and team context (when enabled) / most-reviewed services and team |
leadScore | 87.5 (derived 0-100 score) |
searchQuery / searchLocation / searchSort / availabilityDate / discoveredFromUrl | search 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
- Pick a mode:
Search by keyword & locationorScrape listing URLs. - 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.
- Switch on Full profile enrichment and Collect client reviews as needed.
- Set Max items to control run size and cost, then click Start.
- 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.
URL mode: supported link shapes
Links of every supported shape can be mixed freely in listingUrls:
| Shape | Example | Behavior |
|---|---|---|
| Venue page | https://www.fresha.com/a/rachel-french-hairdresser-sydney-24-30-springfield-avenue-u0by2m6f | Full venue record. |
| Venue link with tracking parameters | same venue link + ?utm_source=... | Parameters are ignored; matched as the same venue. |
| Professional profile | https://www.fresha.com/p/suha-alsayah-5784524 | Professional record. |
| SEO landing page | https://www.fresha.com/lp/en/bt/spas/in/au-sydney | Harvested 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-u0by2m6f | Parsed 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 link | https://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 ApifyClientclient = 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.
How maxItems shapes a search
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 classifiedNEW,UPDATED(withchangedFields),UNCHANGED(suppressed and not billed unlessemitUnchangedis on),REAPPEARED(a listing previously markedEXPIREDthat is found again, keeping its originalfirstSeenAt) orEXPIRED(only withemitExpired, and never billed past yourmaxItems). 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 withignoreFieldsForChanges.stateKeynames 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.
- Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto 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
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | SEARCH | SEARCH by keyword and location, or LISTING_URLS for pasted links. |
searchQuery | string | hair | Service or business keyword (search mode). |
searchLocation | string | Sydney | City or area, resolved to map coordinates automatically. |
latitude | string | (none) | Optional map latitude; with longitude overrides the resolved location. |
longitude | string | (none) | Optional map longitude; with latitude overrides the resolved location. |
maxPages | integer | unlimited | Result pages (20 listings each) per search; blank or 0 = unlimited. |
sort | string | RECOMMENDED | RECOMMENDED, RATING or DISTANCE. |
minPrice | integer | (none) | Minimum service price filter in the local currency. |
maxPrice | integer | (none) | Maximum service price filter in the local currency. |
hasDeals | boolean | false | Only venues currently running deals. |
hasGroupAppointments | boolean | false | Only venues offering group appointments. |
freshaVerifiedOnly | boolean | false | Only Fresha Verified venues. |
availabilityDate | string | (none) | Only venues with bookable availability on this date (YYYY-MM-DD). |
listingUrls | array | sample links | Fresha 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. |
fetchDetails | boolean | true | Full profile: description, phone, hours, services, team, photos, amenities. |
fetchReviews | boolean | true | Client reviews with replies, photos and the star distribution. |
maxReviewsPerListing | integer | 20 | Stop collecting reviews per listing after this many; 0 = no limit. |
reviewSorting | string | LATEST | LATEST, BEST, WORST or RELEVANCE. |
maxItems | integer | 20 | Stop after this many listings (0 = no limit). |
proxy | object | Apify datacenter | Connection settings (see Plan Requirement). |
resumeFromRunId | string | (none) | Continue one interrupted run. |
incrementalMode | boolean | false | Return only new and changed listings on scheduled runs. |
stateKey | string | (none) | Name or share an incremental-mode state. |
emitUnchanged | boolean | false | Also return (and bill) unchanged listings. |
emitExpired | boolean | false | Also return (and bill) expired listings. |
ignoreFieldsForChanges | array | (none) | Extra fields that never mark a listing as updated. |
mcpConnectors | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. |
notionParentPageUrl | string | (none) | Notion connector only: page under which records are created. |
maxNotifyListings | integer | 50 | Cap 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.
Is it legal to scrape Fresha?
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.