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
| Event | What it is | Price | Charged for |
|---|---|---|---|
review | Review row | $0.004 | One 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.
| Field | Type | Default | What it does |
|---|---|---|---|
startUrls | array | [] | 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. |
accommodationIds | array | [] | 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). |
maxReviewsPerAccommodation | integer | 0 | 0 = 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. |
maxRating | number | 0 | 0 = 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. |
minRating | number | 0 | 0 = 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. |
requireText | boolean | false | Drop score-only reviews before billing. Zoover reviews almost always carry text, so this rarely removes much. |
sinceDate | string | "" | 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. |
includeAccommodationRow | boolean | true | Also 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. |
sessions | integer | 2 | How many Apify Proxy sessions (egress IPs) run in parallel. More is faster; each is paced separately. Range 1-16. |
perIp | number | 1 | Pace 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. |
proxyConfiguration | object | {"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.
| Field | Type | What it holds |
|---|---|---|
type | string | review | accommodation | status |
target | string | The input value (URL or id) this row came from |
review_id | string | Zoover's review id (uuid); legacy reviews that carry no uuid get h<review_legacy_id>, which is stable across runs |
review_legacy_id | integer | Zoover's numeric legacy id of the review itself (the API calls it heliosId; it is the review's, not the accommodation's) |
accommodation_id | string | Zoover's internal accommodation id (uuid) — the key the review API takes; feed it back as accommodationIds to skip the page read |
helios_id | string | The number in the accommodation URL /a/<number>/… (Zoover's heliosId) |
giata_id | integer | Giata property id, the cross-OTA hotel identifier |
accommodation_name | string | Accommodation name as Zoover shows it |
accommodation_url | string | The accommodation's page on zoover.nl |
accommodation_type | string | Hotel, Aparthotel, Camping, Bungalow, Vakantiehuis, … |
title | string | Review title as the guest wrote it |
text | string | Full review text |
text_length | integer | Characters of review text, as Zoover counts them |
score | number | The guest's overall score, 1-10 (10 is best) |
aspects | object | The guest's scores per aspect, 1-10: room, food, service, location, hygiene, pool, childFriendly, priceQuality, general — only the ones they rated |
locale | string | Language the review was written in (nl, de, …) |
traveled_with | string | Travel party: COUPLE | FAMILY_WITH_YOUNG_CHILDREN | FAMILY_WITH_OLDER_CHILDREN | FRIENDS | SINGLE | BUSINESS | GROUP |
review_date | string | Publication date, YYYY-MM-DD |
review_datetime | string | Publication timestamp, ISO 8601 UTC |
updated_date | string | Date the review was last edited, YYYY-MM-DD |
travel_date | string | Month of the stay as Zoover records it, YYYY-MM-DD |
reviewer_name | string | Reviewer display name |
reviewer_verified | boolean | Whether Zoover marks the reviewer as verified |
reviewer_level | string | Zoover's reviewer level badge, when the profile has one |
likes | integer | Helpful votes the review received |
photos | array | Photo URLs the guest attached (rewritten from Zoover's gs:// uris to fetchable links) |
photo_count | integer | How many photos the review carries |
response | string | The owner's or manager's reply, in full |
response_date | string | Date of the owner's reply, YYYY-MM-DD |
response_author | string | Who signed the reply |
response_position | string | The replier's role as Zoover labels it, e.g. Owner |
star_rating | integer | Official star classification of the property, 1-5 |
rating | number | Zoover's overall guest rating for the property, 1-10 |
review_count | integer | How many reviews Zoover holds for the property |
aspect_ratings | object | The property's average score per aspect, 1-10 (room, service, location, hygiene, childFriendly, priceQuality, general) |
country | string | Country, in Dutch as Zoover names it |
region | string | Region or island |
city | string | City or resort |
chain | string | Hotel chain the property belongs to, when Zoover knows one |
phone | string | Property phone number |
email | string | Property e-mail address |
address | string | Street address |
postal_code | string | Postal code |
website | string | The property's own website |
image | string | Preview image URL |
bookable | boolean | Whether Zoover sells this property itself |
reviews_listed | integer | Reviews the API reported for the property in this run (totalCount) |
reviews_fetched | integer | Reviews this run actually read before filtering |
scores | object | Reviews per score, keys "1"…"10", from the API's own aggregation |
travel_parties | object | Reviews per travel party, from the API's own aggregation |
status | string | ok | no_reviews | not_found | duplicate | error |
reviews | integer | Reviews delivered (and charged) for this accommodation |
filtered | integer | Reviews read but dropped by the filters — never charged |
pages | integer | Review requests this accommodation needed |
total | integer | Reviews the API says the accommodation has |
duplicates | integer | 1 when this input pointed at an accommodation another input already covered |
error | string | Why this accommodation failed, when it did |
fetched_at | string | When 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
400becomesstatus: "error"with the reason; an empty property isno_reviewsand an unknown id isnot_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
Related scrapers
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