Fresha Scraper at a glance
Fresha Scraper is a ready-to-run Apify actor from abotapi for collecting web data. Its output documents 61 fields, including recordtype, id, name, url and venuetype, which you can export as JSON, CSV or Excel or read through the Apify API. It costs $1.80 per 1,000 results on the Apify Free plan and from $1.00 on paid plans, with smaller per-run or optional feature charges listed on Apify.
- Runs on
- Apify cloud: console, API, schedules and integrations
- Data type
- Web data
- Pricing
- $1.80 per 1,000 results (Free plan), from $1.00 on paid plans
- Output
- 61 documented fields · JSON, CSV, Excel
- Inputs
- 28 options
What Fresha Scraper can do
- 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.
Choose what to collect
These inputs let you configure Fresha Scraper - Salons, Spas, Services & Reviews. Open the actor on Apify to enter your values and review the current options.
| Input | What it controls |
|---|---|
modestring · required | SEARCH scrapes Fresha listings for a keyword and location. LISTING_URLS scrapes the exact venue pages (fresha.com/a/...) and professional profiles (fresha.com/p/...) you paste. |
searchQuerystring | Service or business keyword, e.g. hair, massage, nails, barber, facial, tattoo. Used in SEARCH mode. |
searchLocationstring | City or area name, e.g. Sydney, London, New York. Resolved automatically to map coordinates. Leave blank when latitude/longitude or a venue URL list is provided. |
latitudestring | Optional map latitude. When set together with longitude it overrides the resolved Location. |
longitudestring | Optional map longitude. When set together with latitude it overrides the resolved Location. |
maxPagesinteger | How many result pages (20 listings each) to walk per search. Leave blank or 0 for unlimited until Max items is reached. |
sortstring | Ordering of search results. RATING and DISTANCE are narrowed assertions: results come back in that order. |
Show 21 more inputs
| Input | What it controls |
|---|---|
minPriceinteger | Optional minimum service price filter in the local currency of the search area. |
maxPriceinteger | Optional maximum service price filter in the local currency of the search area. |
hasDealsboolean | Return only venues currently running deals or offers. |
hasGroupAppointmentsboolean | Return only venues offering group appointments or classes. |
freshaVerifiedOnlyboolean | Return only venues carrying the Fresha Verified badge. |
availabilityDatestring | Only venues with bookable availability on this date (YYYY-MM-DD). Used in SEARCH mode. |
listingUrlsarray | Fresha URLs to scrape in LISTING_URLS mode. Supported shapes, which can be mixed freely: venue pages (fresha.com/a/...), professional profiles (fresha.com/p/...), SEO landing pages (fresha.com/lp/..., harvested for the venue links they contain), localized venue links in the short locale form (e.g. fresha.com/de/a/...), and venue links carrying tracking parameters (?utm_source=..., fbclid=..., matched the same as clean venue links). Book-now links (fresha.com/book-now/...) also resolve to their venue when encountered. The URL mode is selected by Mode above. |
fetchDetailsboolean | Fetch each listing's full profile: description, contact number, opening hours, service menu with prices and durations, packages, team members, photo gallery, Instagram, amenities and review highlights. Adds the detail-enrichment charge per listing. Without it you still get the listing card: name, rating, review count, badges, address, geo and photos. |
fetchReviewsboolean | Attach client reviews to each listing: rating, text, date, author, salon reply and review photos, plus the 1-5 star distribution. Adds the review-enrichment charge per listing. First page is included; deeper pages are walked automatically. |
maxReviewsPerListinginteger | Stop collecting reviews for a listing after this many. 0 means no limit (walk every available page). |
reviewSortingstring | Order in which reviews are collected. |
maxItemsinteger | Stop after this many listing records (reviews attached to a record do not count as items). Run stops gracefully at the cap. |
resumeFromRunIdstring | ID of an interrupted run to continue. The run picks up after the last listing already saved and reuses its key-value state. Leave empty for a fresh run. |
incrementalModeboolean | Compare listings against the previous run for the same scope (state key) and only emit NEW or UPDATED rows (plus optional EXPIRED tombstones). Unchanged listings are skipped and not charged. A listing previously marked EXPIRED that is found again is emitted as REAPPEARED with its original firstSeenAt. What drives UPDATED: every emitted listing field except the exclusions below and run bookkeeping (scrapedAt, changeType, changedFields, firstSeenAt, lastSeenAt) - e.g. the services menu (names, prices, durations), rating, reviewsCount, address and coordinates, images, amenities, social links. Never compared, so they never mark a row UPDATED on their own (they rotate or change on their own without the listing changing): portfolioImages, recentReviewers, openingStatus, openingStatusDetails, closed, workingHours, instagramMediaCount, groupingScore, topReviewedServices, reviewTeamMembers. Review order is ignored too: reviews are compared as an orderless set of id, rating, text, date and reply. Extend the exclusions with ignoreFieldsForChanges. |
stateKeystring | Name of the remembered baseline for incremental mode. Defaults to a hash of the current scope (mode, query, location, URL set), so two runs with the same settings compare against each other automatically. Change it to track a different baseline. |
emitUnchangedboolean | In incremental mode, also emit rows classified UNCHANGED (handy for full snapshots or debugging). Unchanged rows still carry changeType so you can filter. |
emitExpiredboolean | In incremental mode, emit an EXPIRED row for listings present in the baseline but missing from this run. Tombstones respect Max items. |
ignoreFieldsForChangesarray | Extra dataset fields that must NOT mark a record UPDATED when they change (rotation noise like image CDNs). Sensible defaults are built in; anything added here is appended. |
mcpConnectorsarray | Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify → Settings → API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write/digest. Leave empty to skip; never changes the dataset output. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com). |
notionParentPageUrlstring | URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors. |
maxNotifyListingsinteger | Cap on items written to each connector per run. Does not affect the dataset. |
Fields in the output
The documented dataset includes the fields below. Availability can depend on the source page and selected mode.
recordTyperecordTypeididnamenameurlurlvenueTypevenueTyperatingratingreviewsCountreviewsCountcitycitycountrycountryamenitiesamenitiesserviceCountserviceCountpriceRangepriceRangeShow 49 more fields
featuredfeaturedisNewisNewhasDealshasDealsisFreshaVerifiedisFreshaVerifiedsearchQuerysearchQuerysearchLocationsearchLocationchangeTypechangeTypescrapedAtscrapedAtdescriptiondescriptioncontactNumbercontactNumbercurrencycurrencyserviceCategoriesserviceCategoriesservicesservicespackagespackageshasPackageshasPackagesservicePriceMinservicePriceMinservicePriceMaxservicePriceMaxdurationMindurationMindurationMaxdurationMaxteamSizeteamSizeteamteamworkingHoursworkingHoursopeningStatusopeningStatusfullAddressfullAddresslatitudelatitudelongitudelongitudeimagesimagesportfolioImagesportfolioImagesinstagramUsernameinstagramUsernamehasGiftCardshasGiftCardshasVouchershasVouchershasMembershipshasMembershipshasFreshaPayhasFreshaPayhasProductStorehasProductStorewebsitewebsitefacebookUrlfacebookUrlreviewRating1TotalreviewRating1TotalreviewRating2TotalreviewRating2TotalreviewRating3TotalreviewRating3TotalreviewRating4TotalreviewRating4TotalreviewRating5TotalreviewRating5TotalreviewSummaryTextreviewSummaryTextrecentReviewersrecentReviewerstopReviewedServicestopReviewedServicesreviewTeamMembersreviewTeamMembersdetailFetcheddetailFetchedchangedFieldschangedFieldsfirstSeenAtfirstSeenAtlastSeenAtlastSeenAtExample output
An example from this actor’s documentation. Values are illustrative; this is not a fresh live result.
{
"recordType": "venue",
"id": "235399",
"name": "Sample Hair Studio",
"url": "https://www.fresha.com/a/sample-hair-studio-sydney-example123",
"venueType": "Hair Salon",
"rating": 5,
"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
}
],
"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"
}Ways to use this data
- 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.
Before you run
Start with a small input and check the resulting records against your expected fields. Source content and actor options can change; the current Apify listing is the reference for availability and pricing.
Questions about this actor
What data does Fresha Scraper return?
Fresha Scraper returns 61 documented fields, including recordtype, id, name, url, venuetype, rating, reviewscount and city. Availability of each field depends on the source page and the selected mode.
How much does Fresha Scraper cost?
It costs $1.80 per 1,000 results on the Apify Free plan and from $1.00 on paid plans, with smaller per-run or optional feature charges listed on Apify. The Apify listing shows the current rate for every plan.
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.
