TikTok comments in Python for sentiment analysis (2026)
Two endpoints: TikTok video search to pick the videos, then TikTok video comments, page by page, for each one. The worked example below is 11 calls and costs $0.022; a failed call is not charged.
Updated
The job: you want to know how people feel about a product or topic on TikTok, measured on what they write, not on view counts. That means a few hundred real comments from the videos where the discussion happens, in a file a sentiment model or an LLM can read. TikTok's own Research API has comments, but only for researchers TikTok approves through an application; what a company can use instead covers that route. Here each call is an HTTPS GET with an API key, no TikTok account or login, and requests is the only library.
You end with tiktok_comments.csv: one row per comment with the video it came from, the video's language, the comment text, likes, reply count and time, deduplicated and with empty rows dropped.
Step by step
Set the key once: export OKDATA_API_KEY=okd_.... Every answer has code ("ok" on success); lists come in data.items with a page that carries the next cursor.
Step 1: find the videos where people talk
TikTok video search takes query and sort: relevance (the default), most_liked or latest. TikTok's search leans on relevance, so latest is not a strict date order; filter on published_at yourself if dates matter. One call returns about 30 videos for $0.002, each with id, the caption in text, lang, published_at, author.username, and metrics.views, metrics.likes, metrics.comments and metrics.shares. Pick by metrics.comments: a video with 4 million views and 80 comments is no use for this.
curl -G "https://api.okdata.online/v1/tiktok/videos/search" -H "Authorization: Bearer $OKDATA_API_KEY" \
--data-urlencode 'query=air fryer' --data-urlencode 'sort=most_liked' \
--data-urlencode 'fields=id,text,lang,author.username,metrics.comments,published_at'
Step 2: page through the comments
TikTok video comments takes the video's numeric ID as id (such as 7429815063927400734). One call returns about 50 top-level comments for $0.002, each with id, text, likes, replies (the reply count), published_at, author.username and, where TikTok shows one, ip_location. For the next page, send page.next_cursor back as cursor, unchanged; stop when page.has_more is false.
curl -G "https://api.okdata.online/v1/tiktok/videos/comments/list" -H "Authorization: Bearer $OKDATA_API_KEY" \
--data-urlencode 'id=7429815063927400734'
Step 3: the full script
The script searches once, keeps the VIDEOS videos with the most comments, reads up to PAGES_PER_VIDEO pages under each, cleans the text and writes the CSV. The same number of pages per video keeps one viral video from drowning out the rest. The helper retries rate limits and busy upstreams with the same Idempotency-Key, so a retry is never charged twice, and skips videos deleted since the search.
import csv
import os
import re
import time
import uuid
import requests
API = "https://api.okdata.online"
KEY = os.environ["OKDATA_API_KEY"]
QUERY = "air fryer"
VIDEOS = 5 # videos to read
PAGES_PER_VIDEO = 2 # comment pages per video; each page is one call
def call(path, **params):
headers = {"Authorization": f"Bearer {KEY}", "Idempotency-Key": str(uuid.uuid4())}
for attempt in range(4):
body = requests.get(API + path, headers=headers, params=params, timeout=60).json()
if body["code"] == "ok":
return body
if body["code"] == "not_found":
return None # deleted or private: not charged
if body.get("retry") in ("yes", "same_key"):
time.sleep(body.get("retry_after_seconds") or 2 ** attempt)
continue
raise RuntimeError(f"{body['code']}: {body['message']} (request {body['request_id']})")
raise RuntimeError(f"{path}: still failing after 4 tries")
def comments(video_id, max_pages):
cursor = None
for _ in range(max_pages):
body = call("/v1/tiktok/videos/comments/list", id=video_id, **({"cursor": cursor} if cursor else {}))
if body is None:
return
yield from body["data"]["items"]
if not body["page"]["has_more"]:
return
cursor = body["page"]["next_cursor"]
def clean(text):
return re.sub(r"\s+", " ", text or "").strip()
videos = call("/v1/tiktok/videos/search", query=QUERY, sort="most_liked")["data"]["items"]
videos = sorted(videos, key=lambda v: v["metrics"]["comments"] or 0, reverse=True)[:VIDEOS]
seen = set()
with open("tiktok_comments.csv", "w", newline="", encoding="utf-8-sig") as f:
out = csv.writer(f)
out.writerow(["video_id", "video_lang", "video_caption", "comment_id", "text", "likes", "replies", "published_at", "author"])
for v in videos:
for c in comments(v["id"], PAGES_PER_VIDEO):
text = clean(c["text"])
if not text or c["id"] in seen:
continue
seen.add(c["id"])
out.writerow([v["id"], v["lang"], clean(v["text"])[:120], c["id"], text,
c["likes"], c["replies"], c["published_at"], c["author"]["username"]])
print(f"{len(seen)} comments from {len(videos)} videos")
That is the worked example: 1 search and 10 comment pages, about 500 comments. For a bigger sample, 20 videos at 4 pages each is 80 comment calls, $0.16 plus the search.
Step 4: score it
The CSV is ready for whatever does the scoring. Three habits make the result more honest:
- Weight by likes. A complaint with 900 likes speaks for more people than one with none. Keep
likesnext to the score and report both the share of comments and the share of likes. - Handle language. Comments carry no language field.
video_langis a fair hint for the thread; mixed threads need a multilingual model or a language detector. - Read a sample. Sarcasm and slang fool every model. Read 30 rows of each label before you trust the split.
An LLM works well on batches of 50 to 100 comments with the video caption as context: ask for a label per comment plus the three most common praises and complaints. TikTok comments for sentiment analysis shows that end of the pipeline.
Good to know
- Top-level comments only. Each comment carries its reply count, not the reply text, so your row count will be lower than the video's
metrics.comments, which includes replies. - Order. Comments come in TikTok's own order; there is no sort parameter. Two pages per video is a sample of what TikTok puts first, not a random sample.
- Locations.
ip_locationis filled only where TikTok shows one; treat it as optional. - Trimming.
fields=id,text,likes,replies,published_atkeeps the answer short when a model reads it directly. It does not change the price. - Free cases. A video deleted between search and comments answers
not_found, which is free, like rate limits and every other error. The same request with the sameIdempotency-Keywithin 24 hours comes back withmeta.replayed: trueand no new charge.
For a single video exported to Excel, exporting TikTok comments to CSV is the shorter path.
What the example costs
One search for air fryer, then 2 pages of comments (about 100) under each of the 5 videos with the most comments
| Endpoint | Calls | Price | Subtotal |
|---|---|---|---|
| TikTok video search | 1 | $0.002 | $0.002 |
| TikTok video comments | 10 | $0.002 | $0.02 |
| Total | 11 | $0.022 |
Prices in force when this page was built. A failed call costs nothing.
In your AI (MCP)
Connect the MCP server once, then say what you want. The agent finds these endpoints, reads their prices and calls them on your balance.
SaySearch TikTok for air fryer, take the 5 videos with the most comments, get about 100 comments under each and tell me what people like and complain about, with a rough positive/negative split.
What the agent sends to the call tool first
{
"endpoint": "tiktok.videos.search",
"params": {
"query": "ai agents"
},
"idempotency_key": "<a new UUID>"
}