kestrel actors docs

kestrel actors › Guest & customer reviews

Zoover Reviews Scraper — Hotel Guest Reviews & Ratings

This Zoover reviews scraper turns any accommodation page on Zoover.nl into structured rows: one per guest review, with the 1-10 score, the title, the full text, the per-aspect scores, the travel party, the travel date, the reviewer and the owner's reply. As a hotel reviews scraper it reads Zoover hotel reviews, camping reviews, bungalow-park reviews and apartment reviews the same way, so you can pull guest reviews for a whole portfolio in one run. No browser, no login, no Zoover api key — just accommodation URLs or ids in, hotel reviews out, billed per delivered review.

Run Zoover Reviews Scraper on Apify →

Zoover reviews scraper pricing: what one row costs

EventWhat it isPriceCharged for
reviewReview row$0.004One Zoover guest review: the 1-10 score, title, full text, per-aspect scores (room, service, location, hygiene, price/quality, child friendliness, food, pool), language, travel party, travel date, reviewer, photos and the owner's reply. Accommodations with no reviews, unknown ids and filtered reviews are free.

$0.004 per review row. Nothing else is charged: accommodation rows, status rows, properties with no reviews, unknown ids, duplicate inputs and every filtered review are free. A typical Zoover property delivers a handful of reviews, so a hundred properties usually land between $0.02 and $0.40 for the whole sweep — and if a property turns out to be empty, that property cost you nothing at all.

There is no per-run start fee and no per-dataset-item fee, which matters when you are sweeping hundreds of properties that may each hold nothing.

Zoover reviews scraper input reference

Every field of Zoover Reviews, straight from the actor's input schema.

FieldTypeDefaultWhat it does
startUrlsarray[]Accommodation pages on Zoover, e.g. https://www.zoover.nl/a/91488/esperos-blue-village-spa — only the number after /a/ counts, the slug does not have to be right. Any query string is fine.
accommodationIdsarray[]The number in /a/<number>/… (Zoover calls it the heliosId), e.g. 91488 — or the internal accommodation id (uuid) an earlier run put in accommodation_id, e.g. 9c471493-27a9-4dfc-a0e5-60ec206b3b62. A uuid is the fastest path: it is the key the review API takes, so the run skips the page read (and, having no page, gets no accommodation row).
maxReviewsPerAccommodationinteger00 = every review the property has (50 per request; Zoover properties rarely hold more than a few dozen, so 0 is normally right and cheap). N = the newest N. The main cost control. Range 0-10000.
maxRatingnumber00 = keep every review. 1-10 = keep only reviews whose overall score is that number or lower — 6 gives a complaints feed on Zoover's 1-10 scale (10 is best). Filtered reviews are never charged. Range 0-10.
minRatingnumber00 = keep every review. 1-10 = keep only reviews whose overall score is that number or higher — 9 gives the testimonials. Combine with the maximum for a band (e.g. 5 to 7). Filtered reviews are never charged. Range 0-10.
requireTextbooleanfalseDrop score-only reviews before billing. Zoover reviews almost always carry text, so this rarely removes much.
sinceDatestring""YYYY-MM-DD, or relative so schedules never go stale: "30 days", "6 months", "2 years". Reviews come newest first, so the run stops paging once a whole page predates the date. Empty = no date limit.
includeAccommodationRowbooleantrueAlso emit one free row per property with its ids, name, star rating, the Zoover guest rating and review count, the seven aspect ratings, its place, chain, address and contact details, plus this run's tally of reviews per score and per travel party. Never charged.
sessionsinteger2How many Apify Proxy sessions (egress IPs) run in parallel. More is faster; each is paced separately. Range 1-16.
perIpnumber1Pace for each session. Zoover serves 1 request/s per IP steadily (10 of 10 accommodation pages in a paced probe); raising it causes rotations, not speed. Range 0.1-3.
proxyConfigurationobject{"useApifyProxy": true}Apify Proxy with the default datacenter group is enough: Zoover's Cloudflare gate reads the TLS fingerprint, not the IP, and this actor's client passes it. Residential works too but costs more. Keep the default.

Example input

{
  "startUrls": ["https://www.zoover.nl/a/91488/esperos-blue-village-spa"],
  "accommodationIds": ["83376", "9c471493-27a9-4dfc-a0e5-60ec206b3b62"],
  "maxReviewsPerAccommodation": 0,
  "includeAccommodationRow": true
}
{
  "accommodationIds": ["91488", "83376"],
  "maxRating": 6,
  "requireText": true,
  "sinceDate": "12 months"
}

Zoover reviews scraper output fields

One row per guest review (charged), plus a free accommodation context row and a free status row per property.

Dataset views in the Apify Console: Overview, Reviews, Complaints, Accommodations, Status.

FieldTypeWhat it holds
typestringreview | accommodation | status
targetstringThe input value (URL or id) this row came from
review_idstringZoover's review id (uuid); legacy reviews that carry no uuid get h<review_legacy_id>, which is stable across runs
review_legacy_idintegerZoover's numeric legacy id of the review itself (the API calls it heliosId; it is the review's, not the accommodation's)
accommodation_idstringZoover's internal accommodation id (uuid) — the key the review API takes; feed it back as accommodationIds to skip the page read
helios_idstringThe number in the accommodation URL /a/<number>/… (Zoover's heliosId)
giata_idintegerGiata property id, the cross-OTA hotel identifier
accommodation_namestringAccommodation name as Zoover shows it
accommodation_urlstringThe accommodation's page on zoover.nl
accommodation_typestringHotel, Aparthotel, Camping, Bungalow, Vakantiehuis, …
titlestringReview title as the guest wrote it
textstringFull review text
text_lengthintegerCharacters of review text, as Zoover counts them
scorenumberThe guest's overall score, 1-10 (10 is best)
aspectsobjectThe guest's scores per aspect, 1-10: room, food, service, location, hygiene, pool, childFriendly, priceQuality, general — only the ones they rated
localestringLanguage the review was written in (nl, de, …)
traveled_withstringTravel party: COUPLE | FAMILY_WITH_YOUNG_CHILDREN | FAMILY_WITH_OLDER_CHILDREN | FRIENDS | SINGLE | BUSINESS | GROUP
review_datestringPublication date, YYYY-MM-DD
review_datetimestringPublication timestamp, ISO 8601 UTC
updated_datestringDate the review was last edited, YYYY-MM-DD
travel_datestringMonth of the stay as Zoover records it, YYYY-MM-DD
reviewer_namestringReviewer display name
reviewer_verifiedbooleanWhether Zoover marks the reviewer as verified
reviewer_levelstringZoover's reviewer level badge, when the profile has one
likesintegerHelpful votes the review received
photosarrayPhoto URLs the guest attached (rewritten from Zoover's gs:// uris to fetchable links)
photo_countintegerHow many photos the review carries
responsestringThe owner's or manager's reply, in full
response_datestringDate of the owner's reply, YYYY-MM-DD
response_authorstringWho signed the reply
response_positionstringThe replier's role as Zoover labels it, e.g. Owner
star_ratingintegerOfficial star classification of the property, 1-5
ratingnumberZoover's overall guest rating for the property, 1-10
review_countintegerHow many reviews Zoover holds for the property
aspect_ratingsobjectThe property's average score per aspect, 1-10 (room, service, location, hygiene, childFriendly, priceQuality, general)
countrystringCountry, in Dutch as Zoover names it
regionstringRegion or island
citystringCity or resort
chainstringHotel chain the property belongs to, when Zoover knows one
phonestringProperty phone number
emailstringProperty e-mail address
addressstringStreet address
postal_codestringPostal code
websitestringThe property's own website
imagestringPreview image URL
bookablebooleanWhether Zoover sells this property itself
reviews_listedintegerReviews the API reported for the property in this run (totalCount)
reviews_fetchedintegerReviews this run actually read before filtering
scoresobjectReviews per score, keys "1"…"10", from the API's own aggregation
travel_partiesobjectReviews per travel party, from the API's own aggregation
statusstringok | no_reviews | not_found | duplicate | error
reviewsintegerReviews delivered (and charged) for this accommodation
filteredintegerReviews read but dropped by the filters — never charged
pagesintegerReview requests this accommodation needed
totalintegerReviews the API says the accommodation has
duplicatesinteger1 when this input pointed at an accommodation another input already covered
errorstringWhy this accommodation failed, when it did
fetched_atstringWhen this row was read, ISO 8601 UTC

Zoover reviews scraper in Python, JavaScript and curl

Python

from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("kestrel/zoover-reviews-scraper").call(run_input={
    "startUrls": ["https://www.zoover.nl/a/91488/esperos-blue-village-spa"],
    "maxReviewsPerAccommodation": 0,
    "maxRating": 6,
})

for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row["type"] == "review":
        print(row["score"], row["review_date"], row["title"])

JavaScript (Node)

import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('kestrel/zoover-reviews-scraper').call({
    accommodationIds: ['91488', '83376'],
    sinceDate: '12 months',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((i) => i.type === 'review').length, 'reviews');

curl

curl -X POST "https://api.apify.com/v2/acts/kestrel~zoover-reviews-scraper/runs?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"startUrls":["https://www.zoover.nl/a/91488/esperos-blue-village-spa"],"maxReviewsPerAccommodation":0}'

curl "https://api.apify.com/v2/acts/kestrel~zoover-reviews-scraper/runs/last/dataset/items?token=$APIFY_TOKEN&format=csv&clean=1" > zoover-reviews.csv

The same three calls are runnable files in the examples repository: examples/zoover-reviews-scraper.py|.js|.sh.

Limits and honest notes

  • Zoover's review depth is shallow. In a measured sample of accommodations from Zoover's own sitemap (2026-08-29) the review counts ran 0, 0, 0, 0, 0, 0, 0, 1, 4, 4, 6, 11, 16, 23 — most properties had none at all and the busiest held 23. Feed it lists of properties, not one property expecting thousands of rows.
  • These are Dutch-language reviews, mostly locale: "nl" — that is the point of the source, and it is why they rarely appear on the English-language sites.
  • A refusal is never reported as "no reviews". A Cloudflare block, a throttle or a 400 becomes status: "error" with the reason; an empty property is no_reviews and an unknown id is not_found.
  • The review endpoint quietly returns one review when the request omits its size, while still reporting the true total. This actor always sends it and pages the cursor to the end.

FAQ

Do I need a Zoover API key?

No. There is no public Zoover api to sign up for. The actor calls the same endpoint the website's own front end calls, through Apify Proxy, so you get zoover reviews without api credentials of any kind.

Can I scrape Zoover reviews by hotel name?

Not directly — targets are URLs or ids. Search zoover.nl for the property, copy the URL from the address bar, and paste it into startUrls. Once a run has read it, keep the accommodation_id and use that for every later refresh.

How do I get the reviews of many hotels at once?

Put every URL in startUrls or every number in accommodationIds — hundreds are fine in one run. That is how bulk hotel reviews are meant to be collected here, and it is why the empty properties being free matters so much.

What language are the reviews in?

Mostly Dutch (locale: "nl"), which is the point: these are dutch travel reviews written by the guests who booked from the Netherlands and Belgium. The field is on every row, so you can split by language before you translate.

Does it include the hotel's replies?

Yes. Zoover owner replies come back in response, in full, with response_date, response_author and response_position. They are part of the review row, at no extra cost.

How fast is it, and will Zoover block me?

One accommodation page plus one review request per property, paced at one request per second per session (raise sessions to go wider). A paced probe of ten different properties on one datacenter session returned ten of ten, and the actor rotates its proxy session on any refusal and retries.

What happens when a property has no reviews?

You get a free status row with status: "no_reviews" and total: 0, and an accommodation row with the property's details. Nothing is charged. The run does not fail — that is the normal answer on this site.

Can I get zoover guest feedback into a spreadsheet?

Yes — export the dataset as CSV or XLSX from the run page, or hit the dataset API with format=csv. Every field in the tables above becomes a column.

How to use Zoover Reviews Scraper

Run Zoover Reviews Scraper

The actor lives on the Apify Store, where the pricing tab carries the live per-row price and the input form has every field above: apify.com/kestrel/zoover-reviews-scraper