# Kwai Scraper — Videos, Profiles & Keyword Discovery (`funny_ground/kwai-scraper`) Actor

Export public international Kwai videos from direct URLs and creator feeds with available pagination, plus first-response keyword discovery snapshots. Get captions, authors and engagement via efficient HTTP.

- **URL**: https://apify.com/funny_ground/kwai-scraper.md
- **Developed by:** [Coor Yu](https://apify.com/funny_ground) (community)
- **Categories:** Social media, Videos
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 public video results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Kwai Scraper — Public Videos, Creator Feeds & Keyword Discovery

HTTP-only scraper for public international **kwai.com** videos. Supports direct video URLs, creator public feeds with available public cursors, and **first-response keyword discovery snapshots** (not full/paginated live search). No login, cookies, paid external API, downstream Actor, browser, Chinese kuaishou.com, hashtag feed or media file download.

### Quick start

```json
{
  "videoUrls": ["https://www.kwai.com/@jacyvieira33/video/5205185379965043809"],
  "maxItems": 1,
  "maxConcurrency": 4
}
```

Targets are public at implementation test time and can become unavailable. Zero saved rows always mean FAILED. Mixed runs with valid rows normally succeed with warnings in RUN_SUMMARY; `failOnPartialFailure: true` makes an empty/failed input fail the whole run. Saved rows remain available and billable if a later error fails the run.

### Supported inputs

- `videoUrls`: direct `https://www.kwai.com/@handle/video/ID` URLs. Exact ID only; recommendations are excluded.
- `profiles`: public creator handles or `/@handle` URLs. Initial public HTML and source-supplied profile cursors only. Count/page/time/access limits apply; complete history is not promised.
- `searchKeywords`: keyword-related public discovery. One API response, up to 20 items, or a small embedded HTML fallback. Ranking, relevance and counts are source-dependent; no search pagination.
- `startUrls`: mixed supported routes: `/@handle`, `/@handle/video/ID`, `/search/keyword/video/ID`, `/discover/keyword`. HTTPS kwai.com/www.kwai.com only. No short links or arbitrary web URLs.

### Creator and keyword example

```json
{
  "profiles": ["jacyvieira33"],
  "searchKeywords": ["cooking"],
  "resultsPerInput": 30,
  "maxItems": 50,
  "maxPagesPerInput": 5,
  "maxConcurrency": 3
}
```

The profile may supply fewer than30 videos. The keyword still supplies **at most20 videos from one discovery response**, not30. Duplicate IDs across inputs are stored once; whichever input finishes first claims the row's attribution. No new HTTP request, redirect or retry starts after the shared output/charge stop condition is reached. A concurrent request-budget stop does not discard a valid response already fetched by another worker.

### Input controls

All limits are hard upper bounds, not promises of availability. Up to100 unique targets per run; each target array may contain at most100 entries. Profile handles are limited to100 characters; keywords to120 characters. Invalid/unsupported nonempty inputs fail validation before crawling rather than silently being billed as results.

| Field | Default | Accepted range / meaning |
|---|---:|---|
| `resultsPerInput` | 20 | 1–1,000 valid video candidates per creator; keyword snapshot cap20; direct URLs request1. |
| `maxItems` | 100 | 1–10,000 globally saved unique videos; plan/platform/event budgets can lower it. |
| `maxConcurrency` | 4 | 1–12 independent inputs. Each profile cursor remains sequential. |
| `requestTimeoutSecs` | 20 | 5–60 seconds per logical HTTP attempt, including redirects and body streaming. |
| `maxRetries` | 1 | 0–3 retries only for HTTP429/5xx; bounded backoff. No retry of login/private blocks,408 or transport errors. |
| `maxPagesPerInput` | 10 | 1–50 public profile pages, including initial HTML. Does not enable keyword pagination. |
| `maxRequests` | 200 | 1–1,000 total HTTP calls, including redirects/retries/fallback. |
| `maxRunTimeSecs` | 180 | 10–1,800 seconds, additionally shortened before the platform deadline. |
| `maxResponseBytes` | 1,048,576 | 65,536–2,097,152 decoded HTML/JSON bytes per response. |
| `maxTotalResponseBytes` | 33,554,432 | 65,536–134,217,728 decoded response bytes across the run. |
| `failOnPartialFailure` | false | Fail when an input is empty/failed, even if other rows were saved. |
| `proxy` | disabled | Optional explicit Apify/custom HTTP(S) proxy; no automatic paid upgrade. |

### Output

One deduplicated public video per dataset row: `platform: "kwai"`, `videoId`, `webVideoUrl`, `text`, `createTimeISO` (UTC or null), `duration` (seconds or null), `author`, optional `music`, `stats`, `statsPrecision`, `statsPrecisionByField`, `hashtags`, `sourceType`, `sourceInput`, `scrapedAt`, and `provenance` (first-party URL, source kind and fetch time).

Missing engagement values are **null, never invented zero**. Explicit source zeros remain zero. `exact` means an unrounded integer provided by Kwai, not independently verified accuracy. `rounded` marks abbreviated public values; check per-field precision. Author metadata is only populated when supplied; no fabricated followers/verification.

Optional `coverUrl` and `videoDownloadUrl` are first-party publicly exposed media/playback URLs, not stored media. They can be signed/expiring, region restricted or later unavailable. No image/video files are fetched, probed, stored or charged as extra result rows. An exposed playback URL does not grant copyright or permission to download/reuse it.

### Pricing

**$0.001 per saved video ($1 per 1,000), no startup fee.** The Apify Pricing tab is authoritative. The result event is the synthetic `apify-default-dataset-item`, automatically billed by Apify dataset writes. Duplicates, errors, empty inputs and RUN_SUMMARY do not create billable video rows.

**An event-charge / maximum-spend limit is not a cap on separate Apify platform usage.** The Actor stops new result writes at the remaining event/item capacity, but compute, transfer, storage or explicitly selected proxy traffic can still incur charges, including on an empty or failed run. Use the run's memory/timeout and the Actor's request/byte/time limits to bound those costs.

**The running user separately pays Apify platform usage** (compute, transfer, storage and any explicitly selected proxy traffic). No paid external API or downstream Actor is used. There is no automatic residential-proxy purchase. Confirmed Apify Free-plan runs (`APIFY_IS_AT_HOME=1` and `APIFY_USER_IS_PAYING=0`) have a developer-imposed cap of **50 saved videos per run**. Paid/unknown-plan runs have no such 50-row cap. `maxItems`, remaining event-charge budget, and `ACTOR_MAX_PAID_DATASET_ITEMS` (when supplied) are honored for all runs.

### Sources, reliability and status

- **Direct video:** parse first-party HTML embedded Nuxt state, with JSON-LD fallback. The requested video ID must match exactly; recommendations do not substitute for missing posts. A native unavailable flag blocks stale SEO data for the same ID.
- **Creator feed:** first resolve the public profile HTML, anchor its stable author ID, and consume only matching creator videos. Continue via the same public web profile cursor API. Missing/repeated cursors, empty/duplicate-only pages and safety budgets stop pagination.
- **Keyword discovery:** one anonymous first-party web discovery API response (up to20); on a non-budget API failure, one allowed HTML fallback can return a small snapshot, also capped20. No hidden browser, crawler-identity spoofing, signature generation or authenticated endpoint is used.

Private, deleted, age-restricted, region-restricted, empty or anonymous-access-blocked targets can return no rows. Zero results are not proof of deletion. Source fields, ranking and public accessibility can change without notice.

`RUN_SUMMARY` in the run's Key-Value Store contains saved/duplicate counts, per-input status/pages/warnings/errors, requests, received bytes, elapsed time and stop reason. It is not a billable video row. A normal status is `SUCCEEDED`; partial coverage uses `SUCCEEDED_WITH_WARNINGS` in the summary while the Apify run succeeds. Any zero-row run, strict partial failure or storage failure is `FAILED`. Dataset contents remain authoritative and existing rows remain billable if a later failure occurs.

Local negative test at implementation time: nonexistent profile `kwai_actor_nonexistent_849205731` and direct ID `9999999999999999999` returned0 rows and whole-run FAILED. The current known-public quick-start URL returned its exact video. Live availability is not guaranteed forever.

### Safety and limits

Default bounds: resultsPerInput20, maxItems100, concurrency4, timeout20s, retries1, profile pages10, HTTP requests200, wall-time180s, 1 MiB per decoded response and 32 MiB total response bytes. Input/schema maximums are hard bounds; retries and redirects count against requests. Only HTTP429/5xx retries use bounded backoff. Public routes/hosts and every redirect are allowlisted; private network destinations are rejected. Login, private, deleted or restricted content is not bypassed.

Direct HTTP is default. `proxy` is optional and explicitly user-configured; its traffic can cost extra. Custom HTTP(S) proxies must be publicly reachable. No user headers, Kwai credentials, cookies or browser fallback are accepted.

The parser never evaluates downloaded JavaScript: a data-only AST allowlist, pre-AST token/nesting bounds, total array-allocation budget and response-byte limits protect against executable or oversized embedded state.

Node22 ESM, Apify SDK, no browser dependencies. Default512MB; 256MB is available for small runs. Local developer tests: `npm ci && npm test`; live anonymous smoke: `npm run smoke`.

### FAQ

**Is this for Chinese Kuaishou?** No. It supports international `kwai.com`, not `kuaishou.com` or unrelated lookalike domains.

**Does a creator input return every video ever posted?** No. It follows only the available anonymous public profile feed/cursor and stops at availability, count, page, request, byte, time or payment limits. Every profile row must match the profile's stable author ID and handle.

**Is keyword discovery a full search engine?** No. It is a first-response public discovery snapshot of up to20 videos. Source ranking/relevance and regional access vary. Search pagination is intentionally unsupported; hashtags in output are extracted from captions, not collected from a hashtag feed.

**Are counters exact and complete?** Only source-supplied unrounded integers are labelled `exact`; this is provenance, not an independent audit. Abbreviations are `rounded`, unavailable fields are null, and explicit source zero is retained. Check `statsPrecisionByField`.

**Are media files downloaded?** No. Optional `coverUrl` / `videoDownloadUrl` are public first-party source URLs only. They may expire or be unavailable in another region. No download permission or copyright license is implied.

**Do I need Kwai login, an external API key or residential proxy?** No. There is no Kwai login, downstream Actor or external paid API. Proxy is off by default; only a user-selected proxy is used, and its usage can cost extra.

**What happens at a budget cap in strict mode?** Intentional item/event/request/time budget stops are recorded as skipped inputs or warnings, not as source failures. `failOnPartialFailure` still fails genuine empty/failed inputs; any whole run with zero saved videos fails. Saved rows remain available and billable after a later failure.

**Does a failed zero-row run have result fees?** No video rows means no result-item events, and this Actor has no startup fee. Separate platform usage can still be charged. The event/max-spend budget does not cap that separate usage.

**Are duplicates billed?** A video ID is saved once within a run, even if found in several inputs. Separate runs create separate snapshots/results.

Collect and use public data lawfully and in accordance with applicable platform terms.

# Actor input Schema

## `videoUrls` (type: `array`):

HTTPS kwai.com/@handle/video/ID URLs. Only the exact requested ID is returned, never recommendations. Up to 100 entries.

## `profiles` (type: `array`):

Public Kwai handles or https://www.kwai.com/@handle URLs. Feed rows, not standalone profile reports. Public cursor pagination only; no complete-history guarantee. Up to 100 entries.

## `searchKeywords` (type: `array`):

Public keyword-related discovery results, not guaranteed live search. At most the first API response (up to 20 items), or small embedded HTML snapshot. No search pagination. Up to 100 keywords, 120 characters each.

## `startUrls` (type: `array`):

Supported routes: /@handle, /@handle/video/ID, /search/keyword/video/ID and /discover/keyword. Only HTTPS kwai.com/www.kwai.com; short links and arbitrary routes are not supported. Up to 100 entries.

## `resultsPerInput` (type: `integer`):

Upper bound, not a promised count. Direct URLs return one video. Keyword snapshots still stop at the first response (up to 20 items), irrespective of a higher requested count.

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

Global unique-video output cap across all inputs; remaining charge/platform budgets and confirmed Free-plan cap50 can lower it.

## `maxConcurrency` (type: `integer`):

Number of independent targets fetched concurrently. Profile cursor pages stay sequential; dataset writes are serialized batches of up to20.

## `failOnPartialFailure` (type: `boolean`):

Default false: valid rows remain successful with warnings when another target is empty, blocked or missing. Zero saved rows always fail. Saved rows remain billable even if strict mode fails the run.

## `proxy` (type: `object`):

Omitted/disabled means direct HTTP: no proxy purchase. If enabled, your proxy choice is used; Apify proxy traffic may cost extra. No automatic residential upgrade. No Kwai login cookies/credentials accepted.

## `requestTimeoutSecs` (type: `integer`):

Timeout for one logical HTTP attempt, including its bounded redirects and response streaming.

## `maxRetries` (type: `integer`):

Bounded backoff only for HTTP 429 or server errors; never retry private/login blocks or transport errors.

## `maxPagesPerInput` (type: `integer`):

Includes initial HTML page and subsequent cursor API requests. Keyword discovery remains single-response; one HTML fallback may be attempted after a failed API call.

## `maxRequests` (type: `integer`):

Includes redirects, retries and fallback calls.

## `maxRunTimeSecs` (type: `integer`):

Collection wall-time budget, additionally shortened to finish before the platform run deadline. Already fetched rows can still be saved within output/charge budgets.

## `maxResponseBytes` (type: `integer`):

Limits streamed decoded HTML/JSON. Images and video files are not fetched.

## `maxTotalResponseBytes` (type: `integer`):

Global decoded HTML/JSON response-byte cap, including retries and all inputs. No image/video files are fetched.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.kwai.com/@jacyvieira33/video/5205185379965043809"
  ],
  "profiles": [],
  "searchKeywords": [],
  "startUrls": [],
  "resultsPerInput": 20,
  "maxItems": 100,
  "maxConcurrency": 4,
  "failOnPartialFailure": false,
  "proxy": {
    "useApifyProxy": false
  },
  "requestTimeoutSecs": 20,
  "maxRetries": 1,
  "maxPagesPerInput": 10,
  "maxRequests": 200,
  "maxRunTimeSecs": 180,
  "maxResponseBytes": 1048576,
  "maxTotalResponseBytes": 33554432
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `summary` (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 = {
    "videoUrls": [
        "https://www.kwai.com/@jacyvieira33/video/5205185379965043809"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("funny_ground/kwai-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 = { "videoUrls": ["https://www.kwai.com/@jacyvieira33/video/5205185379965043809"] }

# Run the Actor and wait for it to finish
run = client.actor("funny_ground/kwai-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 '{
  "videoUrls": [
    "https://www.kwai.com/@jacyvieira33/video/5205185379965043809"
  ]
}' |
apify call funny_ground/kwai-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,funny_ground/kwai-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/STD2yQ2eUhjPYCbbU/builds/y9Uifl2IfvSdVL4HP/openapi.json
