Travel · E-commerce
Viator & GetYourGuide Tours Scraper
Scrape and unify tour and activity prices from Viator and GetYourGuide into one normalized schema — prices, duration, ratings, review counts, booking URLs per activity — export to JSON or CSV. A Viator / GetYourGuide API alternative for tour operators and OTA analysts.
Free Apify credit covers a first run. No credit card to try.
What this Actor scrapes
Two major tours-and-activities platforms, unified into one Pydantic-validated schema:
1. Viator — viator.com/searchResults/all?text=<query> (server-rendered HTML, 24 cards per page, data-automation test attributes) 2. GetYourGuide — getyourguide.com/s/?q=<query> (Vue.js shell with SSR card content, 24 cards per page) 3. Klook *(v2 upgrade — currently returns 0 rows)* — klook.com/search/?keyword=<query> is gated by a JS challenge that requires full browser execution. Documented as a future upgrade behind Camoufox; v1 returns [] with a WARNING.
What we handle for you
- Two platforms, one schema — drop the dataset straight into a spreadsheet or BI tool; no per-platform normalization required.
- We rotate browser fingerprints —
curl-cffiimpersonates Chrome 131 / Chrome 124 / Firefox 147 at the TLS+HTTP/2 layer, so both platforms see real-browser traffic, not Python. - We retry with exponential backoff —
408 / 429 / 503responses trigger up to 5 attempts with doubling delays;Retry-Afterheaders are honoured. - We rotate proxy exits on every block —
BUYPROXIES94952(a static US datacenter pack) is on by default; a freshsession_idand fresh exit IP are issued on every block. - Per-platform isolation — one platform's failure does not abort the run; surviving platforms still produce data.
- Currency-aware — symbol-to-ISO mapping (€/$/£/¥ → EUR/USD/GBP/JPY);
price_usdis populated only when the platform itself displays USD. - Duration parser handles ranges —
"5 to 9 hours"→7.0(midpoint);"30 minutes"→0.5;"1 day"→24.0. - Pydantic v2 validation — input and every output row are model-validated; invalid input fails fast with a clear error before any network call.
- Clean dataset rows — ISO-8601 timestamps, stable platform IDs, no half-parsed strings.
- Configurable cap —
maxPerPlatformlets you cap each platform at 1–100 rows per run.
Use cases
- Tour operator competitive intelligence — find every activity your competitors list in your destination, compare prices, ratings, and durations side-by-side.
- OTA cross-platform analyst dashboards — feed a BI tool with snapshots of how Viator and GetYourGuide each rank the same destination.
- Dynamic pricing strategy — track how the same activity type is priced on each platform over time and adjust your own listings accordingly.
- Destination intelligence reports — schedule weekly runs for "Paris" or "Tokyo" into a named dataset and chart price drift.
- Travel-blogger affiliate research — surface high-rating, high-review-count activities for destination guides without manual browsing.
- Inbound-tour-builder market research — discover which experiences dominate the first-page results when entering a new destination.
- Travel-tech investor diligence — benchmark the top-of-funnel pricing across the experience-booking layer of the travel stack.
Input
Paste this into the Apify Console, or send it as the run input over the API. Proxy settings are on by default; you rarely need to touch them.
| Field | Type | Required | What it does |
|---|---|---|---|
locationQuery | string | yes | Destination text query sent to each platform's search endpoint. Examples: Paris, New York, Tokyo. |
platforms | array | no | List of platforms to scrape. Valid values: viator, getyourguide, klook. Leave empty to scrape all three (order: viator, getyourguide, klook). Note: Klook returns 0 rows in v1 due to DataDome anti-bot. |
maxPerPlatform | integer | no | Cap on activity rows emitted per platform per run. |
useProxy | boolean | no | Route requests through Apify Proxy (BUYPROXIES94952). Recommended on by default since GetYourGuide is Cloudflare-fronted. |
{
"locationQuery": "Paris",
"platforms": [],
"maxPerPlatform": 20,
"useProxy": true
} Output
One row per result, schema-validated before it is written. Export JSON, CSV, Excel or XML from the run, or read it over the API.
platformactivity_idactivity_titlelocation_querylocation_citylocation_countryprice_usdcurrency_originalprice_originalduration_hoursratingreview_countoperator_namecategorybooking_urlimage_url
{
"platform": "getyourguide",
"activity_id": "508441",
"activity_title": "Paris: Le Marais Guided Food Tour with Tastings",
"location_query": "Paris",
"location_city": "Paris",
"location_country": null,
"price_usd": null,
"currency_original": "EUR",
"price_original": 69.0,
"duration_hours": 3.0,
"rating": 4.9,
"review_count": 506,
"operator_name": null,
"category": "experience",
"booking_url": "https://www.getyourguide.com/paris-l16/no-diet-club-unique-local-food-tour-in-paris-le-marais-t508441/",
"image_url": "https://cdn.getyourguide.com/image/.../tour_img/7b9edf635985a601.jpeg",
"scraped_at": "2026-05-16T22:00:00.000Z"
} Pricing
| Event | Price | When |
|---|---|---|
| Actor start | $0.20 | Once per run, covers warm-up and proxy session setup. |
| Activity row | $0.0030 | Per result written to the dataset. |
You pay only for results that land. Cap any run with maxTotalChargeUsd. See pricing & billing for worked examples.
Limitations
- Klook returns 0 rows in v1 — the search endpoint is gated by a JS challenge that
curl-cfficannot clear without a full browser. We document this up front and ship without it rather than over-promise; v2 will add a Camoufox path behind a feature flag. - First page only — no pagination across multiple result pages. Each platform returns ~20–24 cards on the first page; the default cap is 20.
- No detail-page scraping — we scrape the search-results surface only. Itineraries, photo galleries, availability calendars, and meeting points are out of scope for v1.
- Currency follows the platform's display —
price_usdis populated only when the platform itself displays USD. We do not run our own FX conversion. - Search relevance is the platform's — "Paris" can include cards for Versailles or nearby destinations, depending on each platform's relevance engine.
- Datacenter proxy by default —
BUYPROXIES94952is a small static US datacenter pack, not residential; it works at our scale for these targets.
FAQ
Q: Does Viator or GetYourGuide offer an official API I can use instead?
Q: Why is Klook in the schema but returns 0 rows?
curl-cffi cannot clear without a full browser. Adding Klook v2 requires Camoufox, which costs roughly 10× the compute of HTTP scraping. We kept the platform literal in the schema so v2 can land without breaking the dataset shape — but for v1, every Klook call returns [] with a WARNING. Use platforms: ["viator", "getyourguide"] to skip the wasted call entirely.Q: How is `duration_hours` parsed?
"3 hours", "1 hour", "30 minutes", "5 to 9 hours", "1 day", "2.5 hours". We parse all of them. For ranges, we use the midpoint ("5 to 9 hours" → 7.0). Anything unparseable stays null rather than crashing the row.Q: Can I track an activity's price across multiple runs?
Actor.open_dataset(name=...)) or export to your warehouse. The Apify default dataset retention is 7 days; a named dataset persists until you delete it.Q: Can I batch multiple cities in one run?
locationQuery per run. To batch, schedule one Actor task per city (Apify supports unlimited parallel tasks on the free tier up to the concurrent-run cap). Each result row carries location_query so downstream pivots stay correct.Q: Why default `useProxy: true`?
false.Q: Why no detail-page scraping?
Ready to run it?
Open the listing on Apify, paste the input above, and watch rows land. If it ever breaks, it is our problem before it is yours.
Related Actors