Quick answer
Viator and GetYourGuide list overlapping tour-and-activity inventory, but neither platform writes a duration or a price the same way the other does — "5 to 9 hours" on one card, "2.5 hours" on the next, a euro symbol here and a plain number there. Building your own normalization layer across both platforms is a 1–2 dev-week project before you've written a single business query. The Viator & GetYourGuide Scraper hits both platforms in one run and emits a single flat schema — platform, price, currency, duration, rating, booking URL — at $0.003 per activity row plus a $0.05 run-start charge, roughly $3.05 per 1,000 rows.
"5 to 9 hours" has to resolve to a number, not stay a string 🕐
Both platforms write activity duration as free text, and the formats don't agree: "3 hours", "1 hour", "30 minutes", "1 day", "2.5 hours", and — the genuinely awkward one — range formats like "5 to 9 hours". A downstream pricing or scheduling query needs a single comparable number, not a string to re-parse per row. The duration parser resolves every one of those formats to duration_hours as a float, and for ranges specifically, it takes the midpoint — "5 to 9 hours" becomes 7.0. Anything the parser genuinely can't resolve stays null rather than crashing the row or guessing a value with no basis.
Price is a symbol-to-currency lookup before it's a number ⇄
Neither platform ships a clean ISO currency code next to its price — they display a symbol (€, $, £, ¥) embedded in the price string itself. The parser maps each symbol to its ISO-4217 code (EUR, USD, GBP, JPY) before splitting out the numeric price_original. price_usd is populated only when the platform's own display currency is USD — the Actor doesn't run its own FX conversion, because a computed conversion at scrape time would be stale the moment exchange rates move, and presenting it as authoritative would be worse than leaving it null.
Klook returns zero rows, and the Actor says so instead of hiding it 🚫
Klook's search endpoint sits behind a JS challenge that no amount of clever HTTP-layer request-shaping clears — it requires full browser execution to pass. Rather than quietly omit Klook from the schema or return fabricated placeholder rows, platforms: ["klook"] returns [] with a logged WARNING on every run, and the limitation is stated plainly in the Actor's documentation: Klook is a documented v2 upgrade behind a full browser layer, not a silent gap you'd only discover by noticing an empty dataset. Viator and GetYourGuide are both live and unaffected — set platforms: ["viator", "getyourguide"] to skip the wasted Klook call entirely.
What we handle for you 🛡️
-
We rotate browser fingerprints —
curl-cffiimpersonation across Chrome 131, Chrome 124, and Firefox 147 TLS+HTTP/2 profiles. - We rotate residential proxies through Apify Proxy by default — a fresh session and exit IP on every block.
-
We retry with exponential backoff on
408 / 429 / 503, up to 5 attempts, honoringRetry-After. - We isolate failures per platform — one platform's block or parser gap doesn't abort the run; the other keeps producing rows.
-
We fail loud on known gaps — Klook returns an explicit
[]with aWARNING, never a silent, unexplained empty result. - You pay only for activity rows that land. No results, no charge, beyond the small run-start fee.
Full output schema 📦
Sixteen fields per activity:
| Field | Type | Notes |
|---|---|---|
platform |
string |
viator, getyourguide, or klook
|
activity_id |
string | Platform-canonical activity ID |
activity_title |
string | Card title text |
location_query |
string | Echo of the input query |
location_city / location_country
|
string | null | Best-effort parsed from URL or query |
price_usd |
float | null | Populated only when the platform itself displays USD |
currency_original |
string | null | ISO-4217, mapped from the displayed symbol |
price_original |
float | null | Numeric price in the displayed currency |
duration_hours |
float | null | Range strings resolved to their midpoint |
rating |
float | null | 0–5 scale |
review_count |
int | null | Number of reviews |
operator_name |
string | null | Tour operator, when surfaced |
category |
string \ | null |
booking_url |
string | Absolute activity-page URL |
image_url |
string | null | Absolute thumbnail URL |
scraped_at |
string | ISO-8601 UTC timestamp |
Who this is for
Tour-operator competitive intelligence — find every activity a competitor lists in your destination, compared on price, rating, and duration side by side.
Dynamic pricing — track how the same activity type is priced on each platform over time and adjust listings accordingly.
Destination and OTA analyst dashboards — schedule weekly runs into a named dataset and chart price drift by city.
Travel-tech diligence — benchmark top-of-funnel pricing across the experience-booking layer without a partner-API relationship.
Frequently asked questions
Why does Klook return zero rows?
Klook's search endpoint requires full browser execution to clear a JS challenge that HTTP-level request-shaping can't pass. Rather than fake data or silently drop the platform, every Klook call returns [] with a logged warning — a documented v1 limitation, not a bug you'd have to discover on your own.
How is "5 to 9 hours" turned into a number?
The parser resolves range-format durations to their midpoint — 7.0 hours in that example. Single-value formats ("3 hours", "30 minutes", "1 day") resolve directly; anything unparseable stays null instead of crashing the row.
Do I get an official partner-API-equivalent feed?
Viator and GetYourGuide do publish partner APIs, but they require an approved commercial relationship most independent developers can't get. This Actor scrapes the public search-results surface and returns fields compatible with what a partner API would return for the same activities.
What does 1,000 rows cost?
Roughly $3.05 — 1,000 × $0.003, plus the $0.05 run-start charge.
Can I batch multiple destinations in one run?
Not in v1 — one locationQuery per run. Each row carries location_query, so scheduling one Actor task per city and aggregating downstream keeps pivots correct.
Try it
Live on the Apify Store: Viator & GetYourGuide Scraper.
Point it at a destination and get back one normalized schema across two major booking platforms — no manual currency or duration parsing required. Pay-per-event, no subscription.
Built by Devil Scrapes — we build scrapers for the targets that fight back.
Top comments (0)