I build Qovia, the API used in this post. The workflow itself (resolve a video, list the creator's top videos, compute engagement, collect comments, append to CSV) works with any Kuaishou data source; the CSV and pandas parts are plain Python.

Kuaishou (快手, Kwai abroad) is China's other big short-video app next to Douyin. Its audience leans more toward smaller cities and live commerce, and a lot of creators who matter for consumer brands there are barely visible on Douyin. If you're evaluating a Kuaishou creator for a campaign, or just want to know what's working in a niche, the questions are usually the same: how many plays do their best videos get, how engaged are viewers, what do they use as hashtags, and what do people say in the comments?

A note on the angle: the Kuaishou endpoints are built around a video link (its eid) or a creator's numeric uid, and from there you can walk to everything else. There's no Kuaishou-only keyword search; for finding a starting video by keyword, Qovia's cross-platform search can be limited to short-video apps (Step 2b). The hot board gives you a separate view of what's trending.

In this tutorial we'll write a script that:

  1. takes one Kuaishou video link and fetches its detail, including the creator's numeric uid,
  2. pulls the creator's hot videos (most liked first) and computes engagement stats,
  3. collects the first pages of comments under their top videos,
  4. snapshots the Kuaishou hot board,
  5. appends everything to CSV and summarizes it with pandas.

Setup

You need Python 3.9+, requests and pandas:

pip install requests pandas export QOVIA_KEY=sk-qovia-... # key from the Qovia console 

Get a key from Qovia. Requests authenticate with Authorization: Bearer <key> (an x-api-key header also works). The Kuaishou API page lists the five Kuaishou endpoints with a real request and response.

Step 1: a small request helper

Every response has the same envelope: the result is in data, and meta reports the charge (meta.billed, meta.charged_usd, meta.empty). Empty results come back as HTTP 200 with meta.empty: true and are not charged. Errors are {"error": {"code", "message", ...}} and are never charged. The video detail endpoint answers 404 for a post that doesn't exist or was deleted, so the helper turns that into None. A 429 carries a Retry-After header, so the helper waits and retries once:

import os import time import requests BASE = "https://api.qovia.ai" HEADERS = {"Authorization": f"Bearer {os.environ['QOVIA_KEY']}"} spent = 0.0 def get(path, **params): global spent params = {k: v for k, v in params.items() if v is not None} for attempt in range(2): r = requests.get(f"{BASE}{path}", headers=HEADERS, params=params, timeout=60) if r.status_code == 429 and attempt == 0: time.sleep(int(r.headers.get("Retry-After", "10"))) continue if r.status_code == 404: return None # deleted or missing, not charged  r.raise_for_status() body = r.json() spent += float((body.get("meta") or {}).get("charged_usd") or 0) return body.get("data") or {} 

Unknown parameters are rejected with 400 invalid_params, which is why the helper drops None values instead of sending them.

Step 2: from a video link to the creator

Kuaishou video links look like https://www.kuaishou.com/short-video/3xry7mhn2ywjyqg. The last part is the video's eid, and that's what the API takes; the long numeric photo ID is not accepted.

GET /v1/raw/kuaishou/video with video_id=<eid> returns one post: text (the caption), author (nickname, numeric uid, eid, and unique_id, the Kuaishou ID), metrics (plays, likes, comments, shares, saves) and extra with eid, published_at (Beijing time), video_dur_s, topics (hashtags), cover_url, ip_location and url.

The important bit for us is author.uid: the creator endpoint takes the numeric uid, not the eid.

import re EID_RE = re.compile(r"(3x[0-9a-z]{10,20})") def eid_from_link(link): m = EID_RE.search(link) if not m: raise ValueError(f"no Kuaishou eid in {link!r}") return m.group(1) def resolve(link): video = get("/v1/raw/kuaishou/video", video_id=eid_from_link(link)) if not video: raise SystemExit("video not found (deleted or private)") a = video["author"] print(f"{a.get('nickname')} (uid {a['uid']}, Kuaishou ID {a.get('unique_id')})") return video 

The 3x... pattern matches the format the API validates; if Kuaishou changes its link format, you can always paste the eid directly.

Step 2b (optional): find a starting video by keyword

If you don't have a link yet, POST /v1/search searches public content across Chinese platforms. With platforms: ["short_video"] (and types: ["original"] to skip reposts and comments) it is limited to "Kuaishou and other short-video apps", so the results can include other apps too; keep the ones whose url is a Kuaishou link. Dates are whole days in Beijing time (the last 7 days by default), 10 or 20 results per page. This endpoint is priced differently from the Kuaishou ones (see Cost).

def find_kuaishou_links(query, limit=20): global spent r = requests.post(f"{BASE}/v1/search", headers=HEADERS, timeout=60, json={"query": query, "platforms": ["short_video"], "types": ["original"], "limit": limit}) r.raise_for_status() body = r.json() spent += float((body.get("meta") or {}).get("charged_usd") or 0) items = (body.get("data") or {}).get("items", []) return [i["url"] for i in items if "kuaishou.com" in (i.get("url") or "") and EID_RE.search(i["url"])] 

I haven't checked what share of short_video results are Kuaishou for a given query, so treat this as a way to find candidates, then run the rest of the pipeline on a link you picked.

Step 3: the creator's hot videos and their stats

GET /v1/raw/kuaishou/creator_hot_videos takes the uid and returns the creator's hot posts, most liked first, 20 per page, as the same records as the video endpoint. Pass cursor back for the next page and stop when has_more is false.

This is the creator's hot list, not a full upload history, so treat the numbers as "what their best content does" rather than an average over everything they post. That's still the number most people want when comparing creators.

import csv from datetime import datetime, timezone from pathlib import Path VIDEO_FIELDS = ["checked_at", "creator_uid", "creator", "eid", "caption", "plays", "likes", "comments", "shares", "saves", "engagement_rate", "duration_s", "topics", "published_at", "url"] def now(): return datetime.now(timezone.utc).isoformat(timespec="seconds") def hot_videos(uid, max_pages=2): videos, seen, cursor = [], set(), None for _ in range(max_pages): page = get("/v1/raw/kuaishou/creator_hot_videos", uid=uid, cursor=cursor) for v in (page or {}).get("items", []): if v["extra"]["eid"] not in seen: seen.add(v["extra"]["eid"]) videos.append(v) if not (page or {}).get("has_more"): break cursor = page.get("cursor") return videos def video_row(v): m, ex, a = v.get("metrics") or {}, v.get("extra") or {}, v.get("author") or {} plays = m.get("plays") or 0 actions = sum(m.get(k) or 0 for k in ("likes", "comments", "shares")) return { "checked_at": now(), "creator_uid": a.get("uid"), "creator": a.get("nickname"), "eid": ex.get("eid"), "caption": (v.get("text") or "").replace("\n", " ")[:200], "plays": m.get("plays"), "likes": m.get("likes"), "comments": m.get("comments"), "shares": m.get("shares"), "saves": m.get("saves"), "engagement_rate": round(actions / plays, 4) if plays else None, "duration_s": ex.get("video_dur_s"), "topics": "|".join(ex.get("topics") or []), "published_at": ex.get("published_at"), "url": ex.get("url"), } def append_csv(path, fields, rows): path = Path(path) new_file = not path.exists() with path.open("a", newline="", encoding="utf-8") as f: w = csv.DictWriter(f, fieldnames=fields) if new_file: w.writeheader() w.writerows(rows) 

engagement_rate here is (likes + comments + shares) / plays. That's my definition, not Kuaishou's; pick whatever ratio fits your use, but keep it consistent across creators.

Step 4: what viewers say

GET /v1/raw/kuaishou/comments takes the same video_id (eid) and returns first-level comments with a cursor: about 30 on the first page, then about 20. Each comment has cid, text, likes, reply_count, ip_location, created_at (Beijing time), is_hot (Kuaishou's hot-comment flag), liked_by_author, images and author; the page also has total, cursor and has_more.

liked_by_author is a nice signal for creator research: it tells you which comments the creator chose to like, which often says something about how they manage their audience.

COMMENT_FIELDS = ["checked_at", "eid", "cid", "text", "likes", "reply_count", "ip_location", "created_at", "is_hot", "liked_by_author"] def comment_rows(videos, top=5, pages_per_video=1): rows = [] for v in videos[:top]: eid, cursor = v["extra"]["eid"], None for _ in range(pages_per_video): page = get("/v1/raw/kuaishou/comments", video_id=eid, cursor=cursor) for c in (page or {}).get("items", []): rows.append({ "checked_at": now(), "eid": eid, "cid": c["cid"], "text": (c.get("text") or "").replace("\n", " "), "likes": c.get("likes"), "reply_count": c.get("reply_count"), "ip_location": c.get("ip_location"), "created_at": c.get("created_at"), "is_hot": c.get("is_hot"), "liked_by_author": c.get("liked_by_author"), }) if not (page or {}).get("has_more"): break cursor = page.get("cursor") return rows 

Since creator_hot_videos is already sorted most liked first, videos[:top] is the creator's top five.

For a comment with a big reply_count, GET /v1/raw/kuaishou/comment_replies with video_id and comment_id=<cid> returns the thread (about 12 per page, same cursor rules). Each reply has parent_cid and reply_to (the user being answered). Kuaishou doesn't report a reply total, so just page until has_more is false. I left it out of the main loop to keep the call count predictable.

Step 5: what's trending right now

GET /v1/raw/kuaishou/hot_board takes no parameters and returns the whole main hot board in one call, about 50 entries. Each has rank, keyword, hot_value, views, label (the board's tag, such as 霸榜中 "holding the top"), pinned, word_id and a search url. Pinned entries come first and have no rank.

BOARD_FIELDS = ["checked_at", "rank", "keyword", "hot_value", "views", "label", "pinned"] def board_rows(): board = get("/v1/raw/kuaishou/hot_board") or {} ts = now() return [{"checked_at": ts, **{k: w.get(k) for k in BOARD_FIELDS[1:]}} for w in board.get("items", [])] 

Snapshot it every hour or so and you have a history of what Kuaishou users were looking at, which is handy context next to a creator's numbers.

Step 6: put it together

import sys def run(link): video = resolve(link) uid = video["author"]["uid"] videos = hot_videos(uid, max_pages=2) append_csv("ks_videos.csv", VIDEO_FIELDS, [video_row(v) for v in videos]) comments = comment_rows(videos, top=5, pages_per_video=1) append_csv("ks_comments.csv", COMMENT_FIELDS, comments) append_csv("ks_hot_board.csv", BOARD_FIELDS, board_rows()) print(f"{len(videos)} hot videos, {len(comments)} comments") print(f"spent this run: ${spent:.4f}") if __name__ == "__main__": run(sys.argv[1]) 
python3 ks_creator.py https://www.kuaishou.com/short-video/3xry7mhn2ywjyqg 

Output looks something like this (illustrative, not real data):

权少爱吃小汉堡🍔 (uid 228905802, Kuaishou ID quanshaogmod) 40 hot videos, 150 comments spent this run: $0.0090 

Step 7: summarize with pandas

import pa...
Source:https://dev.to/qovia/pull-kuaishou-creator-stats-and-video-comments-in-python-i85