I build Qovia, the API used in this post. The approach (fetch comments as JSON, score them locally) works with any source that gives you Douyin comments, and the scoring part is plain Python.
Douyin is the Chinese version of TikTok, and its comment sections are where people say what they think of a product, a campaign or a news item. In this tutorial we'll write a script that:
- searches Douyin for a keyword and picks a video,
- pages through that video's comments,
- scores each comment's sentiment locally,
- prints a summary, with the most-liked positive and negative comments.
Setup
You need Python 3.9+, requests and snownlp (a small Chinese-text sentiment library):
pip install requests snownlp
export QOVIA_KEY=sk-qovia-... # key from the Qovia console
Get a key from Qovia. Requests authenticate with Authorization: Bearer <key>.
Step 1: one helper
Responses come in an envelope: the result is in data, billing info is in meta (meta.charged_usd). One helper covers everything:
import os
import requests
BASE = "https://api.qovia.ai"
HEADERS = {"Authorization": f"Bearer {os.environ['QOVIA_KEY']}"}
spent = 0.0
def get(path, **params):
global spent
r = requests.get(f"{BASE}{path}", headers=HEADERS, params=params, timeout=60)
r.raise_for_status()
body = r.json()
spent += float(body.get("meta", {}).get("charged_usd") or 0)
return body["data"] or {}
Step 2: find a video
GET /v1/raw/douyin/search takes a keyword and returns videos and image posts. The video id you need for comments is in extra.source_id:
results = get("/v1/raw/douyin/search", keyword="新能源汽车", count=10)
for i, item in enumerate(results["items"]):
print(i, item["metrics"]["comments"], item["text"][:40], item["extra"]["url"])
video_id = results["items"][0]["extra"]["source_id"]
Pick the video yourself rather than blindly taking the first one: the metrics.comments count tells you which ones have enough comments to be worth analyzing.
Step 3: page through the comments
GET /v1/raw/douyin/comments takes item_id (the video id), count (1 to 50) and cursor (starting at 0). Each page returns items, a cursor and has_more. Douyin can reorder comments between pages, so key them by cid to drop duplicates:
def all_comments(item_id, max_pages=10):
comments, cursor = {}, 0
for _ in range(max_pages):
page = get("/v1/raw/douyin/comments", item_id=item_id, cursor=cursor, count=50)
for c in page.get("items", []):
comments[c["cid"]] = c
if not page.get("has_more"):
break
cursor = page["cursor"]
return list(comments.values())
comments = all_comments(video_id)
print(len(comments), "comments, spent so far $", round(spent, 6))
Each comment has cid, text, likes, reply_count, created_at, ip_location, is_hot and an author. max_pages caps the cost of a run, since every call that returns data is charged.
Step 4: score the sentiment
SnowNLP(text).sentiments returns a number between 0 and 1 (higher is more positive). It was trained on e-commerce reviews, so treat it as a rough signal for Douyin slang and emoji-heavy comments, not as ground truth. The 0.6 and 0.4 cut-offs below are my own choice; tune them on a sample you've labeled by hand.
from collections import Counter
from snownlp import SnowNLP
def label(score):
if score >= 0.6:
return "positive"
if score <= 0.4:
return "negative"
return "neutral"
for c in comments:
c["score"] = SnowNLP(c["text"]).sentiments
c["label"] = label(c["score"])
counts = Counter(c["label"] for c in comments)
liked = {k: sum(c["likes"] for c in comments if c["label"] == k) for k in counts}
print("comments:", dict(counts))
print("likes by label:", liked)
Counting likes as well as comments matters: one popular negative comment can outweigh many quiet positive ones, and likes lets you see that.
Step 5: read the extremes
Numbers are a starting point. Always read the comments behind them:
for k in ("positive", "negative"):
top = sorted((c for c in comments if c["label"] == k), key=lambda c: c["likes"], reverse=True)[:5]
print(f"\nTop {k}")
for c in top:
print(f"{c['likes']:>6} {c['text']} ({c['ip_location']})")
Illustrative output (made-up numbers and text, to show the shape only):
comments: {'positive': 120, 'neutral': 80, 'negative': 50}
likes by label: {'positive': 3000, 'neutral': 900, 'negative': 2100}
Top negative
932 <comment text> (<province>)
Replies and a cross-check
- Replies under a comment come from
GET /v1/raw/douyin/comment_replies. Check the endpoint's reference for its parameters before using it; it's worth fetching only for comments with a highreply_count. - To check your local scores against another view,
POST /v1/searchis a cross-source search with asentimentfilter (negative,neutral,positive) and aplatformsfilter (use["douyin"]). It searches posts and content rather than the comments of one video, so it answers a different question: how is a topic discussed on Douyin overall.
r = requests.post(
f"{BASE}/v1/search",
headers=HEADERS,
json={"query": "新能源汽车", "platforms": ["douyin"], "sentiment": ["negative"], "limit": 10},
timeout=60,
)
data = r.json()["data"]
print(data["total"], data["by_platform"])
Cost
Prices in the catalog: GET /v1/raw/douyin/search and GET /v1/raw/douyin/comments are $0.5 per 1,000 calls each; POST /v1/search is $5 per 1,000 calls. Empty results are not charged. Print the spent counter at the end of every run so the cost is never a surprise. Full details are on the pricing page.
Limits
- Only public content; no private accounts or DMs.
- Local sentiment models make mistakes on sarcasm, slang and mixed-language comments. Sample and read.
- The comments you get are the ones the API returns for the pages you fetch, not necessarily every comment on the video.
If you build on this, tell me in the comments what you'd want added.
This article was written with the help of AI.
Top comments (0)