Answers
Why do I get fewer Douyin comments than the video's comment count shows?
Short answer
The counter on a Douyin video includes replies, and some comments are folded, hidden or deleted, so the number of public top-level comments is always lower. okdata returns every top-level comment Douyin serves, about 20 per page at $0.002, each with its reply count, so you can check coverage: 2,000 comments cost $0.20.
The video shows 2,416 comments. Your export has 619 rows. The first instinct is that the scraper is broken, and sometimes it is: a failed signature or a block can end paging early with no error. But even a perfect collector will not reach 2,416, and it helps to know why before you chase a bug.
Where the comments go
- Replies count too. Douyin's counter includes replies under comments. A video with 600 top-level comments and 1,800 replies shows 2,400.
- Folded and hidden comments. Douyin folds low-quality or reported comments, and creators can hide or delete them. Those are not served publicly.
- Very large threads. On videos with tens of thousands of comments, Douyin may stop serving top-level comments before the end.
Check your coverage
Douyin video comments returns top-level comments with a replies count on each. Sum 1 + replies over everything you collected and compare it with metrics.comments from video detail. If the two are close, you have everything public. If your sum is far lower and the last page still said has_more: true, your collection stopped early.
Cost
| Comments collected | Calls | Cost |
|---|---|---|
| 620 (the example above) | 31 | $0.062 |
| 2,000 | 100 | $0.20 |
| plus the video's counter | 1 | $0.002 |
Code for the check
import os, requests
API, HEAD = "https://api.okdata.online", {"Authorization": f"Bearer {os.environ['OKDATA_API_KEY']}"}
vid = "7301234567890123456"
total = requests.get(API + "/v1/douyin/videos/get", headers=HEAD, params={"id": vid}, timeout=60).json()["data"]["item"]["metrics"]["comments"]
seen, params = 0, {"id": vid}
while True:
body = requests.get(API + "/v1/douyin/videos/comments/list", headers=HEAD, params=params, timeout=60).json()
seen += sum(1 + (c["replies"] or 0) for c in body["data"]["items"])
if not body["page"]["has_more"]:
break
params["cursor"] = body["page"]["next_cursor"]
print(f"covered {seen} of {total}")
Every page is a separate charge; if a page fails, it is not charged and you can resend it with the same cursor.
If you need the replies themselves
This catalog lists top-level comments only. Reply text needs another tool: MediaCrawler and TikTokDownloader can collect replies with a logged-in account, and their issue trackers show that reply endpoints break more often than top-level ones. For most sentiment work, top-level comments weighted by likes and reply counts carry the signal. See word-of-mouth analysis.
Updated