KKday Scraper - Tours, Activities & Prices
Pricing
from $1.70 / 1,000 activity records
KKday Scraper - Tours, Activities & Prices
Scrape KKday travel activities by keyword, city or URL. Every row carries prices, discount, ratings, booking counts, destinations and images. Full details (description, address, coordinates, price tiers) are one toggle away, with recurring change tracking built in.
Pricing
from $1.70 / 1,000 activity records
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
1
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
KKday Scraper: Activity Prices, Ratings, Details & Change Tracking
KKday Scraper turns KKday's travel activities catalog into structured data. Search by keyword or paste KKday links to collect tours, attractions and experiences with their price range, original price, discount, rating, review count, booking-demand count, destinations and images. Fetch activity details (on by default) adds the full description, meeting address, coordinates and complete price-tier list for each activity. Schedule recurring runs with Incremental monitoring to get only new and changed activities, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.
Why This Scraper?
- Two ways to find activities. Search by keyword, or paste KKday links: individual activity pages or keyword search-result pages, which keep paging forward from wherever you pasted them.
- Rich listing data out of the box. Price range, original price, discount percent, rating, review count, booking-demand count, destinations, tags and a cover image come straight from the search results, with no extra toggle needed.
- Full activity detail on demand. Fetch activity details, on by default, adds the long description, meeting address, coordinates and the complete price-tier list, billed only for records it actually reaches and saves.
- Language and currency control. Search mode reads result pages in the site language and currency you choose; pasted links are read exactly as pasted.
- Built for schedules. Incremental monitoring tracks one search or URL set across recurring runs and returns only new and changed activities, so unchanged ones are not billed again.
- Resume without repaying. Point Resume at a previous run or dataset and activities already saved there are skipped.
- A refused run fails loudly instead of returning an empty dataset, so "nothing matched this search" is never confused with "the connection was refused."
Use Cases
- Travel content and comparison sites: build destination pages with current activity prices, discounts and ratings.
- Deal and price monitoring teams: schedule recurring runs with Incremental monitoring to catch new discounts or price changes without re-billing unchanged activities.
- Travel agencies and OTAs: enrich a curated list of activity URLs with full descriptions, coordinates and price tiers for repackaging.
- Market and competitor researchers: track booking-demand counts, ratings and supplier names across a destination's activities over time.
- Trip-planning tools and chatbots: feed activity search results, coordinates and price tiers into your own app or assistant.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
id | "20000001" |
name | "Sample Harbor Sunset Cruise" |
url | KKday link to the activity (always the English page) |
minPrice / maxPrice | 24.5 / 89.0 |
officialPrice | 29.0 |
currency | "USD" |
discountPercent | 15.5 |
ratingStar / ratingCount | 4.7 / 1204 |
bookedCount | "8K+" |
destinations | ["Sample Harbor"] |
displayTags | ["Instant Confirmation"] |
supplierName | "Sample Supplier" |
introduction | short teaser line from the search result |
imageUrl | cover image link (imageUrls carries the full gallery when Fetch activity details is on) |
address | "1 Sample Pier, Sample City, ST" (when Fetch activity details is on) |
latitude / longitude | 25.0 / 121.5 (when Fetch activity details is on) |
description | full activity description (when Fetch activity details is on) |
priceTiers | [{ "price": 24.5, "currency": "USD", "name": "Adult" }] (when Fetch activity details is on; each tier carries its own currency) |
detailStatus | "complete" (also "listing_only", "unavailable", "skipped_breaker") |
changeType | "NEW" (every row is NEW with Incremental monitoring off; with it on, also "UPDATED", "UNCHANGED", "EXPIRED") |
changedFields | ["minPrice", "ratingCount"] (empty with Incremental monitoring off) |
firstSeenAt / lastSeenAt | ISO timestamps (both equal this run's read time with Incremental monitoring off) |
scrapedAt | ISO timestamp of this run's read |
Activities read from a pasted single-activity URL do not carry minPrice, maxPrice, officialPrice, discountPercent, ratingStar, ratingCount, bookedCount, destinations, displayTags, supplierName or introduction: KKday only exposes those on search-result pages, not on the activity page itself. On those rows currency simply repeats your Currency input, so read the price currency from each priceTiers entry instead. Enriched descriptions and addresses come from the English activity page whatever Language you pick.
How to Use
- Pick a mode:
search(find activities by keyword) orurl(read pasted KKday links). - For search mode, set the query, language and currency. For URL mode, paste activity pages or keyword search-result pages.
- Keep Fetch activity details on (the default) for the full description, address, coordinates and price tiers, or turn it off for listing fields only, and set Max items and Max search pages to control run size and cost.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.
Search by keyword:
{"mode": "search","query": "Kyoto","language": "en","currency": "USD","maxItems": 20}
Paste an activity link, with full details:
{"mode": "url","urls": ["https://www.kkday.com/en/product/20000001-sample-activity"],"fetchDetails": true,"maxItems": 1}
Continue a pasted search page, capped to 3 pages:
{"mode": "url","urls": ["https://www.kkday.com/en/product/productlist?q=Osaka"],"maxItems": 30,"maxPages": 3}
Search in another language and currency:
{"mode": "search","query": "Taipei","language": "zh-tw","currency": "TWD","maxItems": 10}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/kkday-com-scraper").call(run_input={"mode": "search", "query": "Kyoto", "maxItems": 20})for activity in client.dataset(run["defaultDatasetId"]).iterate_items():print(activity["name"], activity["minPrice"], activity["currency"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/kkday-com-scraper').call({ mode: 'search', query: 'Kyoto', maxItems: 20 });const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Max items and Max search pages
Max items stops a run once that many activities have been returned, not scanned: a suppressed row (unchanged, with Emit unchanged off) never counts against it, so an activity ranked after a run of unchanged ones on the same result page is still reached and returned. Max search pages caps how many result pages one search, or one pasted search-result URL, will read; leave it at 0 for no page limit. A run that stops on either cap (including one whose returned rows exactly reach Max items), or a pasted URL that could not be read, never counts as a fully scanned run, so Emit expired never fires from it. On a fully scanned run, EXPIRED rows are added after the scan and are not limited by Max items.
Resume and recurring updates
- Resume from run or dataset (
resumeFromRunId) skips activity ids already saved by a previous run: paste its run id or dataset id. - Incremental monitoring (
incrementalMode) tracks one search or URL set across recurring runs under a state key (set your own withstateKey, or let the actor derive one from mode, query or URLs, language and currency). Each activity is classifiedNEW,UPDATED(withchangedFieldsnaming what moved), or, only when you opt in,UNCHANGED(emitUnchanged, suppressed and unbilled otherwise) orEXPIRED(emitExpired, only once a run has fully read the tracked scope). With Incremental monitoring off, output is exactly as before. - Resume from run cannot be combined with Incremental monitoring once a baseline already exists for the same state key.
- With Fetch activity details on, an activity whose detail page could not be read on one run comes back as
UPDATEDwithdetailStatusand the detail fields inchangedFields, and is returned (and billed as a record) like any other update.
Send results into your apps (MCP connectors)
Current status: connector delivery is not active in this build. The connector fields below are accepted so saved inputs stay valid, but selecting a connector currently sends nothing; every result is in the Apify dataset only. The rest of this section describes the intended behavior.
The connector option is meant to pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each activity becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify, Settings, Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. If the picker is empty, you haven't authorized a connector yet.
- For Notion, also set
notionParentPageUrlto the page where activity pages should be created.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search (find activities by keyword) or url (read pasted KKday links). |
query | string | Taipei | Keyword for activity search, such as a city, attraction or experience. Ignored in URL mode. |
language | string | en | Site language path for search mode, for example en or zh-tw. English pages are verified; other codes reuse the same layout. Pasted URLs keep their own language. |
currency | string | USD | Three-letter display currency. Search mode requests pages in it; pasted URLs are read as pasted, so put the currency in a pasted search link yourself. |
urls | array | sample search URL | Activity pages (/product/<id>) or keyword search pages (/product/productlist). Search pages keep paging forward from the pasted page. |
fetchDetails | boolean | true | Add description, meeting address, coordinates and price tiers to each activity. Billed only for records actually enriched and saved (detail-enrichment). Pasted single-activity links are always read from the activity page and carry this charge either way. |
maxItems | integer | 20 | Maximum number of records returned, from 1 to 500. The only cap that stops a run early by default. |
maxPages | integer | 0 | Maximum search pages to read per search. 0 means no page limit. |
resumeFromRunId | string | (none) | Skip activity ids already present in a previous run or dataset. |
incrementalMode | boolean | false | Return only new or changed activities for the same state key. |
stateKey | string | (none) | Name for shared recurring-run state. Leave empty to derive it from mode, query or URLs, language and currency. |
emitUnchanged | boolean | false | Return (and bill) activities unchanged since the previous run. |
emitExpired | boolean | false | Return (and bill) activities present before but no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked scope. |
mcpConnectors | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. Not active in the current build. |
notionParentPageUrl | string | (none) | Notion connector only: page under which activity pages are created. Not active in the current build. |
maxNotifyListings | integer | 50 | Cap on records written to each connector per run. Does not affect the dataset. Not active in the current build. |
proxyConfiguration | object | Apify Proxy (Residential, SG) | Connection settings for KKday requests. Residential exits are required; datacenter exits are refused by the site edge. |
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"id": "20000001","name": "Sample Harbor Sunset Cruise","url": "https://www.kkday.com/en/product/20000001-sample-harbor-sunset-cruise","minPrice": 24.5,"maxPrice": 89.0,"officialPrice": 29.0,"currency": "USD","discountPercent": 15.5,"ratingStar": 4.7,"ratingCount": 1204,"bookedCount": "8K+","destinations": ["Sample Harbor"],"displayTags": ["Instant Confirmation"],"imageUrl": "https://example.com/img/sample.jpg","imageUrls": ["https://example.com/img/sample.jpg"],"supplierName": "Sample Supplier","introduction": "A relaxed evening cruise around the fictional Sample Harbor.","address": "1 Sample Pier, Sample City, ST","latitude": 25.0,"longitude": 121.5,"description": "A fictional sunset cruise with narration and drinks included, used here only as a placeholder.","priceTiers": [{ "price": 24.5, "currency": "USD", "name": "Adult" }],"detailStatus": "complete","changeType": "NEW","changedFields": [],"firstSeenAt": "2026-01-01T00:00:00+00:00","lastSeenAt": "2026-01-01T00:00:00+00:00","scrapedAt": "2026-01-01T00:00:00+00:00"}
Plan Requirement
KKday's edge refuses datacenter connections, so the default proxy setting uses residential exits (Singapore by default); switch the country under Connection to match your market. If the selected connection is refused, the actor automatically steps down to a fallback connection lane and locks onto whichever one starts serving content for the rest of the run. If you supply your own proxy URLs, the actor uses only those and never switches lanes. No KKday account is needed; only public catalog and activity pages are read.
FAQ
How much does it cost?
You pay per record returned, plus a one-time detail charge per record only when its activity page was actually read and the record saved: with Fetch activity details on, or for any pasted single-activity link (those are always read from the activity page). The Pricing tab shows the current rates. Use Max items to cap the cost of any run.
Is it legal to scrape KKday?
This actor collects only publicly available catalog and activity data, without logging into any account. You are responsible for how you use it: follow KKday's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Prices and availability change constantly and are generally facts, but activity photos and descriptions may be subject to third-party rights.
Can I get only new or changed activities on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental monitoring. Each run then returns only new and updated activities for the same state key, and unchanged ones are not billed unless you opt in with Emit unchanged.
Does Incremental monitoring flag an activity that disappeared and came back?
No. Activities are classified NEW, UPDATED or UNCHANGED, plus EXPIRED when Emit expired is on and a run fully scanned the tracked scope. An activity that vanishes and is later seen again is classified UPDATED or UNCHANGED the next time it is scanned, not as a separate reappeared state.
Why does a pasted activity URL return fewer fields than a search result?
KKday only publishes price range, discount, rating, review count, booking-demand count, destinations, tags and supplier name on its search-result pages, not on the activity page itself. Pasting a single activity link (or turning on Fetch activity details) still gets you the name, description, address, coordinates and price tiers, but those listing-only fields stay empty. To get both, search or paste a search-result URL with Fetch activity details on.
Why did my run fail instead of returning an empty dataset?
If every request the actor tried was refused, the run stops with a clear message so "no activities matched" is never confused with "nothing could be read." Run it again in a few minutes. If only some requests were rejected, you still get what was found.
Can I use it with AI agents or MCP?
Yes. Call it through the Apify API, any Apify integration or an MCP client such as Apify's MCP server, and read the results from the dataset. The connector field for pushing results straight into Notion, Linear or Airtable is not active in the current build.
🔗 More scrapers you might like
Pair this actor with these related scrapers from the same team:
| ✈️ Viator Scraper Collect Viator.com tour and activity results from destinations, categories, search pages... | 🏨 Klook Scraper Scrape Klook activities, tours, attractions and travel experiences from search pages or... |
| 🛒 Tokopedia Scraper Extract product data from Tokopedia, Indonesia’s largest marketplace. Search by keyword... | 🧩 ASOS Scraper Scrape ASOS by keyword, category or URL. Every row carries the selling price and the... |
| 🧩 Fever Scraper Scrape Fever events by city, category, keyword or URL. Extract from-prices, venues, dates... | 🏨 Hipcamp Scraper Scrape Hipcamp campgrounds, RV parks, glamping and unique stays by US or AU region or... |
💬 Support
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.