Klook Search Scraper
Pricing
from $1.60 / 1,000 search result chargeds
Klook Search Scraper
Get full-text search across every Klook vertical from Klook. Talks directly to Klook's own mobile app API, no login needed. Split from the mature, published klook-all-in-one-api for a focused, single-purpose workflow.
What does Klook Search Scraper do?
Klook Search Scraper runs full-text activity/experience search across every Klook vertical (Tours, Attractions, Transport, Hotels, More) with the app's entire real filter panel — main/branch category, dates, guided language, services, departure time, duration, minimum review score, price range, and more. It also exposes the search screen's init state, as-you-type suggestions, and the live filter schema as separate modes.
It talks directly to the same internal API the official Klook Android app uses, reverse-engineered by disassembling the app's native request-signing library and validated against live traffic captured from a real device. No Klook account, no API key. This is the search slice of Klook All-in-One API, split out as its own focused Actor.
Why use Klook Search Scraper?
- Every real search filter — every param below was confirmed live by the parent Actor by tapping the actual control in the app's Filters dialog and reading the resulting request off the wire, not guessed
- Every vertical, not just activities —
mainCategory=30(Transport) returns real Tokyo subway/Shinkansen tickets,mainCategory=54(Hotels) returns real bookable listings, through the exact same endpoint - Real, working pagination — confirmed live with real result counts and zero id overlap across pages
- No account needed — every mode works fully anonymously
- Use cases: activity discovery bots, price/rating monitoring, category-id/filter-id discovery via
filtersmode, as-you-type search UX prototyping viasuggestmode
How it connects to the parent
This Actor is a narrow, batch-run spin-off of klook-all-in-one-api (an always-on Standby REST API exposing every Klook endpoint). It reuses the parent's exact, proven request-signing logic (klook-client.ts, copied verbatim) but only wraps /search/init, /search/suggest, /search, and /search/filters, and runs as a normal input-in/dataset-out Actor instead of a Standby API.
Input
| Field | Type | Description |
|---|---|---|
mode | string | search (default, paginated) | suggest | init | filters |
query | string | Required for search/suggest/filters. Search text |
start | integer | 1-indexed result offset for mode=search. Default 1 |
size | integer | Results per page for mode=search. Default 10 |
maxPages | integer | Stop after this many pages (mode=search). Default 10 |
maxItems | integer | Stop once this many results have been pushed (mode=search only) |
sort | enum | most_relevant | price | participants | publish_time | review_score |
date | string (date) | YYYY-MM-DD — a single date, not a real range |
mainCategory | string | 1=Tours & experiences, 2=Attraction tickets, 30=Transport, 54=Hotels, 70=More. Also the tab for mode=filters |
branchCategory | string | Sub-category id — see klook-categories-scraper or mode=filters |
guidedLanguage | string | Tour guide language id, e.g. 3029=English guided |
services | string[] | Small group, hotel pick-up, free cancellation, guaranteed departure, ... |
departureTime | string[] | Evening/afternoon departure, ... |
duration | string[] | 0-3 hours, 3-5 hours, full day, 2+ days, ... |
reviewScore | string | 3, 3.5, 4, or 4.5 |
priceMin / priceMax | number | Price range |
other | string[] | other_instant_key=Instant confirmation, 36822=Halal-friendly |
location | string | Narrow results to a location id, e.g. 1012=Japan |
currency | string | ISO code, default GBP |
Example:
{ "mode": "search", "query": "tokyo", "mainCategory": "1", "sort": "price", "size": 10 }
Output
One row per search result for mode=search. Real example (from the parent Actor's own live-confirmed capture of GET /search?query=tokyo&main_category=1&sort=price&size=1):
{"data": {"city_id": 28,"title": "Tokyo Chopstick Making Experience in Ginza","deep_link": "https://www.klook.com/en-GB/activity/183178-tokyo-chopstick-making-experience-in-ginza/"}}
For mode=init/suggest/filters, one row wrapping the raw response: { "mode": "...", "data": { ... } }.
Known limitations
- Requires Apify's RESIDENTIAL proxy group. Klook's edge blocks non-residential IPs with a DataDome bot-challenge page — confirmed directly from this development sandbox (a request byte-for-byte identical to a working one, sent without the proxy, came back as a DataDome JS-challenge HTML page, not JSON). All outbound requests route through
Actor.createProxyConfiguration({ groups: ['RESIDENTIAL'] }). - Local dev smoke testing outside the Apify platform needs "Proxy external access" enabled on the account. This Actor's code was verified to be a byte-for-byte match against the parent's already-published, working
klook-client.ts, and the local smoke test run during development failed exactly at the proxy-URL-acquisition step (ProxyConfiguration: The "Proxy external access" feature isn't enabled for your account) — before any request to Klook was even attempted. That's an account-tier restriction on pulling a proxy URL from outside a real platform Actor run, not a defect in this code; once deployed and actually run on Apify's platform, proxy access works through the normal SDK path. mode=filters'sbranchCategoryoptions are query-scoped — a narrow query silently omits categories with zero matching results. For category ids independent of any query, useklook-categories-scraperinstead.- This is an unofficial, reverse-engineered integration, not affiliated with or endorsed by Klook Travel Technology Limited.
Pricing
Pay per event. Charged $0.003 per search-result (FREE tier) — one event per row pushed to the dataset. See the Actor's Pricing tab for rates across all tiers.