TikTok Comments Scraper: the thread under the video
TikTok Comments Scraper reads the comments on a public TikTok video or photo post and returns each one with its engagement, its detected language, and the commenter’s own profile, bio included.
Free Apify plan: 5 runs a month, 10 items a run. No credit card.
- $0.30
- per 1,000 comments
- =
- replies cost the same as comments
- 100–200
- comments per second
- 99.68%
- run success, last 30 days
Read on 27 September 2026.
One URL field, one switch behind it
There is only one way in: the URL of a post. Everything else about a run is decided by whether replies are being collected and where the ceiling sits.
startUrls$0.0003per comment
No per-post fee
A video or photo post link. Profile and hashtag URLs are not accepted here, those are other actors.
https://www.tiktok.com/@user/video/7050551461734042926Multiplies the bill without changing the price
includeRepliessame ratemore rows
Rows, not a new fee
Off by default. On, the run walks the reply threads under each top-level comment as well.
includeReplies: truemaxItems÷ $0.0003budget to cap
Counts replies too
The ceiling across the whole run, shared between every post in it. Comments and replies both count against it.
5000customMapFunctionfreeno charge
Transform, never filter
Reshapes each row on the way out. For transformation only, the listing blocks accounts that use it to filter.
(o) => ({ user: o.user.username, text: o.text })
Nothing is billed per post, so a run over one video and a run over fifty cost the same for the same number of comments. The variable that moves the total is the switch.
| Apify actor | apidojo/tiktok-comments-scraper |
|---|---|
| Inputs | startUrls · includeReplies · maxItems · customMapFunction |
| Accepts | Video and photo post URLs. Not profiles, not hashtags |
| Returns | One record per comment, with an 11-field commenter profile on each |
| Billing | A single event at $0.0003 a row. A reply is a row like any other |
| Threading | parentId against id, a reply names the comment it answers |
| Login or proxy | Neither. Public posts only |
| Reliability | 99.68% of 306,977 runs succeeded in the last 30 days |
Top level only, the full thread, or a qualifying pass
Three runs worth setting up differently. The first two differ by one boolean; the third changes what you do with the output rather than how you ask for it.
How do you scrape comments from a TikTok video?
Paste the post URL and leave includeReplies off. A thousand comments cost $0.30, and nothing is charged for the post itself. This is the run to start with, because it gives a row count you can multiply.
Input
{
"startUrls": [
"https://www.tiktok.com/@user/video/7050551461734042926"
],
"includeReplies": false,
"maxItems": 1000
}1,000 × $0.0003=$0.30
What comes back
The comment record, Engagement, detected language, and whether the creator hearted it.
{
"id": "7277992603752203013",
"text": "TE AMO MI NIÑA💚💚💚💚💚💚🙌🌷",
"createdAt": "2023-09-12T17:28:42.000Z",
"likeCount": 0,
"replyCount": 0,
"commentLanguage": "es",
"isAuthorLiked": false,
"awemeId": "7050551461734042926"
}Ten comments is the floor per URL
The listing asks that every post you submit return at least ten comments, and says that scraping near-silent videos creates a negative margin and may be blocked. A list of posts chosen by view count rather than by comment count is the usual way into that.
How do you get replies as well as comments?
Set includeReplies to true. Replies arrive as ordinary rows at the same $0.0003, so the rate does not change, the row count does, by a factor the video decides. A thousand comments averaging three replies each is four thousand rows, or $1.20.
Input
{
"startUrls": [
"https://www.tiktok.com/@user/video/7050551461734042926"
],
"includeReplies": true,
"maxItems": 4000
}4,000 × $0.0003=$1.20
What comes back
The reply record, Same shape as a comment. parentId is what marks it as a reply.
{
"parentId": "42517992603752202618",
"id": "7277992603752203013",
"replyCount": 0,
"isAuthorLiked": false
}The listing does not promise replies always arrive
Its own FAQ says users have reported that replies sometimes do not come through as expected, and asks you to verify the output for your case. Treat a first run with the switch on as a measurement, count rows carrying a parentId, before you budget a large job around it.
Can you find leads in TikTok comments?
This is where the commenter block earns its place. Every row carries the account’s bio, region, language, and two booleans saying whether an email and a phone number are on the profile, so the filtering happens in your own data, at no extra cost.
Input
{
"startUrls": [
"https://www.tiktok.com/@user/video/7050551461734042926"
],
"maxItems": 5000
}5,000 × $0.0003=$1.50
What comes back
The commenter profile, Eleven fields, nested on every comment. No second scrape needed.
{
"user": {
"username": "21billie_eilish",
"displayName": "¡★!",
"url": "https://www.tiktok.com/@21billie_eilish",
"bio": "Billiestan for ever¡★!",
"region": "CO",
"language": "es",
"hasEmail": false,
"hasPhone": false
}
}hasEmail is a flag, not an address
The two contact fields are booleans: they tell you a profile advertises an email or a phone, not what it is. They are useful as a filter on a large pull, narrow to the accounts worth a second look, rather than as a contact list on their own.
A comment, and the person who left it
Two blocks per row. The comment side is compact; the commenter side is not, and it is what separates this output from a generic comment dump.
5
The comment
Always present
- id
- text
- createdAt, ISO 8601
- commentLanguage
- awemeId, the post it sits under
3
Engagement
Small on purpose
- likeCount
- replyCount
- isAuthorLiked, the creator hearted it
1
Thread position
parentId
- Absent or empty on a top-level comment
- Names the comment a reply answers
11
The commenter
user, on every row
- username, displayName, id
- url
- bio
- region, language
- hasEmail, hasPhone
- avatarUrl, coverImage
id | The comment’s own identifier |
|---|---|
parentId | The comment a reply answers. Group on it to rebuild a thread |
text | Comment body, emoji intact |
createdAt | ISO 8601 timestamp, ready to sort without parsing |
likeCount · replyCount | Engagement on the comment itself |
isAuthorLiked | Whether the video’s creator hearted this comment, a cheap relevance signal |
commentLanguage | Language TikTok detected on this comment, not on the account |
awemeId | The post the comment belongs to. Group on it across a batch |
user.username · displayName | Handle and shown name of the commenter |
user.bio | Their profile bio, demonstrated in the listing’s output sample |
user.region | Two-letter account region, e.g. CO |
user.language | The account’s own language, which can differ from commentLanguage |
user.hasEmail · hasPhone | Booleans saying contact details exist on the profile, not the details themselves |
user.avatarUrl · coverImage | Profile imagery, signed and time-limited |
Two language fields sit side by side and are not the same thing: commentLanguage is what this comment was written in, user.language is what the account is set to. A mismatch is often the most interesting row in a sentiment pull.
What a run needs to go well
Four conditions from the listing’s usage note and troubleshooting, plus one thing its own FAQ is honest enough to flag.
- 01
Each URL should return at least ten comments
The listing sets this as a usage rule, not advice: very low-engagement posts create a negative margin and may be blocked. Pick targets by comment count, not by views.
- 02
Post URLs only
startUrls takes video and photo post links. A profile URL or a hashtag page belongs to a different actor in the family and will not produce comments here.
- 03
Replies are not guaranteed
The listing’s FAQ states plainly that replies sometimes fail to come through when includeReplies is on, and asks you to verify against your own use case. Measure before you budget.
- 04
The ceiling is shared and counts replies
maxItems limits the run as a whole across every URL, and a reply consumes one of those rows exactly as a comment does. Turning the switch on halves your effective comment depth unless you raise it.
- 05
Large batches can outrun the log
If output stops partway, the listing points at a "logging is too fast, some lines were skipped" message and prescribes smaller batches rather than a retry.
Free accounts are in demo mode: 5 runs a month, 10 items each, no charge.
What the switch costs you
The rate never moves: every row is $0.0003, whether it is a comment or a reply. So the only question that matters for a budget is how many rows a thread produces, and that is the number worth measuring on a small run first.
| Run | Method | Calculation | Cost |
|---|---|---|---|
| 100 comments | Replies off | 100 × $0.0003 | $0.03 |
| 1,000 comments | Replies off | 1,000 × $0.0003 | $0.30 |
| 1,000 comments | Replies on, 3 each | 4,000 × $0.0003 | $1.20 |
| 5,000 comments | Replies off | 5,000 × $0.0003 | $1.50 |
| 10,000 comments | Replies on, 2 each | 30,000 × $0.0003 | $9.00 |
| 100,000 rows | Either way | 100,000 × $0.0003 | $30.00 |
The last row is the point: a hundred thousand rows costs $30 regardless of how they split between comments and replies. The switch changes how fast you get there, not the price of arriving.
Who reads comments for a living?
The listing points at brand monitoring, sentiment work, lead generation and competitor research. The commenter block is what decides which of those this actor is actually best at, a bio and a region on every row is a qualifying tool, not a mood ring.
Lead generation
Top level, then filter on the user block
Pull the commenters on a niche video and keep the ones whose bio and contact flags say they are running a business
$1.50 per 5,000 comments
Brand monitoring
Top level, across a campaign’s posts
Read the reaction to a launch with isAuthorLiked showing which replies the brand itself endorsed
$0.30 per 1,000
Sentiment analysis
Full threads
Build labelled corpora where commentLanguage lets you split the set before it reaches a model
$1.20 per 1,000 with 3 replies each
Competitor research
Top level, on a rival’s posts
Find the complaints an audience will make in public but never in a survey
$0.03 per 100-comment probe
Community teams
Full threads, scheduled
Catch unanswered questions by looking for top-level comments with a replyCount of zero
$9.00 per 30,000 rows
Wiring it up
The actor is apidojo/tiktok-comments-scraper. The one thing worth writing carefully is the reply check: the listing says threads may come back short, so measure the ratio on a small run and size the ceiling from what you actually got.
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
ACTOR = "apidojo/tiktok-comments-scraper"
def comments(urls, with_replies=False, cap=1000):
run = client.actor(ACTOR).call(run_input={
"startUrls": urls,
"includeReplies": with_replies,
"maxItems": cap,
})
return list(client.dataset(run.default_dataset_id).iterate_items())
rows = comments(["https://www.tiktok.com/@user/video/7050551461734042926"],
with_replies=True, cap=2000)
# The listing warns replies may not always arrive. Check, do not assume.
replies = [r for r in rows if r.get("parentId")]
print(f"{len(replies)} replies in {len(rows)} rows")
# Contact flags are booleans: use them to narrow, not to contact.
leads = [r for r in rows if r["user"]["hasEmail"] and r["user"]["bio"]]
for r in leads:
print(r["user"]["username"], r["user"]["region"], r["user"]["bio"][:40])import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('apidojo/tiktok-comments-scraper').call({
startUrls: ['https://www.tiktok.com/@user/video/7050551461734042926'],
includeReplies: true,
maxItems: 4000,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
// parentId is the whole threading model: absent means top level.
const threads = Object.groupBy(items.filter((c) => c.parentId), (c) => c.parentId);
const unanswered = items.filter((c) => !c.parentId && c.replyCount === 0);
console.log(Object.keys(threads).length, 'threads,', unanswered.length, 'unanswered');curl -X POST \
"https://api.apify.com/v2/acts/apidojo~tiktok-comments-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"startUrls":["https://www.tiktok.com/@user/video/7050551461734042926"],"includeReplies":false,"maxItems":1000}'JSON, CSV or Excel from the Storage tab, or read straight off the API. The Console preview shows a subset of fields, the full commenter block is in the download.
TikTok Comments Scraper FAQ
How much does it cost to scrape TikTok comments?
Every row is $0.0003, so a thousand comments cost $0.30 and a hundred thousand cost $30. Nothing is charged per post or per run.
Do replies cost extra?
Not at a different rate. A reply is an ordinary dataset item at $0.0003, so turning includeReplies on raises the total only by adding rows.
Will includeReplies always return the replies?
The listing’s own FAQ says users have reported replies sometimes not coming through as expected, and asks you to verify for your use case. Count the rows carrying a parentId on a small run before scaling.
What does the scraper return about each commenter?
An eleven-field user object: username, display name, ID, profile URL, bio, region, language, avatar, cover image, and two booleans for whether an email and a phone are on the profile.
Can I get commenters’ email addresses?
No. hasEmail and hasPhone say whether contact details exist on a profile; they do not return the details themselves.
Can I scrape comments from a profile or a hashtag?
Not with this actor. startUrls takes video and photo post URLs, profiles, hashtags, locations and music pages each belong to a different scraper in the family.
How do I tell a reply from a comment?
A reply carries a parentId naming the comment it answers. Top-level comments do not.
How fast is it?
The listing states 100–200 comments per second, which it also expresses as about 10,000 comments a minute.
Where TikTok Comments Scraper fits
Parent scraper
TikTok Scraper
The six TikTok scrapers side by side. Two of them touch comment data in passing; this is the one that takes a post URL and returns the conversation under it, with a profile attached to every line.
Sibling scrapers
Guides
- Qualifying leads from TikTok comment dataComing soon
- Finding unanswered questions in a comment setComing soon
- Splitting a comment corpus by detected languageComing soon
Read one thread
A thousand comments for thirty cents, each with a profile attached.
startUrls: [one post URL] · includeReplies: false
Apidojo is not affiliated with TikTok or ByteDance.