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
13 days ago
Last modified
Categories
Features
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.
- Availability calendar on demand. Turn on Include availability calendar to add, for every package of an activity, each date's lowest price and whether the date can be booked, for a window you choose (up to 180 days). Read-only and not billed as a separate event.
- 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 skips activities already saved in a previous run or dataset.
- 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 / latitude / longitude | "1 Sample Pier, Sample City, ST" / 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) |
availability | [{ "packageId": "1001", "packageName": "Adult ticket", "date": "2026-12-01", "available": true, "price": 24.5, "basePrice": 24.5, "saleWindowFrom": "2026-11-01", "saleWindowTo": "2027-10-31" }] (only when Include availability calendar is on; a date with no price is not bookable, available is false and price is null; with Time slots on each entry also carries itemId, timeSlots, soldOut, discounted and skus, one per ticket type and slot: { "skuId", "skuName": "Adult", "timeSlot": "10:00", "price", "basePrice", "remainingQty" }; remainingQty is KKday's published remaining count for that slot, null when none is shown; basePrice is the crossed-out price) |
availabilityStatus | "complete" (also "unavailable", "skipped_breaker"; only when Include availability calendar is on) |
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") |
previousMinPrice / minPriceChange / minPriceChangePercent / previousSeenAt | 24.5 / -2.0 / -7.5 / ISO timestamp (Incremental monitoring only, on activities the baseline already knew; empty on new activities and never a reason for a row to count as changed) |
previousPrice / priceChange / priceChangePct | on each availability entry and each skus cell: the price this exact package and date (and ticket type and slot) had on the previous Incremental run, and the move since (Incremental monitoring with Include availability calendar only; absent on a first run and on dates the previous run did not price; never a reason for a row to count as changed) |
changedFields | ["minPrice", "ratingCount"] (empty with Incremental monitoring off) |
firstSeenAt / lastSeenAt / scrapedAt | ISO timestamps (all equal this run's read time with Incremental monitoring off) |
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 (a link written with
q=is read as a keyword search). - 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. |
sortBy | string | prec | Search order: prec (recommended), omdesc (most popular), sdesc (top rated), pasc (price low to high). Applies to search mode; a pasted results link keeps its own sort. |
urls | array | sample search URL (.../en/product/productlist?q=Taipei) | Activity pages (/product/<id>-<slug> or the id-only /product/<id>) or keyword search pages (/product/productlist). Other pages (city or destination pages, categories, home) are skipped with a warning. Search pages keep paging forward from the pasted page. The Console form also pre-fills example activity links; a run started through the API without urls reads only the sample search 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. |
includeAvailability | boolean | false | Add the per-package, per-date availability calendar to each activity. Read-only; it adds no billed event and does not make a row count as changed in Incremental monitoring. With Incremental monitoring on, the actor remembers each calendar price between runs so every date shows its own price move. |
availabilityDateFrom | string | today | First calendar date, YYYY-MM-DD. Past dates start today. |
availabilityDays | integer | 30 | Calendar length in days, 1 to 180. |
availabilityOnlyAvailable | boolean | false | Keep only bookable dates. |
availabilityTimeSlots | boolean | false | Also read, per package and date, the time slots, the sold-out and discounted flags and each ticket type's name, price and remaining count (timeSlots, soldOut, discounted, skus). One extra read per package, so runs are slower. |
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. |
ignoreFieldsForChanges | array | [] | Field names (or dotted nested paths) that never count as a change; rows differing only in them stay UNCHANGED. Changing the list does not reset the baseline. |
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 tried first; when the site refuses them the actor switches to its backup connection automatically. |
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 sits behind a verification challenge that plain datacenter connections do not pass, 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 backup connection lane after the first refusal and locks onto whichever one starts serving content for the rest of the run.
A paid Apify plan is required to use the built-in connections: the FREE plan has no Apify proxy access, so on FREE you must supply your own proxy URLs. 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 the actor could not load a single page, the run stops with a clear message so "no activities matched" is never confused with "nothing could be read." Three messages exist, chosen from what the connection actually did:
- "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid." appears when every connection attempt was refused by the proxy itself.
- "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested KKday pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again." appears when the proxy refused the default connection (a free plan), the backup connection was reachable, and KKday still served no usable page.
- "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later." appears for every other run that loaded no page, including a paid run where KKday challenges every connection (try again later).
When the default connection is refused by the proxy, a warning is also written once at the start of the run and the actor continues on its backup connection. If only some requests were rejected, or the run time nearly ran out, 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... |
| ✈️ Pollstar Scraper Scrape Pollstar upcoming concerts and tour dates: play date, venue with full address and... | 🧩 Fever Scraper Scrape Fever events by city, category, keyword or URL. Extract from-prices, venues, dates... |
| 🧩 ASOS Scraper Scrape ASOS by keyword, category or URL. Every row carries the selling price and the... | 🛒 Tokopedia Scraper Extract product data from Tokopedia, Indonesia’s largest marketplace. Search by keyword... |
💬 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.