TikTok Comments Scraper - Comments, Authors & Likes
Pricing
from $1.00 / 1,000 results
TikTok Comments Scraper - Comments, Authors & Likes
Scrape comments from TikTok videos using one or more video URLs or IDs. Extract comment text, author, likes, reply count and timestamp in clean structured data. Supports multiple videos in a single run with no login required.
Pricing
from $1.00 / 1,000 results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
25
Total users
6
Monthly active users
15 days ago
Last modified
Categories
Share
TikTok Comments Scraper: Video Comments, Replies & Recurring Monitoring
TikTok Comments Scraper turns a video, or a creator's recent videos, into a clean comments dataset. Paste video links or ids, or give usernames and let the actor walk each account's recent videos, then filter by likes, a keyword, replies or age before anything is written. Every run can resume a large crawl, track changes across a schedule, and optionally pipe results into your own apps.
Why This Scraper?
- Two ways to pick videos. Paste video links or ids, or give usernames and let the actor discover each account's recent videos.
- Filters that save money. A minimum like count, a required keyword, a reply exclusion or a days-old cutoff are all applied before a row is written, so filtered-out comments are never billed.
- Reply capture, honestly reported. Reply fetching is attempted per comment and switches itself off for a video once it is clear nothing is coming back, so you are never billed for a long stream of empty attempts.
- Recurring monitoring. Incremental mode classifies every comment NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED against a remembered baseline, so a scheduled run returns only what changed.
- Resume big crawls. Paste a previous run or dataset id and comments already collected there are skipped, so you never pay twice.
- Export to your apps. Optionally pipe results into Notion, Linear, Airtable or Apify through connectors, without changing the dataset.
- A quiet zero is never called a success. A run that collects nothing is reported clearly, so a scheduled job cannot fail silently.
Use Cases
- Brand and campaign teams: track sentiment and engagement on your own or a competitor's TikTok videos.
- Community managers: pull recent comments for moderation review, filtered to a keyword or a minimum like count.
- Researchers and analysts: build a dataset of comment text, likes and timestamps across a set of videos or creators.
- Growth and marketing teams: monitor a creator's newest videos on a schedule and get only new or changed comments.
- Customer feedback teams: search a brand account's videos for comments that mention a product or keyword.
Data You Get
Sample shape: values are illustrative placeholders, not from a live video.
| Field | Example |
|---|---|
recordType, sourceMode | "comment", "comments" |
commentId | "7685134583982015250" |
videoId | "7673909736131038495" |
videoUrl | "https://www.tiktok.com/video/7673909736131038495" |
isReply | false |
replyToCommentId | null |
text | "This made my day" |
authorUsername | "sampleuser" |
authorUid | "7000000000000000002" |
authorAvatarUrl | "https://p16-sign-va.tiktokcdn.com/sample-avatar.jpeg" |
authorNickname, authorRegion | "Sample User", "US" |
diggCount | 12 |
replyCommentTotal | 3 |
createTime, createTimeIso | 1700000000, "2023-11-14T22:13:20.000Z" |
scrapedAt | "2026-01-01T00:00:00.000Z" |
cid, uid, uniqueId, avatarThumbnail, createTimeISO | Aliases of commentId, authorUid, authorUsername, authorAvatarUrl, createTimeIso (same values, for drop-in use with other TikTok comment exports) |
repliesToId | Parent comment id on a reply, null on top-level comments (alias of replyToCommentId) |
videoWebUrl, submittedVideoUrl, input | The video's web URL, the video URL it was collected for, and the URL / id / username you entered |
likedByAuthor, pinnedByAuthor | true / false when the source says; null when it does not |
mentions, detailedMentions | @handles in the comment, and mentioned users with userId, userUniqueId, secUid and text offsets (empty arrays when none) |
images, sticker | Image URLs attached to the comment (empty array when none) and the sticker (id, staticUrl, animateUrl, width, height; null when none) |
changeType, changedFields, firstSeenAt, lastSeenAt | Incremental mode only: "NEW", ["diggCount"] (UPDATED rows), and ISO timestamps |
Every comment is one record of this shape; there is no separate record type for videos or authors. A video's own metadata is not scraped, only the comment fields above, and authorUsername, diggCount and a few other fields can come back null when the source omits them for a particular comment.
How to Use
- Pick a mode:
videos(paste the exact videos you care about) orsearch(give usernames and let the actor walk their recent videos). Search mode only follows a given account's own videos; it does not run a keyword or hashtag search across TikTok. - Add your video URLs or ids, or your usernames. Paste the full video URL or the numeric video id (accepted shapes are in the table below); short share links (vm.tiktok.com, vt.tiktok.com) are not resolved and are skipped with a warning. A private or removed video returns no comments.
- Turn on any filters you want (minimum likes, a keyword, replies on or off, a days window), and set Max comments per video (and Max videos per username in search mode) to control run size and cost.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.
Videos mode, the exact videos you care about:
{"mode": "videos","videoUrls": ["https://www.tiktok.com/@tiktok/video/7673909736131038495"],"maxCommentsPerVideo": 50}
Search mode, walk an account's recent videos:
{"mode": "search","usernames": ["tiktok"],"maxVideosPerUser": 10,"maxCommentsPerVideo": 50}
Filtered comments, only what matters:
{"mode": "videos","videoUrls": ["https://www.tiktok.com/@tiktok/video/7673909736131038495"],"minCommentLikes": 10,"commentKeyword": "love","excludeReplies": true,"commentsSinceDays": 30}
Recurring monitoring, only what changed since last time:
{"mode": "videos","videoUrls": ["https://www.tiktok.com/@tiktok/video/7673909736131038495"],"incrementalMode": true,"stateKey": "my-campaign"}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/tiktok-comments-scraper").call(run_input={"mode": "videos","videoUrls": ["https://www.tiktok.com/@tiktok/video/7673909736131038495"],"maxCommentsPerVideo": 50,})for comment in client.dataset(run["defaultDatasetId"]).iterate_items():print(comment["authorUsername"], comment["text"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/tiktok-comments-scraper').call({mode: 'videos',videoUrls: ['https://www.tiktok.com/@tiktok/video/7673909736131038495'],maxCommentsPerVideo: 50,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
About patience and caps
The source answers only a share of requests, and only during certain windows, some of which let almost nothing through. concurrency is how many requests the actor tries at once for a page, and pagePatienceSeconds is how long it keeps sampling before giving up on that page, so a window that opens partway through is still caught. maxAttemptsPerPage is an optional hard ceiling on top of that budget; leave it at 0 to let the patience budget alone decide. If a video keeps coming back as unreadable, raising pagePatienceSeconds (for example to 240) usually helps more than adding retries.
Resume and recurring updates
- Resume (
resumeFromRunId) continues one interrupted run: paste its run or dataset id and the actor skips every comment id already collected there, so you don't pay twice. It cannot be combined with Incremental mode in the same run. - Incremental mode (
incrementalMode) is for scheduled runs over the same videos or accounts. Each comment is classifiedNEW,UPDATED(withchangedFields),UNCHANGED(suppressed and not billed unlessemitUnchangedis on),REAPPEAREDorEXPIRED. A comment is only markedEXPIRED, and only whenemitExpiredis on, after a run that reached the natural end of every tracked video or account, with no block or resume, and with Max comments per video (and, in search mode, Max videos per username) set to0or high enough that the cap was never reached. In search mode, any video selection that leaves some of the account's listed videos out (Post sorting other than latest with a smaller Max videos per username, a post date window, or Exclude pinned posts) also counts as a partial walk and never marks anythingEXPIRED.REAPPEAREDonly follows a comment that was previously tombstonedEXPIREDand is then seen again.stateKeynames or shares the stored baseline; leave it empty to let the actor derive one from the videos, usernames, the number you entered in Max videos per username, the post selection options and the filters.UPDATEDfires when ANY output field changes, not only the comment text,diggCountorreplyCommentTotal: author flags such aslikedByAuthorandpinnedByAuthor,authorNickname,mentionsandimagescount too, andchangedFieldsnames what changed. OnlyscrapedAt,authorAvatarUrl(a signed, rotating picture link),avatarThumbnail,submittedVideoUrl,inputand the fields you list inignoreFieldsForChangesare left out; they stay in the output. A capped or partial walk never marks anythingEXPIRED. With Incremental mode off, output is exactly as before.
Send results into your apps (MCP connectors)
Optionally pipe the scraped comments into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each comment, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify, Settings, Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto the page where items should be created.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | videos | videos = scrape the URLs/ids you paste; search = walk usernames' videos. |
videoUrls | array | the 2 sample video URLs below (two different videos) | Video URLs or numeric video ids. Videos mode. Short share links are not supported. Changed from []: a run started with no input now scrapes the sample videos instead of nothing. |
usernames | array | [] (prefill: tiktok) | TikTok usernames, with or without @. Search mode. |
maxVideosPerUser | integer | 10 | How many of each account's recent videos to scan. 0 = the maximum (10,000). Search mode. |
profileSorting | string | latest | Search mode: latest, popular (most liked) or oldest. popular and oldest rank the account's 500 most recent videos. |
profileScrapeSections | array | ["videos"] | Search mode: only videos is available. reposts is not exposed by the source, so a reposts-only request scans nothing and logs a warning. |
oldestPostDateUnified | string | (none) | Search mode: skip videos older than this (YYYY-MM-DD, or an age like 30 / 2 weeks). |
newestPostDate | string | (none) | Search mode: skip videos newer than this date. |
excludePinnedPosts | boolean | false | Search mode: leave out videos pinned to the top of the profile. |
topLevelCommentsPerPost | integer | 0 | Stop after this many top-level comments per video that pass your filters (replies and filtered-out comments do not count). 0 = no separate limit. |
minCommentLikes | integer | 0 | Only keep comments with at least this many likes. 0 = no minimum. |
commentKeyword | string | (none) | Only keep comments containing this word or phrase, case-insensitive. |
excludeReplies | boolean | false | Drop replies and keep only top-level comments. |
commentsSinceDays | integer | 0 | Drop comments older than this many days. 0 = no date filter. |
fetchReplies | boolean | false | Also try to collect replies. The source usually returns none to automated requests. |
maxRepliesPerComment | integer | 5 | Reply cap per comment when reply fetching is on. 0 = the maximum (1,000). |
replyPatienceSeconds | integer | 10 | Time budget per reply request. |
maxCommentsPerVideo | integer | 20 | Comments to collect per video. 0 = the maximum (100,000). |
maxPagesPerVideo | integer | 0 | Reserved; currently has no effect. Use Max comments per video to limit run size. |
pagePatienceSeconds | integer | 120 | Per-page time budget; a page keeps being requested until it succeeds or this runs out. |
maxAttemptsPerPage | integer | 0 | Optional hard ceiling on requests per page. 0 = no ceiling. |
concurrency | integer | 20 | Parallel requests per page, 1 to 20. |
resumeFromRunId | string | (none) | Previous run or dataset id; comments already collected there are skipped. |
incrementalMode | boolean | false | Classify each comment against the remembered baseline and suppress unchanged rows. |
stateKey | string | (none) | Optional name for this monitoring campaign. Leave empty to derive one automatically. |
ignoreFieldsForChanges | array | [] | Incremental mode only. Extra top-level field names to exclude from UPDATED change detection, on top of the built-in exclusions (scrapedAt, authorAvatarUrl, avatarThumbnail, submittedVideoUrl, input). Any other output field, including the author flags, can trigger UPDATED. |
emitUnchanged | boolean | false | Also return (and bill) comments that did not change. |
emitExpired | boolean | false | Also return (and bill) comments that are gone, only after a complete tracked scan. |
mcpConnectors | array | (none) | Optional MCP connectors to also send results to. Never changes the dataset. |
notionParentPageUrl | string | (none) | Notion parent page for the Notion connector only. |
maxNotifyListings | integer | 50 | Cap on items sent to each connector per run. |
proxyTier | string | datacenter | datacenter or residential. Measured 2026-10-15 through Apify Proxy, datacenter and residential both pass only a few percent of requests (1/40 vs 0/40), so datacenter stays the default; residential is a fallback, not an improvement. |
proxy | object | (none, prefill: {"useApifyProxy": true}) | Leave unset to use Apify Proxy automatically; proxyTier above picks the group. |
Accepted URL shapes
| Shape | Example | Result |
|---|---|---|
| Canonical video | https://www.tiktok.com/@tiktok/video/7673909736131038495 | scraped |
| Canonical with query string | https://www.tiktok.com/@tiktok/video/7673909736131038495?lang=en | scraped |
| Mobile/legacy | https://m.tiktok.com/v/7673909736131038495.html | scraped |
| Bare numeric id | 7673909736131038495 | scraped |
Profile URL / @name / name in usernames (search mode) | https://www.tiktok.com/@tiktok | videos walked |
Profile URL in videoUrls | https://www.tiktok.com/@tiktok | skipped with a warning |
| Short share link | https://vm.tiktok.com/ZMabc123/ | skipped with a warning |
An empty videoUrls in videos mode scrapes the two sample videos, not nothing (the default lists two different sample videos).
Output Example
Sample shape: values are illustrative placeholders, not from a live video.
{"recordType": "comment","sourceMode": "comments","commentId": "7685134583982015250","videoId": "7673909736131038495","videoUrl": "https://www.tiktok.com/video/7673909736131038495","isReply": false,"replyToCommentId": null,"text": "This made my day","authorUsername": "sampleuser","authorNickname": "Sample User","authorUid": "7000000000000000002","authorAvatarUrl": "https://p16-sign-va.tiktokcdn.com/sample-avatar.jpeg","authorRegion": "US","diggCount": 12,"replyCommentTotal": 3,"createTime": 1700000000,"createTimeIso": "2023-11-14T22:13:20.000Z","scrapedAt": "2026-01-01T00:00:00.000Z"}
Output tabs and run summary
The Output tab offers three table views of the same comment rows: Comments (everything), Replies (every comment row, shown with reply-focused columns; replies are the rows where isReply is true, top-level comments appear too with an empty replyToCommentId) and Changes (incremental monitoring columns such as changeType and changedFields; filled when incrementalMode is on). A run with two or more distinct videos (by video id; the same video pasted in several shapes counts once) or two or more distinct usernames also saves a run summary under the key OUTPUT in the run's key-value store (comments pushed, unreachable videos, videos with no comments, filtered out).
Plan Requirement
This actor is pay-per-event, with three events as registered on the Pricing tab (which shows the current rates):
- Actor Start (
apify-actor-start): one event per GB of run memory (minimum one), charged once when the run starts. - Result (
apify-default-dataset-item): one event per comment written to the dataset. - Reply fetch attempted (
reply-fetch): one event per comment the actor tries to fetch replies for, only when Fetch replies is on, whether or not any reply rows come back. Replies are fetched, and charged, only for top-level comments that passed your filters (minimum likes, keyword, days window); comments dropped by a filter are never charged. Reply rows that do come back are billed as Results.
Proxy requirement. The default tier is datacenter. Measured 2026-10-15 through Apify Proxy, datacenter and residential both pass only a few percent of requests (1/40 vs 0/40), so datacenter stays the default; residential is a fallback, not an improvement. The comment API answers only a small share of requests per attempt on either tier. If a run returns too few comments, switch Proxy tier to residential under Proxy & connection and raise pagePatienceSeconds. Apify free plans do not include Apify Proxy or residential proxies. When the selected connection is refused, the actor prints:
The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable. For residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs.
and, once a page is served that way: "Served over the backup connection (the default connection was unavailable)." If no page can be loaded at all, the run fails with one of these messages:
- Proxy refused for the account: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid."
- Residential unavailable and backup also failed: "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested TikTok pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
- Any other connection failure: "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."
FAQ
How much does it cost?
This actor uses pay-per-event pricing: a charge when the run starts, a charge for each comment written to the dataset, and a small charge each time the actor attempts to fetch replies for a comment (only when Fetch replies is on), whether or not any reply rows come back. The Pricing tab shows the current rates. Use Max comments per video to cap the size, and cost, of any run.
Is it legal to scrape TikTok comments?
This actor collects only comments that are publicly visible on TikTok. You are responsible for how you use the data: follow TikTok's terms and the laws that apply to you, and get legal advice before any commercial use, especially one that stores or republishes personal data such as usernames or comment text.
Can I get only new or changed comments on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then classifies every comment against what was remembered from earlier runs on the same State key: NEW, UPDATED (with the changed fields listed), or UNCHANGED (suppressed and not billed unless Emit unchanged is on). A comment is only reported EXPIRED after a run that reached the natural end of every tracked video or account, with no block or resume, and with Max comments per video (and, in search mode, Max videos per username) set to 0 or high enough that the cap was never reached, with no post date window, Exclude pinned posts or ranked Post sorting trimming the account's video list, and only when Emit expired is on. A comment is only reported REAPPEARED once it was previously tombstoned EXPIRED and is then seen again. Because the source can return a slightly different comment list from one request to the next, a schedule may legitimately report a few new rows every run.
Why does turning on Fetch replies usually add nothing?
The source rarely returns reply rows to automated requests, even with Fetch replies on. The actor still tries, since some accounts and windows do return them, but gives up on a video after three attempts in a row come back empty. Every attempt is billed once, whether or not it returns a row, because the request itself has a real cost; any reply rows that do come back are billed separately as ordinary results.
Why did my run fail instead of returning an empty dataset?
A run that collects zero comments is reported as failed, not as a quiet empty success, whenever at least one video could not be read or returned no comments at all from the source. If every video was read and returned comments, but none were written because your filters removed them, a resumed run had already collected them, or incremental mode found nothing changed, the run finishes successfully with a message saying the result is genuinely empty.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.
π Want more TikTok data?
Pair this actor with these related scrapers from the same team:
| π± TikTok Profile, Hashtag, Search, Video & Trending Scraper Scrape TikTok without login. Extract profiles, bios, follower stats and videos; search by... | π± Tiktok Live Recorder Record TikTok live streams to MP4 with full metadata, all stream quality URLs, and... |
| π§© TikTok Shop Scraper Scrape TikTok Shop by keyword, category or product URL. Extract prices, discounts, sales... | π§© Dzen.ru Scraper Scrape Dzen.ru (ex Yandex.Zen): articles, videos and channels. Full article text with... |
| π± Lemon8 Feed Scraper Scrape Lemon8 feeds across 22 categories and 10+ regions. Extract posts, images, videos... | π§© Pikabu Scraper Scrape Pikabu (ΠΠΈΠΊΠ°Π±Ρ): keyword search, hot/new/best feeds, communities, tags, full story... |
π Browse all abotapi scrapers
π¬ Support
- π Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- π οΈ Need another site, extra fields or a private build? Email contact@abotapi.com.
- β Enjoying it? A quick review on the actor page helps other users find it.