# KKday Scraper - Tours, Activities & Prices (`abotapi/kkday-com-scraper`) Actor

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.

- **URL**: https://apify.com/abotapi/kkday-com-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 activity records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## KKday Activities Scraper

Scrape KKday travel activities, tours and experiences by keyword or pasted URL. Every record carries the selling price range, the original price, the discount, ratings, booking demand, destinations and images. Flip one toggle to add the full description, meeting address, coordinates and price tiers.

### What it collects

Search mode reads KKday keyword result pages (`/product/productlist`), newest first, 10 per page, paging forward until Max items or the end of results. URL mode reads pasted activity pages (`/product/<id>`) or continues a pasted search page forward. Detail enrichment opens each activity page for the long description, address, latitude and longitude, and the full price tier list.

Sample output (illustrative placeholders, not live listings):

| Activity | From | Rating | Booked |
|---|---|---|---|
| Sample Harbor Sunset Cruise | USD 24.50 | 4.7 (1,204) | 8K+ booked |
| Sample Old Town Walking Tour | USD 12.00 | 4.8 (3,410) | 21K+ booked |

- **Emit unchanged** and **Emit expired** return, and bill, extra rows (billed
  as dataset items); leave them off unless you want full snapshots or expiry
  tracking. Emit expired rows are produced only when a run fully scans the
  tracked scope, never when Max items capped the run or a pasted URL could not
  be read.

### Send results into your apps (MCP connectors)

Results can be piped into the apps you already use through Model Context
Protocol (MCP) connectors, as an optional side channel. This never changes
the dataset output.

- `mcpConnectors`: authorize a connector under Apify, Settings, API &
  Integrations, then select it here. Notion receives a page per record;
  other connectors receive a best-effort write or digest. Each connector
  gets a condensed summary per record; the complete record always stays in
  the dataset.
- `notionParentPageUrl`: the Notion page under which record pages are
  created. Required for the Notion connector, ignored by the others.
- `maxNotifyListings`: cap on records written to each connector per run.

### Connection

KKday's edge refuses datacenter exits, so residential proxy exits are preselected (Singapore default; switch the country to your market). No account is needed; only public catalog pages are read.

### Resume and recurring updates

- **Resume from run or dataset**: skips activity IDs already saved by a previous run.
- **Incremental monitoring**: tracks the same search across scheduled runs and returns only NEW, UPDATED or REAPPEARED rows, with `changedFields` naming what moved. Combine with Emit unchanged or Emit expired to also return those rows (each returned row is billed).
- Incremental monitoring cannot be combined with Resume from run when a baseline already exists for the same state key.

### Pricing

Pay per event: Actor start, one Activity record per saved row (primary), plus Activity details once per record only when detail enrichment actually enriched and saved that row.

# Actor input Schema

## `mode` (type: `string`):

Search KKday by keyword or read pasted KKday URLs.

## `query` (type: `string`):

Keyword for activity search, such as a city, attraction or experience. Ignored in URL mode.

## `language` (type: `string`):

Site language path, for example en or zh-tw. English pages are verified; other codes reuse the same layout.

## `currency` (type: `string`):

Three-letter display currency applied in both search and URL modes, for example USD.

## `urls` (type: `array`):

Activity pages (/product/<id>) or keyword search pages (/product/productlist). Search pages keep paging forward from the pasted page.

## `fetchDetails` (type: `boolean`):

Add description, meeting address, coordinates and price tiers to each activity. An extra charge applies only to records actually enriched and saved (detail-enrichment).

## `maxItems` (type: `integer`):

Maximum number of records returned. This is the only cap that stops a run early by default.

## `maxPages` (type: `integer`):

Maximum search pages to read per search. Zero means no page limit.

## `resumeFromRunId` (type: `string`):

Skip activity IDs already present in a previous run or dataset. See Incremental monitoring for recurring runs.

## `incrementalMode` (type: `boolean`):

Return only new or changed activities for the same state key. Cannot be combined with Resume from run when a baseline already exists.

## `stateKey` (type: `string`):

Name for shared recurring-run state. Leave empty to derive it from mode, query or URLs, language and currency.

## `emitUnchanged` (type: `boolean`):

Return records that have not changed during incremental monitoring. Unchanged rows are billed as dataset items.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to return records that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked scope: not when Max items capped the run, and not when a pasted URL could not be read. This returns, and bills, extra synthetic rows, so leave it off unless you need expiry tracking.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a page per record; other connectors get a best-effort write or digest. Each connector receives a condensed summary per record, not the full record; the complete record always stays in the dataset. Leave empty to skip; this never changes the dataset output. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which record pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on records written to each connector per run. Does not affect the dataset.

## `proxyConfiguration` (type: `object`):

Apify Proxy settings used for KKday requests. Residential exits are required; datacenter exits are refused by the site edge.

## Actor input object example

```json
{
  "mode": "search",
  "query": "Taipei",
  "language": "en",
  "currency": "USD",
  "urls": [
    "https://www.kkday.com/en/product/productlist?q=Taipei"
  ],
  "fetchDetails": true,
  "maxItems": 20,
  "maxPages": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "SG"
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "search",
    "query": "Taipei",
    "language": "en",
    "currency": "USD",
    "urls": [
        "https://www.kkday.com/en/product/productlist?q=Taipei"
    ],
    "fetchDetails": true,
    "maxItems": 20,
    "maxPages": 0,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "SG"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/kkday-com-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "search",
    "query": "Taipei",
    "language": "en",
    "currency": "USD",
    "urls": ["https://www.kkday.com/en/product/productlist?q=Taipei"],
    "fetchDetails": True,
    "maxItems": 20,
    "maxPages": 0,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "SG",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/kkday-com-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "search",
  "query": "Taipei",
  "language": "en",
  "currency": "USD",
  "urls": [
    "https://www.kkday.com/en/product/productlist?q=Taipei"
  ],
  "fetchDetails": true,
  "maxItems": 20,
  "maxPages": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "SG"
  }
}' |
apify call abotapi/kkday-com-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/kkday-com-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ChjtiW3SuNFfHWUCq/builds/7lVZFgORKLXgqnvXb/openapi.json
