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.

apidojo/tiktok-comments-scraper

Estimated run cost

$0.00

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/7050551461734042926

  • Multiplies the bill without changing the price

    includeReplies

    same 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: true

  • maxItems

    ÷ $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.

    5000

  • customMapFunction

    freeno charge

    Transform, never filter

    Reshapes each row on the way out. For transformation only: accounts that use it to filter get blocked.

    (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.

TikTok Comments Scraper specificationsapidojo/tiktok-comments-scraper
Apify actorapidojo/tiktok-comments-scraper
InputsstartUrls · includeReplies · maxItems · customMapFunction
AcceptsVideo and photo post URLs. Not profiles, not hashtags
ReturnsOne record per comment, with an 11-field commenter profile on each
BillingA single event at $0.0003 a row. A reply is a row like any other
ThreadingparentId against id, a reply names the comment it answers
Login or proxyNeither. Public posts only
Reliability99.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

Every post you submit should return at least ten comments. 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
}

Replies may not always arrive

Users have reported that replies sometimes do not come through as expected, so 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
Field referenceEvery field a run returns
idThe comment’s own identifier
parentIdThe comment a reply answers. Group on it to rebuild a thread
textComment body, emoji intact
createdAtISO 8601 timestamp, ready to sort without parsing
likeCount · replyCountEngagement on the comment itself
isAuthorLikedWhether the video’s creator hearted this comment, a cheap relevance signal
commentLanguageLanguage TikTok detected on this comment, not on the account
awemeIdThe post the comment belongs to. Group on it across a batch
user.username · displayNameHandle and shown name of the commenter
user.bioTheir profile bio, shown in the sample output
user.regionTwo-letter account region, e.g. CO
user.languageThe account’s own language, which can differ from commentLanguage
user.hasEmail · hasPhoneBooleans saying contact details exist on the profile, not the details themselves
user.avatarUrl · coverImageProfile 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 for a clean run, plus one known gap in how replies come back.

  • 01

    Each URL should return at least ten comments

    It is a usage rule rather than 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

    Replies sometimes fail to come through when includeReplies is on. Verify against your own use case and 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, look for a "logging is too fast, some lines were skipped" message. The fix is smaller batches, not 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.

What a run costsQuery fee + $0.0005 × billable items
RunMethodCalculationCost
100 commentsReplies off100 × $0.0003$0.03
1,000 commentsReplies off1,000 × $0.0003$0.30
1,000 commentsReplies on, 3 each4,000 × $0.0003$1.20
5,000 commentsReplies off5,000 × $0.0003$1.50
10,000 commentsReplies on, 2 each30,000 × $0.0003$9.00
100,000 rowsEither way100,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?

Brand monitoring, sentiment work, lead generation and competitor research are the usual jobs. 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: 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)

# 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?

Not always. Users have reported replies sometimes not coming through as expected, so 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?

It collects 100–200 comments per second, or about 10,000 a minute.

Where TikTok Comments Scraper fits

Read one thread

A thousand comments for thirty cents, each with a profile attached.

startUrls: [one post URL] · includeReplies: false

Run this scraper free

Apidojo is not affiliated with TikTok or ByteDance.