# TikTok Comments Scraper — Replies, Likes & Commenters (`scrapersdelight/tiktok-comments-scraper`) Actor

Scrape every comment on any TikTok video or photo post: text, like count, reply count, timestamp, language, pinned and creator-liked flags, mentions, hashtags, comment images, and the commenter's handle, nickname, id, secUid and avatar. Optional threaded replies. No login, no cookies.

- **URL**: https://apify.com/scrapersdelight/tiktok-comments-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Social media, Videos, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.25 / 1,000 per comment returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## 💬 TikTok Comments Scraper

**Paste a TikTok video URL, get every comment as clean rows.** Text, like count, reply count,
exact timestamp, language, thread position, and the full commenter profile — handle, nickname,
user id, **secUid**, avatar and profile URL. Replies included on a toggle, flagged and linked to
their parent so you can rebuild the thread from a flat CSV.

No login. No cookies. No browser. One plain HTTP request per 50 comments — which is why this is
priced at **$0.00025 per comment, the lowest per-comment price measured in the lane** (see the
pricing table below for the live ladder it was measured against on 2026-09-02).

***

### 🎯 What does TikTok Comments Scraper do?

It reads TikTok's own public comment feed for a post and turns it into a dataset — one row per
comment, ready for CSV, Excel, JSON, or straight into a Google Sheet.

- 💬 **Every comment**, paged until TikTok stops serving more
- ↩️ **Threaded replies** on a toggle — `isReply` + `parentCommentId` on every row
- ❤️ **Like count and reply count** per comment, as numbers you can sort on
- 👤 **Commenter identity**: `@handle`, display name, user id, **secUid**, avatar, profile URL
- 🕒 **Exact timestamps** — ISO 8601 *and* raw Unix, so date filters just work
- 📌 **Pinned by creator** and **liked by creator** flags — the two strongest engagement signals
- 🏷️ **Author labels** (e.g. `Creator`) and **verified** badges
- 🔗 **Mentions** parsed out of the text with the mentioned user's id and secUid
- 🖼️ **Comment images/stickers** as direct URLs
- 🎬 **The video caption** joined onto every row, free — no second request

Works on **video posts and photo posts**, full URLs, `vm.tiktok.com` / `vt.tiktok.com` short links
(resolved for you), and bare numeric video ids.

***

### 📊 Measured field fill — real numbers, not claims

Measured 2026-09-02 on **576 unique comments** across **3 real videos** (12 pages, 50 per page,
**zero duplicate comment ids** across the whole set), and confirmed on a **live 723-row run on
Apify** (2 videos, replies on: 300 top-level + 423 replies, 723/723 unique ids, 105 seconds):

| Field | Fill | Notes |
|---|---|---|
| `commentId`, `likeCount` | **100%** | |
| `text` | **99.8%** offline / **97.4%** live | the misses are image-only comments, which have no text |
| `createdAt`, `createdAtTimestamp`, `language` | **100%** | ISO 8601 + Unix seconds |
| `commenterUsername`, `commenterNickname` | **100%** | `@handle` and display name |
| `commenterId`, `commenterSecUid` | **100%** | secUid is the key TikTok's own profile endpoints take |
| `commenterProfileUrl`, `commenterAvatarUrl` | **100%** | |
| `videoId`, `videoUrl`, `videoCaption` | **100%** | caption comes free with the comments |
| `commentShareUrl` | **100%** offline / **90.3%** live | TikTok's own share link; TikTok's share service intermittently blanks it for a whole page. Reported honestly, not faked |
| `pinnedByCreator`, `likedByCreator`, `isReply` | **100%** | always a real boolean, never null |
| `commenterVerificationLabel` | **2.3%** / **1.2%** live | only verified and institution accounts have one |
| `authorLabel` | **0.5%** / **0.7%** live | TikTok only labels a few authors (e.g. `Creator`) |
| `imageUrls` | **1.6%** / **2.1%** live | only comments that attached an image |
| `mentions` | **0.2%** / **3.6%** live | only comments that @-tag someone |
| `hashtags` | **0%** in both samples | comment hashtags are genuinely rare; parsed from the same `text_extra` structure that produced the mentions above, so the code path is exercised — but we will not quote a fill rate we have not observed |

`replyCount` is **100% on top-level comments**. Reply rows carry `null` there — TikTok's reply
endpoint does not return a nested-reply count, and inventing a `0` would be a lie.

***

### 🚀 Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `videoUrls` | array | *(sample videos)* | Video URLs, photo-post URLs, short links, or bare ids |
| `maxCommentsPerVideo` | integer | `100` | Top-level comments per video. `0` = everything TikTok pages |
| `includeReplies` | boolean | `false` | Also fetch replies under each comment that has them |
| `maxRepliesPerComment` | integer | `20` | Cap per comment when replies are on. `0` = all |
| `proxyConfiguration` | object | Apify Proxy on | Rotates the exit IP. Switch to RESIDENTIAL if you see blocked videos |

```json
{
  "videoUrls": [
    "https://www.tiktok.com/@zachking/video/7095025543627705643",
    "https://www.tiktok.com/@guinnessworldrecords/video/7172183875538488582"
  ],
  "maxCommentsPerVideo": 200,
  "includeReplies": false,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

Leaving `videoUrls` empty is safe: the run falls back to the two documented sample videos and still
produces output, rather than failing.

***

### 📦 Output

```json
{
  "videoId": "7095025543627705643",
  "videoUrl": "https://www.tiktok.com/@zachking/video/7095025543627705643",
  "videoCaption": "#answer to @spidermark790",
  "commentId": "7521666400186221368",
  "text": "Search: original video 7 billion views",
  "likeCount": 104,
  "replyCount": 2,
  "createdAt": "2025-06-30T09:07:40.000Z",
  "createdAtTimestamp": 1751274460,
  "language": "en",
  "isReply": false,
  "parentCommentId": null,
  "replyToCommentId": null,
  "pinnedByCreator": false,
  "likedByCreator": false,
  "authorLabel": null,
  "commenterUsername": "warukaxender",
  "commenterNickname": "WarukA Xender",
  "commenterProfileUrl": "https://www.tiktok.com/@warukaxender",
  "commenterId": "7124324677996381190",
  "commenterSecUid": "MS4wLjABAAAApLC7tj0vaAVzpRNua7nJXsBlXK1XcKgo7...",
  "commenterAvatarUrl": "https://p16-common-sign.tiktokcdn.com/...",
  "commenterVerified": false,
  "commenterVerificationLabel": null,
  "mentions": [],
  "hashtags": [],
  "imageUrls": [],
  "commentShareUrl": "https://m.tiktok.com/v/7095025543627705643.html?...",
  "scrapedAt": "2026-09-02T04:23:24.839Z"
}
```

***

### 💰 Pricing

**$0.00025 per comment delivered.** Pay per event, no monthly fee, no charge for starting a run.

You are billed as rows are delivered, not before — so if you set a spend cap, the run stops at your
cap instead of handing you rows you already paid past. Blocked or unreadable videos return no rows
and cost nothing.

| Comments | Cost |
|---|---|
| 1,000 | $0.25 |
| 10,000 | $2.50 |
| 100,000 | $25.00 |

Replies are billed as comments, because they are comments.

**The live ladder this was priced against** (read from each actor's `pricingInfos` on the Apify
Store API, 2026-09-02): `$0.00028 + $0.00005/start` · `$0.0003` · `$0.0003 + $0.003 per query` ·
`$0.0005 + $0.02 per video` · `$0.0005` · `$0.00198` · `$0.0025` · `$0.00499 + $0.09/start`, with
the lane leader at `$0.00125` per result. $0.00025 is under all of them, with no start fee.

***

### ⚙️ How it works (and why it stays cheap)

TikTok's comment feed is served by a public JSON endpoint that takes four parameters and a
`referer` header. That is the entire request — no signature, no `msToken`, no `X-Bogus`, no cookie,
no headless browser. A browser-based competitor pays for 4 GB of Chromium on every run; this pays
for one HTTP GET per 50 comments. That cost difference is the price difference.

Three things were measured and built around, rather than assumed:

1. **Sending empty signature parameters is worse than sending none.** The long browser-style query
   string returns HTTP 200 with a **zero-byte body**; the minimal four-parameter query returns
   65 KB of comments. The query string here is built from a fixed key set and nothing is appended.
2. **A page size above ~50 silently loses comments.** Measured on one video: `count=20` → 19 rows,
   `count=50` → 48 rows, `count=100` → **still 48 rows but the cursor advances 100**, `count=200` →
   48 rows and the cursor advances 200. Delivery is capped near 50 while the cursor moves by the
   requested count, so a bigger page skips roughly half the comment section every time. The page
   size is pinned to 50.
3. **A zero-byte HTTP 200 is a block, not an empty comment section.** Those two outcomes are
   counted separately and named separately in the run summary. A blocked video is reported as
   **blocked**, never quietly as "this video has no comments".

Reliability details: retries with backoff and a fresh proxy session on every soft block or 5xx;
a wall-clock budget derived from the run's own timeout so a big job stops cleanly instead of being
killed mid-page; and crash handlers that end the run cleanly with an explanatory status message.
The run only **fails** when the output would be genuinely invalid — nothing read at all and every
video blocked.

***

### 🌍 Proxies

Apify Proxy is on by default (automatic/datacenter group). Verified working 2026-09-02 from a
residential home IP **and** through Apify Proxy `auto`, `BUYPROXIES94952` (datacenter) and
`RESIDENTIAL` — all three returned HTTP 200 with full comment payloads.

If a long, high-volume run starts reporting blocked videos in the status message, switch
`proxyConfiguration` to `RESIDENTIAL`. That is the documented escalation, and the status message
tells you when you need it.

***

### ❓ FAQ

**Do I need a TikTok account, cookies, or an API key?**
No. Nothing is authenticated. You paste a public video URL.

**Can it scrape replies to comments?**
Yes — turn on `includeReplies`. Replies come back as ordinary rows with `isReply: true` and
`parentCommentId` pointing at the comment they answer, so a flat CSV still reconstructs the thread.

**Will it get ALL the comments on a video with 60,000 of them?**
It pages until TikTok stops returning more. TikTok itself caps how deep the public feed goes, and
that depth varies by video — so treat very large videos as "as many as TikTok will serve", not a
guaranteed 100%. Set `maxCommentsPerVideo: 0` to take everything available.

**Does it work on photo posts (image carousels)?**
Yes. `/photo/<id>` URLs use the same comment endpoint.

**Do short links work?**
Yes. `vm.tiktok.com` and `vt.tiktok.com` links are redirect-resolved to the numeric video id first.

**What happens if a video is private, deleted, or has comments turned off?**
It returns no rows for that video, the run still succeeds, and the status message names the video
and says it returned zero comments — separately from any video that was actually blocked.

**Do I get commenter email addresses?**
No. TikTok does not publish them, and this actor does not guess or enrich them. What you get is the
handle, secUid and profile URL — which is what you need to look a commenter up.

**What is `secUid` and why should I care?**
It is TikTok's stable, non-numeric user key. TikTok's own profile and feed endpoints accept it,
so it is the field that makes a comment row joinable to anything else you scrape.

**Can I run this on many videos at once?**
Yes — pass as many URLs as you like in `videoUrls`. Each is capped independently by
`maxCommentsPerVideo`.

**Is the output stable enough to schedule?**
Yes. It is a normal Apify Actor: schedule it, pipe it to a webhook, or call it from the API. Field
names do not change without a version bump.

**How is a comment counted for billing?**
One charge per row actually written to your dataset. Duplicates are removed before delivery, and
rows past your spend cap are never delivered *or* charged.

***

### ⚖️ Legal & fair use

This actor reads **publicly visible** comments — the same data any logged-out visitor sees on the
video page. It does not log in, does not bypass a paywall or an age gate, does not touch private
accounts, and does not collect email addresses, phone numbers or any contact data (TikTok does not
publish those).

You are responsible for how you use the output: comply with TikTok's Terms of Service, and with
GDPR/CCPA and any other applicable privacy law where personal data is involved. Comment text and
usernames are personal data in many jurisdictions — have a lawful basis before you store or process
them, and honour deletion requests. Do not use the output to harass, profile or target individuals.

For scraping research and dataset use, see Apify's guidance on
[web scraping and the law](https://blog.apify.com/is-web-scraping-legal/).

# Actor input Schema

## `videoUrls` (type: `array`):

The TikTok posts to read comments from. Accepts a full video URL (https://www.tiktok.com/@user/video/123…), a photo-post URL (/photo/123…), a vm.tiktok.com / vt.tiktok.com short link (it is redirect-resolved for you), or a bare numeric video id. Leave empty to run the two documented sample videos.

## `maxCommentsPerVideo` (type: `integer`):

Stop after this many top-level comments per video. Popular videos carry tens of thousands, so the default is deliberately small — raise it once you know the scope you want. 0 = every comment TikTok will page through.

## `includeReplies` (type: `boolean`):

Also fetch the replies under every comment that has any. Replies arrive as normal rows with `isReply: true` and `parentCommentId` set, so you can rebuild the thread from a flat CSV. This costs one extra request per comment with replies and bills per reply row.

## `maxRepliesPerComment` (type: `integer`):

Cap on replies fetched under each comment when "Include replies" is on. 0 = every reply. Ignored when replies are off.

## `proxyConfiguration` (type: `object`):

Apify Proxy is on by default and rotates the exit IP, which is what keeps TikTok from rate-limiting a long run. Datacenter (the default automatic group) was verified working; if you see blocked videos in the run summary, switch this to RESIDENTIAL.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.tiktok.com/@zachking/video/7095025543627705643",
    "https://www.tiktok.com/@guinnessworldrecords/video/7172183875538488582"
  ],
  "maxCommentsPerVideo": 10,
  "includeReplies": false,
  "maxRepliesPerComment": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `comments` (type: `string`):

The dataset of scraped TikTok comments (one item per comment; replies included as rows when the reply option is on).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "videoUrls": [
        "https://www.tiktok.com/@zachking/video/7095025543627705643",
        "https://www.tiktok.com/@guinnessworldrecords/video/7172183875538488582"
    ],
    "maxCommentsPerVideo": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/tiktok-comments-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "videoUrls": [
        "https://www.tiktok.com/@zachking/video/7095025543627705643",
        "https://www.tiktok.com/@guinnessworldrecords/video/7172183875538488582",
    ],
    "maxCommentsPerVideo": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/tiktok-comments-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "videoUrls": [
    "https://www.tiktok.com/@zachking/video/7095025543627705643",
    "https://www.tiktok.com/@guinnessworldrecords/video/7172183875538488582"
  ],
  "maxCommentsPerVideo": 10
}' |
apify call scrapersdelight/tiktok-comments-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/tiktok-comments-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/lKCQnwEngtbfIWDNK/builds/VjmfzTPt36iVueX7F/openapi.json
