TikTok Comments Scraper By Real Comment Filter avatar

TikTok Comments Scraper By Real Comment Filter

Pricing

$19.99/month + usage

Go to Apify Store
TikTok Comments Scraper By Real Comment Filter

TikTok Comments Scraper By Real Comment Filter

TikTok Comments Scraper extracts comments from public TikTok videos at scale. Collect comment text, usernames, likes, replies, and timestamps. Ideal for sentiment analysis, audience research, moderation workflows, and social media insights.

Pricing

$19.99/month + usage

Rating

0.0

(0)

Developer

Scraper Engine

Scraper Engine

Maintained by Community

Actor stats

0

Bookmarked

16

Total users

1

Monthly active users

6 days ago

Last modified

Share

TikTok Comments Scraper — Spam Score, Media URLs, Duplicate Groups

TikTok Comments Scraper By Real Comment Filter extracts the comment section under any public TikTok video as structured JSON and cleans it before you are billed: the comment text, the commenter's uniqueId and commenterProfileUrl, diggCount, createTimeISO, decoded mediaUrls for picture and sticker comments, and an auditable spamScore with the exact spamSignals behind it. Sixty-five keys per row, no HTML parsing, no TikTok login. Paste video links or creator usernames and press Start.

⚠️ The filter is a rule, not a model — and the optional AI runs on your own provider bill

Two things get conflated in comment scrapers that mention spam, so they are separated here before anything else.

The "real comment filter" is deterministic and free. spamScore, spamRiskLevel and spamSignals are computed inside the Actor from data that is already in the comment payload — a link pattern in the text, the same text repeated elsewhere in the run, three or more @mentions, text that is nothing but emoji, and a commenter who appears three or more times in the run. Five weighted signals, no model, no network call, no key, no extra cost. hideLikelySpam filters on that number and nothing else. It works on every run, including a completely default one.

aiEnhancement is the only feature that calls an external model, it is off by default, and Apify does not bill it. Switching it on sends comment text to the provider implied by aiModel — Anthropic for claude-*, Google for gemini-*, OpenAI for gpt-* / o1 / o3, xAI for grok-*, DeepSeek for deepseek-*, Perplexity for sonar*, Mistral for mistral-*, and OpenAI as the fallback for any name that matches nothing. Those calls are charged to your account with that provider, on their pricing, entirely separately from your Apify usage. Comments go out in batches of 25 with each comment truncated to 400 characters, so token spend scales with how many comments survive your filters, not with how many videos you queue.

Comment text leaves Apify when AI enhancement is on. A TikTok comment is written by an identifiable person. Under GDPR that model provider becomes a processor in your processing chain: it belongs in your record of processing activities, needs to be covered by your controller-processor terms, and should appear where your privacy notice lists recipients and international transfers. With aiEnhancement off, no comment text goes anywhere except your own Apify dataset.

If AI is on and no key is found, the run does not fail — and the rows are still charged. The Actor reads aiApiKey first, then the provider's environment variable (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, XAI_API_KEY, DEEPSEEK_API_KEY, PERPLEXITY_API_KEY, MISTRAL_API_KEY). If both are empty it logs a warning and carries on: every comment is still scraped, still written and still charged as a row_result, with aiIsSpam, aiCategory and aiSpamReason all null. Unlike a silent no-op, the reason is written into the row — aiError reads no API key supplied, so you can detect it in the dataset rather than only in the log.

The model is called after the filters, never before them. De-duplication, the media filter, the spam filter and duplicate-text collapsing all run first, and only the rows that survive are sent to your provider. You never pay a model to classify a comment the Actor then discards.

A fake, spam or bot label is an inference about a person's post, not a fact. spamScore is a weighted rule of thumb and aiIsSpam / aiCategory / aiSpamReason are a language model's opinion of a truncated string. Both can be wrong about a real person's real comment. No accuracy figure is claimed anywhere in this README, for either layer, and the defaults ship with filtering off so the score stays advisory until you decide otherwise.

What is TikTok Comments Scraper By Real Comment Filter?

It is an Apify Actor that reads TikTok's public comment list endpoint for a video, returns one dataset row per top-level comment with replies nested inside it, and adds three layers the raw payload does not give you: duplicate comment IDs removed before anything is pushed or billed, image and sticker comments decoded into real URLs instead of arriving as blank text, and a per-row spam score whose individual signals are all visible as their own columns.

No TikTok account, login or session cookie is required, and the input schema accepts none. The Actor reads the same public endpoints a signed-out visitor's browser calls. An LLM provider key is needed only if you switch aiEnhancement on.

It is built for social listening and community teams triaging a noisy comment section, brand-safety and influencer analysts checking whether engagement is real, researchers assembling comment corpora, and developers or AI engineers feeding comment text into their own pipelines.

What TikTok comment data is publicly available to scrape?

TikTok's comment endpoint serves a signed-out visitor the full comment body and the commenter's public identity: text, likes, exact timestamp, handle, nickname, avatar, numeric user ID, the comment's own permalink, TikTok's language tag for the comment, and the image or sticker attached to it. What it withholds is anything belonging to a private or restricted video, and anything about the commenter beyond their public profile stub.

Data CategorySigned-out visitorGated behind a TikTok login or the account owner
Comment text, like count, exact timestamp✅ Public
Commenter handle, nickname, avatar, numeric user ID, sec_uid✅ Public
Comment permalink and TikTok's own comment language tag✅ Public
Image and sticker comments (image_list, cmt_sticker_struct)✅ Public
Reply threads under a comment, and the video's total comment count✅ Public
TikTok's pinned, top-comment, author-liked and author-reply markers✅ Public
Comments on private, friends-only or age-restricted videosLogin and eligibility — never returned
Comments the creator deleted, hid or filteredNot served to anyone
Commenter's follower count, bio, email or phoneNot present in the comment payload at all
Whether a commenter is a bot❌ TikTok publishes no such flag❌ Inferred inside the Actor — see the spam and AI sections

TikTok Comments Scraper By Real Comment Filter only returns publicly visible data — what any signed-out visitor sees under the video. Nothing behind a login wall. The duplicate, spam and AI columns are not fetched from TikTok at all; they are derived from the comments the endpoint already returned.

What data can I extract with TikTok Comments Scraper By Real Comment Filter?

Every charged row carries five things: the comment and who wrote it, the picture or sticker it contains, whether anyone else in the run wrote the same thing, a spam score with its individual signals exposed, and — only if you asked for it — a model's opinion. Sixty-five top-level keys are written on every row of every run.

The tables below are split by where each value comes from, because that distinction matters legally as well as analytically: what TikTok published, what the Actor computed from a rule, and what a language model wrote.

The comment, the commenter and the video — published by TikTok

Field NameDescription
cidTikTok's comment ID. The de-duplication key, and the field to keep for subject-access requests
textThe comment body, verbatim. "" on a picture-only comment — check isMediaOnlyComment before treating that as broken
createTimeComment timestamp as a Unix epoch integer
createTimeISOThe same instant as YYYY-MM-DDTHH:MM:SS.000Z in UTC. "" when TikTok gave no usable timestamp
diggCountLikes on the comment
replyCommentTotalTikTok's own count of replies under the comment — the true total, independent of how many you fetched
uidThe commenter's numeric TikTok user ID
uniqueIdThe commenter's handle, without the @
nicknameThe commenter's display name, or null
secUidTikTok's opaque secondary user ID, or null
commenterProfileUrlhttps://www.tiktok.com/@{uniqueId}, built from the handle. null when TikTok returned no handle
avatarThumbnailFirst avatar thumbnail URL, or ""
commentLanguageTikTok's own language code for the comment, or null. Never defaulted to "en"
commentLinkTikTok's share permalink for the individual comment, or null
likedByAuthorTikTok's is_author_digged flag — the creator liked this comment
pinnedByAuthorTikTok's author_pin flag
isTopCommentParsed from TikTok's sort_tags string — the comment sits in TikTok's top list
isAuthorReplyParsed from the same sort_tags string
isHighPurchaseIntentFlagTikTok's own is_high_purchase_intent flag, passed through raw and honestly named. It is TikTok's label, not a buying signal this Actor stands behind
replyScoreTikTok's internal sort_extra_score.reply_score, or null
showMoreScoreTikTok's internal sort_extra_score.show_more_score, or null
foldStatusTikTok's raw fold_status moderation value
noShowTikTok's raw no_show moderation flag
userBuriedTikTok's raw user_buried moderation flag
commentStatusTikTok's raw comment status value
repliesToIdThe comment this one replies to, or null on a top-level comment
videoIdThe video's numeric ID, from the comment's aweme_id
videoTotalCommentsTikTok's total comment count for the video, taken from the first page. null on nested replies
videoWebUrlCanonical video URL, query string stripped, short links resolved
submittedVideoUrlA second copy of videoWebUrl — despite the name it holds the resolved URL, not the string you submitted
inputThe exact URL this row was collected from. In creator mode this is the discovered video URL, not the username

The four raw moderation flags (foldStatus, noShow, userBuried, commentStatus) are passed through unmodified and were constant on the samples the Actor was built against. They are exposed so you can check them yourself, not because they are claimed to detect anything.

Image and sticker comments — decoded from TikTok's payload

A large share of a busy TikTok comment section is pictures and stickers, and those comments arrive from the API with an empty text. Left undecoded they look like blank or broken rows. These ten keys turn them into usable data.

Field NameDescription
mediaUrlsArray of full-size image URLs from image_list, preferring origin_url and falling back to crop_url. [] when there are none
mediaThumbnailsMatching cropped previews, preferring crop_url and falling back to origin_url
mediaDimensionsArray of { "width", "height" } objects, one per image. Both values are null when TikTok did not report them — never invented
mediaCountNumber of images on the comment
stickerUrlAnimated sticker URL from cmt_sticker_struct, falling back through mid, low and static resolutions. null when absent
stickerIdThe sticker's ID as a string, or null
hasMediaRule-derived. true when the comment carries an image or a sticker
mediaTypeRule-derived. "image", "sticker", "imageAndSticker" or null
isMediaOnlyCommentRule-derived. true when there is media and no text — the key that tells an empty text apart from a broken row
isEmptyWithoutMediaRule-derived. true for a genuinely empty comment with no media either

Duplicates and the spam score — computed inside the Actor, rule-derived

None of the fields below is published by TikTok. Every one is calculated from the comments this run already downloaded, across the whole run rather than per video — so duplicateTextCount and commenterCommentsInRun describe your run's dataset, not the video in isolation.

Field NameDescription
isDuplicateTexttrue when another row in this run has the identical text after case and whitespace normalisation
duplicateTextCountHow many rows in the run share that exact text. 0 on empty-text rows, which are never grouped
duplicateTextGroupIdStable 1-based group ID, largest group first. null when the text is unique or empty
isCollapsedDuplicateGrouptrue only when collapseDuplicateTexts was on and this row is the surviving representative of a repeated-text group. false on every row otherwise
mentionsArray of handles found in the text by regex. TikTok's own text_extra entity list measured empty on the live samples, so the regex is the real path rather than a fallback
mentionCountLength of mentions
containsLinktrue when the text matches a URL, a www. host, or a bare domain on a common TLD
linkInTextThe first matched link string, or null
isMentionStuffedtrue at three or more mentions
isEmojiOnlyTexttrue when the text is non-empty and consists entirely of emoji, symbols and variation selectors
commenterCommentsInRunHow many rows in this run share this uid
isRepeatCommenterInRuntrue at three or more comments from the same uid in this run
spamSignalsArray naming exactly which signals fired: containsLink, repeatedTextInRun, mentionStuffing, emojiOnlyText, repeatCommenterInRun
spamScore0100. Weights: link 45, repeated text in run 30, mention stuffing 30, emoji-only 15, repeat commenter 15, summed and capped at 100
spamRiskLevel"high" at 60 or above, "medium" at 30 or above, "low" above 0, "none" at 0. These thresholds are fixed and independent of spamScoreThreshold

The weighting is the whole design: at the default spamScoreThreshold of 60, no single weak signal hides a row on its own, but two independent ones do. A link alone scores 45 and stays. A link plus emoji-only text scores 60 and is hidden once you switch hideLikelySpam on. Because spamSignals is a column, you can always reconstruct why a score landed where it did instead of trusting the number.

One signal was deliberately left out: a default-profile-picture heuristic, which returned zero hits on two independent live samples during the build and was judged not to generalise. It is not shipped and not scored.

The AI columns — model output, not TikTok data and not a fact

Four keys are written by a language model, and only when aiEnhancement is on. They are always present in the row; they are null unless a verdict actually landed.

Field NameDescription
aiIsSpamThe model's boolean opinion on whether the comment is spam — AI-derived inference about a person's post, not a verified fact. null when AI is off, unkeyed, or the call failed
aiCategoryOne of genuine, question, praise, criticism, joke, engagement_bait, promo_spam, scam, as returned by the model. The Actor does not validate the string against that list — whatever the model wrote is what you get
aiSpamReasonThe model's short justification, requested at a maximum of 12 words. Present in the row, not in the default table view
aiErrorWhy no verdict exists: no API key supplied, model returned no verdict for this row, or an exception type and message truncated to 180 characters. null on a successful classification. Present in the row, not in the default table view

aiIsSpam never removes a row. hideLikelySpam filters on the rule-based spamScore only, so the AI layer adds columns and changes nothing about what you are charged for.

Two honest limits on this layer. A picture-only comment is sent to the model as the literal placeholder [image or sticker comment, no text] — the model never sees the image, so any verdict on such a row is an opinion about a placeholder. And comments longer than 400 characters are truncated before being sent, so a long comment is judged on its opening.

Replies, and the run's own accounting

Field NameDescription
type"comment" on every pushed row; "reply" on nested reply objects
isReplyfalse on every pushed row; true on nested reply objects
parentCidnull on a pushed row; the parent comment's cid on a nested reply
repliesArray of nested reply objects, up to maxRepliesPerThread each. [] when the thread has none or replies are disabled
scrapedAtUTC ISO-8601 timestamp, identical on every row of one run

Nested reply objects carry 46 keys — everything from the TikTok-published and media tables above, plus one key that exists only on replies, isCreatorReply, set from the reply endpoint's label_text marker. Replies do not carry the duplicate, spam, AI or scrapedAt fields: those are computed on top-level rows only.

Beyond the dataset, each run writes two files to the run's key-value store. output.json holds the full row array, and run-summary.json holds 22 accounting keys including rowsReturnedByApi, duplicateRowsRemoved, duplicateRowsCaughtWhilePaging, duplicateRowPercent, duplicateTextGroups, topDuplicateTexts, rowsWithMedia, mediaOnlyRows, rowsWithSpamSignal, rowsHighSpamRisk, collapsedRowsRemoved, rowsPushed, aiClassified and aiFailed — the audit trail for what the filters actually did.

🤖 Add-on: Need additional TikTok data?

A comment section answers "what did the audience say". TikTok Profile Scraper answers the other half — take a creator's post feed with per-post views, likes and captions, then feed its webVideoUrl values straight into this Actor's videoUrls. Tiktok Video Scraper (Creator Profile & Contact) starts from individual video URLs and returns the creator behind them with public contact details attached. For discovery before you pick videos, TikTok Trending Hashtags Scraper By Multiple Countries and Tiktok Trending Hashtags Analytics Scraper By Country surface what is rising.

Why not build this yourself?

TikTok publishes developer APIs, and neither serves an arbitrary third party's comment section on demand. The Display API returns data only for users who have completed an OAuth login with your own registered app, and the Research API is application-only with an eligibility review — check TikTok's current published developer documentation for the terms, which change and are not reproduced here. Reading a competitor's or an influencer's comments means reading the public endpoint, and that is the part worth understanding before you write it yourself.

The specific costs this Actor absorbs:

  • HTTP 200 is not success on TikTok. A refused signed endpoint answers with a zero-byte 200. A WAF challenge answers with a small HTML page, also 200. This Actor never keys on status code alone: a 0-byte body is classified as empty_body, and any body at or under 3,000 bytes that does not contain "comments" is classified as challenge. Trust the status code and you will confidently report an empty comment section on a video with thousands of comments.
  • The comment endpoints are param-sensitive to the point of fragility. One extra query parameter on the comment list or reply list URL turns the response into that zero-byte 200. The request is built with exactly the parameters that work and nothing else.
  • The API hands the same comment back on more than one page. Overlapping pagination is normal, not exceptional, and it grows with pagination depth. In a pay-per-result scraper every one of those repeats is a billed row. Getting it right means de-duplicating on cid at two separate points — while paging, and again globally across every video in the run — before anything is pushed.
  • The reply endpoint has to be paged, not sliced. Fetching cursor 0 once and slicing the result caps replies at the page size no matter what the caller asked for. Real pagination means following the API's returned cursor, with a fallback for when it does not advance, and a page ceiling so a pathological thread cannot loop.
  • Empty text is usually a picture. image_list and cmt_sticker_struct are nested several levels deep with multiple resolution fallbacks each. Skip that decoding and a large slice of a busy comment section reads as blank rows in your dataset.
  • Creator discovery needs a browser; comments do not. Resolving "the last 10 videos from a handle" means scrolling a profile page and intercepting its XHR responses, which is why headless Chromium is installed. The comment fetch itself is plain HTTP and never uses it.
  • The proxy ladder. Direct works most of the time and costs nothing; residential is needed sometimes and costs money. Escalating in the right order, and only ever upward, is the difference between a cheap run and an expensive one.

How to use TikTok Comments Scraper By Real Comment Filter

The Actor runs on Apify. Start it from the Apify Console or call it through the Apify API — your Apify token is the only credential involved.

  1. Open the Actor on Apify and click Try for free
  2. Paste video links into TikTok video links (videoUrls). Full URLs, short vm. and vt. links and bare numeric video IDs all work. Or leave it empty and put handles into TikTok usernames (creatorUsernames) instead
  3. Set Comments to keep per video (maxCommentsPerVideo) and Replies to follow per comment (maxRepliesPerThread). Set replies to 0 to skip the reply endpoint entirely and run considerably faster
  4. Optionally narrow the dataset: What to do with media comments (mediaCommentHandling), Collapse identical comments into one row (collapseDuplicateTexts), and Hide rows that look like spam (hideLikelySpam) with its Spam score needed to hide a row (spamScoreThreshold)
  5. Leave Ask an AI model to double-check each comment (aiEnhancement) off unless you want the model columns and are willing to pay your own provider for them
  6. Click Start, then export as JSON, CSV, Excel or XML, or read the dataset through the Apify API

No field is marked required in the schema, but one of videoUrls or creatorUsernames is required in practice. A run with both empty logs Provide at least one video URL or one creator username. and exits immediately — no rows pushed, nothing charged. A run whose creator discovery finds no videos logs No video URLs to process. Exiting. and does the same. The Console form ships with both fields pre-filled with example values, so pressing Start without editing anything scrapes the example video with 10 comments and 3 replies per thread, no key and no proxy needed.

If both fields are filled, the video links win and the usernames are ignored — including the pre-filled example username.

How to scrape comments from multiple TikTok videos in one run

videoUrls is a list, so bulk is the normal mode rather than a special case. Duplicate URLs in that list are removed before scraping, and up to 10 videos are processed concurrently with up to 10 reply threads in flight at a time. maxCommentsPerVideo applies per video — ten videos at 100 comments each is a 1,000-row budget before the filters take their cut.

Creator mode is the other bulk path: put handles in creatorUsernames, set videosPerCreator, and the Actor discovers each creator's video URLs first and then scrapes every one of their comment sections. Discovered URLs are de-duplicated across creators too, so a video reachable from two handles is scraped once.

What can you do with TikTok comment data?

  • 🧹 A community manager triaging a viral video's comment section sorts on spamScore and spamSignals, answers the isTopComment rows first, and uses duplicateTextCount to spot the copy-pasted wave without reading it forty times.
  • 🕵️ A brand-safety analyst vetting an influencer before a campaign reads commenterCommentsInRun, isRepeatCommenterInRun and duplicateTextGroupId across a creator's last 20 videos to see whether the engagement comes from a broad audience or a handful of accounts repeating themselves.
  • 🖼️ A creative researcher studying how an audience actually responds sets mediaCommentHandling to onlyMedia and pulls mediaUrls, stickerUrl and mediaType to build a picture-reply corpus that text-only scrapers throw away as blank rows.
  • 🌍 A regional social team filters on commentLanguage and takes text, uniqueId and commentLink for the languages they staff, using commentLink to jump straight back to the comment on TikTok to reply.
  • 📉 A product researcher tracking complaints exports text, diggCount and createTimeISO for every comment on a launch video and ranks by likes to find the criticism the audience itself upvoted.
  • 🤖 An AI engineer building a social-listening agent indexes text with commentLanguage, spamRiskLevel, videoWebUrl and createTimeISO as retrieval metadata, filtering on spamScore at query time so the agent answers from real comments rather than from a wall of engagement bait.

Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run returning typed JSON.

How does TikTok Comments Scraper By Real Comment Filter handle rate limits and blocking?

Three mechanisms, all automatic.

Block detection keyed on response size, not status code. TikTok answers a refused signed endpoint with HTTP 200 and zero bytes, and a WAF challenge with HTTP 200 and a small HTML page. Both are classified as blocks and logged as BLOCK SIGNAL lines: a 0-byte body becomes empty_body, and a body of 3,000 bytes or fewer that does not contain "comments" becomes challenge. A 200 that is neither, but is not parseable JSON, is logged as unparseable rather than treated as an empty result.

A proxy ladder that only ratchets upward. Every run starts on the container's direct connection with no proxy at all. On a block the Actor switches to an Apify datacenter proxy — using your proxyConfiguration if you enabled Apify Proxy, otherwise requesting one itself — and then to Apify residential with three fresh exit IPs, sticking at residential for the rest of the run once it gets there. Each switch is logged as a PROXY EVENT line. Across a fallback chain the Actor keeps whichever attempt returned the most rows.

Retries and pacing. Each request retries up to three times, with backoff on HTTP 403, 429 and 503 and on network errors and timeouts. Comment pages are fetched with a randomised 0.2–0.6 second pause between them and reply pages with 0.15–0.4 seconds, and pagination is bounded — up to 400 comment pages per video and 60 reply pages per thread. Comment requests time out at 30 seconds, reply requests at 15.

There is no CAPTCHA solving in this Actor and none is claimed. Headless Chromium is used only for creator discovery — resolving a username into video URLs — never for fetching comments. When a video still cannot be read after the whole ladder, that video simply contributes no rows: no error row is written for it, so check the run log, and compare videosProcessed against the row counts in run-summary.json.

⬇️ Input

Nineteen parameters, every one of them read directly from the Actor's input schema. Nothing is marked required — see the notes under the table for what that means in practice.

ParameterRequiredTypeDescriptionExample Value
videoUrlsNoarrayTikTok video pages to pull the comment section from, one per line (stringList editor). Full links, short vm. and vt. links and bare numeric video IDs all work. Pre-filled with one example video URL.["https://www.tiktok.com/@mrbeast/video/7578547467189374239"]
maxCommentsPerVideoNointegerHow many unique comments to keep from each video. Duplicates are dropped before they count toward this number. Minimum 1, default 10.100
maxRepliesPerThreadNointegerReplies pulled from each comment thread, nested under replies. Minimum 0, default 3. Set 0 to skip the reply endpoint entirely.3
deduplicateByNostring"commentId" removes the same comment returned on two different pages. "commentIdAndText" additionally folds a commenter who posted word-for-word the same thing twice under two different IDs. Default "commentId"."commentId"
collapseDuplicateTextsNobooleanEmit one row per distinct comment text, keeping the most-liked example and reporting the group size in duplicateTextCount. Default false.false
mediaCommentHandlingNostring"include" keeps everything, "onlyMedia" keeps only comments containing an image or sticker, "excludeMedia" drops them for a text-only dataset. Default "include"."include"
hideLikelySpamNobooleanDrops rows whose spamScore reaches spamScoreThreshold. Default false — the score is advisory, and leaving this off keeps every row with its score attached.false
spamScoreThresholdNointegerScore at which a row is hidden. Minimum 0, maximum 100, default 60. Only read when hideLikelySpam is on.60
creatorUsernamesNoarrayTikTok usernames without the @, one per line (stringList editor). Used only when videoUrls is empty. Pre-filled with one example username.["mrbeast"]
videosPerCreatorNointegerVideos to collect per username before their comment sections are scraped. Minimum 1, maximum 1000000, default 10.10
creatorTabsNoarrayWhich profile tab to read (select editor). Enum: videos (own uploads), reposts. At least one entry, no duplicates, default ["videos"].["videos"]
videoOrderNostring"latest" (newest first), "oldest", or "popular" (most liked). Default "latest". Oldest and popular are re-ordered locally from a wider candidate pool, so they cost more time."latest"
videosPostedAfterNostringKeep only videos posted on or after this date (datepicker editor). Absolute YYYY-MM-DD or relative such as 3 months, 2 weeks, 10 days. No default."2026-01-31"
videosPostedBeforeNostringKeep only videos posted on or before this date (datepicker editor). Same formats. No default."2026-06-30"
skipPinnedVideosNobooleanIgnore pinned videos, which sit at the top of a profile and are often months old. Default false.false
aiEnhancementNobooleanAdds aiIsSpam, aiCategory, aiSpamReason and aiError using your own provider key. One provider call per 25 comments, billed by your provider, not by Apify. Default false.false
aiModelNostringModel, and by prefix the provider: claude-haiku-4-5, claude-sonnet-5, claude-opus-4-8, claude-fable-5, gpt-4o-mini, gpt-4o, gpt-4.1-mini, gpt-4.1, o3-mini, o1, gemini-2.0-flash-lite, gemini-2.0-flash, gemini-1.5-pro, gemini-2.5-flash, gemini-2.5-pro, grok-2-latest, grok-3-mini, grok-3, grok-beta, deepseek-chat, deepseek-reasoner, sonar, sonar-pro, sonar-reasoning, mistral-small-latest, mistral-large-latest. Default "claude-haiku-4-5"."claude-haiku-4-5"
aiApiKeyNostringYour own key for the provider implied by aiModel, stored encrypted as a secret field. Left empty, the matching provider environment variable is used instead. Read only when aiEnhancement is on."<YOUR_PROVIDER_API_KEY>"
proxyConfigurationNoobjectApify Proxy settings (proxy editor). Pre-filled with {"useApifyProxy": false} — a direct connection. Used as the datacenter rung of the fallback ladder if TikTok blocks the direct request.{"useApifyProxy": false}

⚠️ Ten undocumented input aliases exist, and they win over the parameters above. For compatibility with the base Actor's input shape, the Actor reads postURLs before videoUrls, profiles before creatorUsernames, commentsPerPost before maxCommentsPerVideo, maxRepliesPerComment before maxRepliesPerThread, resultsPerPage before videosPerCreator, profileScrapeSections before creatorTabs, profileSorting before videoOrder, oldestPostDateUnified before videosPostedAfter, newestPostDate before videosPostedBefore, and excludePinnedPosts before skipPinnedVideos. Base-key-first is deliberate — Apify pre-fills schema defaults into the run input, so reading the new key first would mean the base key was never reachable. The consequence for you: if you send both, the alias is what takes effect. Send one or the other, never both.

Further behaviours worth knowing, all read from the Actor's own input handling:

  • Values are coerced, not rejected. An unrecognised deduplicateBy falls back to "commentId" and an unrecognised mediaCommentHandling to "include", each with a COERCED: warning in the log. spamScoreThreshold outside 0100 is clamped with the same warning. A non-numeric maxCommentsPerVideo or maxRepliesPerThread falls back rather than failing the run.
  • The schema default and the code's own fallback differ. Apify fills maxCommentsPerVideo with the schema default of 10 and maxRepliesPerThread with 3, and that is what a Console run uses. If the key reaches the Actor genuinely absent, the code's internal fallbacks are 100 comments and 0 replies. Pass the values explicitly if you care which applies.
  • videosPerCreator's maximum of 1,000,000 is not achievable. Discovery scrolls a profile page and stops after 80 scrolls, or after four consecutive scrolls that surface nothing new — whichever comes first. On top of that, videoOrder of "popular" or "oldest" sorts locally over a candidate pool explicitly capped at 200 videos, so those two orderings cannot see past the newest 200 posts however high you set this.
  • Date filters and skipPinnedVideos apply to creator discovery only. They filter the videos a username resolves to. They have no effect at all when you supply videoUrls directly, and no effect on comment timestamps. Contrary to what the field help implies, they are applied for all three videoOrder values, including "popular".
  • creatorTabs of reposts is best-effort. Discovery loads the profile and clicks the Reposts tab within a 10-second window; many profiles do not have that tab at all, and when the click fails that tab silently contributes zero videos.
  • proxyConfiguration genuinely defaults to no proxy. The shipped prefill is {"useApifyProxy": false} and the run really does start on the container's direct IP. Your configuration is used for the datacenter rung of the ladder; if it cannot be built the Actor falls back to requesting an Apify proxy itself, and residential retries always use the RESIDENTIAL group regardless of what you set.

Example input

{
"videoUrls": [
"https://www.tiktok.com/@mrbeast/video/7578547467189374239",
"https://vm.tiktok.com/ZMhKQ8pTa/",
"7521904337788128542"
],
"maxCommentsPerVideo": 200,
"maxRepliesPerThread": 5,
"deduplicateBy": "commentId",
"collapseDuplicateTexts": false,
"mediaCommentHandling": "include",
"hideLikelySpam": false,
"spamScoreThreshold": 60,
"creatorUsernames": [],
"videosPerCreator": 10,
"creatorTabs": ["videos"],
"videoOrder": "latest",
"videosPostedAfter": "2026-01-31",
"videosPostedBefore": "2026-06-30",
"skipPinnedVideos": false,
"aiEnhancement": false,
"aiModel": "claude-haiku-4-5",
"aiApiKey": "",
"proxyConfiguration": { "useApifyProxy": false }
}

⬆️ Output

Typed, normalized JSON with a consistent shape across runs — one row per top-level comment, 65 top-level keys on every row, replies nested inside their parent. Unavailable values arrive as null or as an empty array rather than a missing key, including the four AI columns, which are present and null when aiEnhancement is off. Export as JSON, CSV, Excel or XML from the Console, or read the dataset through the Apify API. Three dataset views ship with the Actor: Clean comments (27 columns), Image & sticker comments (14) and Repeat text & spam signals (17).

Charging, stated plainly — and there is no filter expression to give you, because there is nothing to filter. Every row this Actor pushes is a real comment, pushed with the row_result charged event. There are no error rows, no accounting rows and no diagnostic rows: the codebase contains exactly one push call and it always charges. So there is no errorReason, no isError, no status and no marker key to exclude. If it is in the dataset, it is a comment and it was charged.

What that buys you is that the filters cost you nothing:

  • Duplicates are removed before any push. The same cid returned on two pages is dropped while paging, and again globally across every video in the run before the first charge. A duplicate row is never written and never billed.
  • Filtered rows are never charged. Comments removed by mediaCommentHandling, hidden by hideLikelySpam, or folded away by collapseDuplicateTexts are dropped before the push loop. They cost you the fetch, not the row.
  • Filtered rows never reach your model either. The AI step runs last, after every filter, so a discarded comment burns no provider tokens.
  • Replies are free. They are nested inside their parent row, not pushed as rows of their own, so a thread with 20 replies is still one charged row.

What it also means: a video that could not be read, that has no comments, or whose URL could not be parsed contributes nothing — no row, no marker, no charge. It is visible in the run log and in the videosProcessed figure in run-summary.json, not in the dataset.

Three limits to plan around, stated rather than glossed:

  • Duplicate and spam counts are run-scoped, not video-scoped. duplicateTextCount, duplicateTextGroupId, commenterCommentsInRun and therefore spamScore are computed across every comment in the run. Scraping ten videos in one run gives different numbers than scraping them in ten runs. This is intentional — a bot posting the same line under ten videos is exactly what the run-wide view catches — but it means the score is not comparable across differently shaped runs.
  • De-duplication does not span runs. Comments are de-duplicated within a run only. Re-running the same video returns the same comments again, and charges again. De-duplicate on cid on your side if you poll.
  • Replies carry no derived columns. Nested reply objects have the TikTok and media fields but no spamScore, no duplicate grouping, no AI verdict and no scrapedAt. They are also de-duplicated only within their own thread.

Example output

One charged comment row, AI enhancement off:

{
"videoWebUrl": "https://www.tiktok.com/@mrbeast/video/7578547467189374239",
"submittedVideoUrl": "https://www.tiktok.com/@mrbeast/video/7578547467189374239",
"input": "https://www.tiktok.com/@mrbeast/video/7578547467189374239",
"cid": "7578612904471692059",
"createTime": 1753362188,
"createTimeISO": "2026-07-24T13:03:08.000Z",
"text": "the editing on this one is insane, how long did the timelapse take @jimmy",
"diggCount": 1842,
"likedByAuthor": true,
"pinnedByAuthor": false,
"repliesToId": null,
"replyCommentTotal": 27,
"uid": "6904063862041396225",
"uniqueId": "milesonfilm",
"avatarThumbnail": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/5e2902b2db8018cbbde7a1aed077a35a~c5_100x100.jpg",
"mentions": ["jimmy"],
"type": "comment",
"isReply": false,
"parentCid": null,
"videoTotalComments": 48211,
"commentLanguage": "en",
"commentLink": "https://www.tiktok.com/@mrbeast/video/7578547467189374239?comment_id=7578612904471692059",
"secUid": "MS4wLjABAAAAqT7l0Zx3nB9r6mKQ2wYcVJdE1sHfXo4pR",
"commenterProfileUrl": "https://www.tiktok.com/@milesonfilm",
"nickname": "Miles",
"videoId": "7578547467189374239",
"isHighPurchaseIntentFlag": false,
"replyScore": 0.8213,
"showMoreScore": 0.4417,
"foldStatus": -1,
"noShow": false,
"userBuried": false,
"commentStatus": 1,
"isTopComment": true,
"isAuthorReply": false,
"mediaUrls": [],
"mediaThumbnails": [],
"mediaDimensions": [],
"mediaCount": 0,
"stickerUrl": null,
"stickerId": null,
"hasMedia": false,
"mediaType": null,
"isMediaOnlyComment": false,
"isEmptyWithoutMedia": false,
"duplicateTextCount": 1,
"isDuplicateText": false,
"duplicateTextGroupId": null,
"containsLink": false,
"linkInText": null,
"mentionCount": 1,
"isMentionStuffed": false,
"isEmojiOnlyText": false,
"commenterCommentsInRun": 1,
"isRepeatCommenterInRun": false,
"spamSignals": [],
"spamScore": 0,
"spamRiskLevel": "none",
"isCollapsedDuplicateGroup": false,
"aiIsSpam": null,
"aiCategory": null,
"aiSpamReason": null,
"aiError": null,
"scrapedAt": "2026-07-25T09:41:07.000Z",
"replies": [
{
"videoWebUrl": "https://www.tiktok.com/@mrbeast/video/7578547467189374239",
"cid": "7578620117762409498",
"createTimeISO": "2026-07-24T14:11:52.000Z",
"text": "40 days of shooting for 12 seconds of footage",
"diggCount": 903,
"uniqueId": "mrbeast",
"nickname": "MrBeast",
"commenterProfileUrl": "https://www.tiktok.com/@mrbeast",
"type": "reply",
"isReply": true,
"parentCid": "7578612904471692059",
"repliesToId": "7578612904471692059",
"isCreatorReply": true,
"hasMedia": false,
"mediaType": null,
"isMediaOnlyComment": false
}
]
}

A picture-only comment that a text-only scraper would return as blank, with a spam signal firing and AI enhancement on:

{
"cid": "7578613440992215045",
"text": "",
"createTimeISO": "2026-07-24T13:09:44.000Z",
"diggCount": 3,
"uniqueId": "dealsdailyshop27",
"nickname": "DAILY DEALS",
"commenterProfileUrl": "https://www.tiktok.com/@dealsdailyshop27",
"commentLanguage": null,
"mediaUrls": [
"https://p16-comment-sign-va.tiktokcdn.com/tos-maliva-avt-0068/oQBAfeIzTgNc4m~tplv-origin.image"
],
"mediaThumbnails": [
"https://p16-comment-sign-va.tiktokcdn.com/tos-maliva-avt-0068/oQBAfeIzTgNc4m~tplv-crop.image"
],
"mediaDimensions": [{ "width": 1080, "height": 1080 }],
"mediaCount": 1,
"stickerUrl": null,
"stickerId": null,
"hasMedia": true,
"mediaType": "image",
"isMediaOnlyComment": true,
"isEmptyWithoutMedia": false,
"duplicateTextCount": 0,
"isDuplicateText": false,
"duplicateTextGroupId": null,
"containsLink": false,
"linkInText": null,
"mentionCount": 0,
"isMentionStuffed": false,
"isEmojiOnlyText": false,
"commenterCommentsInRun": 4,
"isRepeatCommenterInRun": true,
"spamSignals": ["repeatCommenterInRun"],
"spamScore": 15,
"spamRiskLevel": "low",
"isCollapsedDuplicateGroup": false,
"aiIsSpam": true,
"aiCategory": "promo_spam",
"aiSpamReason": "image-only reply from an apparent shop account",
"aiError": null,
"scrapedAt": "2026-07-25T09:41:07.000Z",
"replies": []
}

Read that second row carefully. aiIsSpam is true while spamScore is only 15. The two layers disagree, the rule-based score is the one hideLikelySpam acts on, and the model was judging the placeholder [image or sticker comment, no text] rather than the picture itself. Neither is a verdict about the person.

How does it work?

Every video you supply is reduced to a numeric video ID — short vm. and vt. links are resolved by following the redirect, /video/ and /photo/ URLs are split, and a bare number of 15 digits or more is accepted as-is. The Actor then pages TikTok's public comment list endpoint with exactly the parameters that endpoint accepts and no others, asking for only as many comments as you still need rather than a fixed page, and following the cursor the API itself returns instead of guessing an offset. Threads with replies are paged separately against the reply endpoint. Creator mode adds a step in front: a headless Chromium session loads the profile, intercepts the feed XHR responses while scrolling, and resolves handles into video URLs.

Nothing is written twice. Comment IDs seen while paging are dropped immediately, and the whole run is de-duplicated again before the first row is pushed. Everything after that is pure computation on data already downloaded: media URLs decoded out of the nested payload, identical texts grouped and counted, five spam signals scored, your filters applied, and only then the optional model call.

Requests begin on a direct connection and escalate through Apify datacenter and residential proxies only when TikTok pushes back, with blocks detected by response size rather than status code. Only publicly visible comment data is returned — there is no login, no cookie field and no session anywhere in the input. Because the data comes from a JSON endpoint rather than CSS selectors, a TikTok front-end redesign does not change your field names.

Integrations

TikTok Comments Scraper By Real Comment Filter is an Apify Actor, so it works with anything that can call the Apify API or read a dataset.

Calling the Actor from Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<YOUR_USERNAME>/tiktok-comments-scraper-by-real-comment-filter").call(run_input={
"videoUrls": ["https://www.tiktok.com/@mrbeast/video/7578547467189374239"],
"maxCommentsPerVideo": 200,
"maxRepliesPerThread": 5,
"hideLikelySpam": False,
})
for c in client.dataset(run["defaultDatasetId"]).iterate_items():
print(c["spamScore"], c["spamRiskLevel"], c["uniqueId"], c["text"][:80])

Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request. Every row in the dataset is a comment, so no error-row branch is needed; filter on spamScore yourself if you kept hideLikelySpam off, which is the recommended way to run it.

No-code tools (n8n, Make, LangChain)

In n8n, use the Apify node — or an HTTP Request node pointed at the Actor's run endpoint with your token — and pass the same JSON input; an IF node on spamScore then splits the clean comments from the flagged ones into separate branches, and the flat rows drop straight into a Google Sheets or Postgres node. In Make, the Apify module supports run-and-wait, so a daily sweep of a campaign video's comment section can feed an Airtable or Slack step with no glue code. In LangChain, build documents from text with commentLanguage, spamRiskLevel, uniqueId and videoWebUrl as metadata — every value is already a typed primitive, array or plain object, so no output parser is needed. Apify schedules and webhooks cover recurrence and completion triggers.

Scraping publicly visible pages is broadly treated as permissible where no authentication is bypassed and no private content is accessed, and this Actor does neither — it reads only what a signed-out visitor is served. But the output is unambiguously personal data, and this Actor goes one step further than most by attaching an automated judgement to each individual, so treat it that way from the first run.

uniqueId, nickname, uid, secUid, commenterProfileUrl and avatarThumbnail identify a real person. text is that person's own written expression, tied back to them by cid, commentLink, createTime and createTimeISO. mediaUrls, mediaThumbnails and stickerUrl are pictures they chose to post, mentions names third parties who never appeared in your input, and commentLanguage is an attribute of them. Every nested object inside replies carries the same fields about a different person. Under GDPR, UK GDPR and the CCPA, collecting and storing this is processing, and the fact that data is publicly available is not itself a lawful basis.

An inferred spam or bot label is itself personal data. spamScore, spamRiskLevel, spamSignals, isRepeatCommenterInRun and isMentionStuffed are the Actor's own inferences about a named individual, and aiIsSpam, aiCategory and aiSpamReason are a third-party model's. TikTok's isHighPurchaseIntentFlag is TikTok's inference, passed through. Inferred data attracts the same rights as observed data — including the right to rectification of an inference the person considers wrong, and the right to object to it. If you act on these labels rather than merely storing them, check whether your use engages the rules on automated decision-making before you build the workflow.

In practice that means six things:

  • Lawful basis. Decide and document yours before you store rows. For research, brand safety and moderation that is usually legitimate interest, which requires a written balancing test weighing your purpose against the commenter's reasonable expectation that a throwaway comment would be aggregated, scored and retained in someone's database.
  • Minimisation. If you only need the spam distribution and engagement volume, keep spamScore, spamRiskLevel, diggCount and createTimeISO, and drop text, uniqueId, nickname, commenterProfileUrl, avatarThumbnail, mediaUrls and mentions first. Setting mediaCommentHandling to excludeMedia also keeps the pictures out of your dataset in the first place, which is a stronger position than collecting them and filtering later.
  • Transparency and retention. Set a retention period matched to your purpose and delete on schedule. If you are a controller in scope of the GDPR's transparency rules, Article 14 obliges you to inform people whose data you collected without contacting them first — and that obligation covers the inferred labels, not just the scraped text.
  • Subject requests. Keep cid and uid so a specific person's rows can be located and removed on an access, objection, rectification or erasure request.
  • AI enhancement adds a processor to your chain. With aiEnhancement on, comment text — personal data written by an identifiable person — is sent to Anthropic, OpenAI, Google, xAI, DeepSeek, Perplexity or Mistral depending on aiModel. That provider processes it on your behalf under your own account, so it belongs in your record of processing activities, needs to be covered by your controller-processor terms with them, and should appear where your privacy notice lists recipients or international transfers.
  • Acting on the data is a separate regime from collecting it. Contacting commenters, or reporting and blocking them on the strength of a score, is governed by its own rules — marketing and cold-contact law plus TikTok's own terms if you act on-platform. A valid basis for collection does not carry over.

Consult legal counsel if your use case involves bulk storage of personal data, AI training corpora, resale, automated moderation decisions, or sending comment text to a model provider at scale.

❓ Frequently asked questions

What TikTok comment fields does this Actor return?

The five most used are text, uniqueId, diggCount, createTimeISO and spamScore. Every row also carries commentLanguage, commentLink, replyCommentTotal, the decoded media block (mediaUrls, stickerUrl, mediaType, isMediaOnlyComment), the duplicate block (isDuplicateText, duplicateTextCount, duplicateTextGroupId) and the full spam breakdown (spamSignals, spamRiskLevel and the five individual signal booleans). Sixty-five top-level keys in total — see the data fields section above.

No. There is no login field, no password field and no cookie field anywhere in the input schema. The Actor calls the same public comment endpoints a signed-out browser calls, and your Apify token is the only credential involved. One nuance for completeness: the creator-discovery browser will attach a sid_tt cookie if a TIKTOK_SID_TT environment variable happens to be set on the run, but nothing in the input schema sets one and comment fetching never uses it. The trade-off is that anything TikTok serves only to a logged-in session — comments on private or friends-only videos, age-gated content — is out of reach by design.

What does "real comment" mean here — is the filter an AI model?

No. The filter is a deterministic rule, and it works with AI switched off. spamScore is the weighted sum of five signals computed from the comment payload itself: a link in the text (45), the same text repeated elsewhere in this run (30), three or more mentions (30), text that is nothing but emoji (15), and the same commenter appearing three or more times in this run (15), capped at 100. hideLikelySpam compares that number to spamScoreThreshold and nothing else. The optional aiEnhancement layer adds aiIsSpam, aiCategory and aiSpamReason as extra columns — it never removes a row and never changes what you are charged. De-duplication and media decoding are likewise pure rules, not model output.

How accurate is the spam score and the AI label?

No accuracy figure is claimed for either, and you should not assume one. spamScore is a heuristic: it fires on patterns that correlate with spam, and a genuine comment containing a link, or a superfan posting under four videos in one run, will score too. aiIsSpam and aiCategory are a language model's opinion of a string truncated to 400 characters — and for a picture comment, of a placeholder rather than the picture. Both are inferences about a person's post, not facts about the person. That is why hideLikelySpam defaults to off, why every individual signal is exposed as its own column so you can audit the score instead of trusting it, and why the default threshold of 60 requires two independent signals rather than one. A signal that returned zero hits during the build — a default-profile-picture check — was deliberately not shipped rather than included to pad the score.

What happens if a video has no comments, is private, or was removed?

That video contributes no rows and no charges, and the run continues with the next one. There is no error row: this Actor pushes only real comments, so a video that returns nothing is visible in the run log rather than in the dataset. A URL the Actor cannot reduce to a video ID logs Could not extract a video ID from: <url>. A blocked or challenged response logs a BLOCK SIGNAL line naming whether it was a zero-byte refusal or a small challenge body, and triggers the proxy ladder before giving up. If every video in the run comes back empty, the Actor logs 0 comments collected. Nothing pushed, nothing charged. and still writes run-summary.json to the key-value store so the run stays auditable. Compare videosProcessed in that summary against your input count to find the gap.

How many comments can I extract, and can I scrape multiple videos at once?

Yes to multiple videos — videoUrls is a list, up to 10 videos are processed concurrently, and duplicate URLs are removed before scraping. maxCommentsPerVideo applies per video, has a minimum of 1 and no maximum in the schema, and is genuinely per-video: ten videos at 200 is a 2,000-row budget before filtering. The real ceilings are TikTok's and the Actor's pagination guards: comment pages are requested at up to 50 comments at a time and capped at 400 pages per video, reply pages at 20 at a time and capped at 60 pages per thread. maxRepliesPerThread is properly paginated rather than sliced from a single page, so values above the page size are genuinely delivered when the thread is that deep.

Am I charged for comments the filters removed, and does the AI option cost extra?

No to the first, yes to the second. Duplicate comment IDs, comments dropped by mediaCommentHandling, comments hidden by hideLikelySpam and rows folded away by collapseDuplicateTexts are all removed before the push loop, so they are never written and never billed — one row_result is charged per row that actually lands in your dataset, and nested replies are free. The AI option is billed by your own provider, not by Apify: one call per 25 comments, on your key, at their pricing. Because the model runs after every filter, you are never charged tokens for a comment the Actor then discards. And if aiEnhancement is on but no key is found in aiApiKey or the provider environment variable, the run does not fail — comments are still scraped, still written and still charged, with the AI columns null and aiError reading no API key supplied in the row itself.

How does this Actor compare to other TikTok comment scrapers?

All observations below were checked on the Apify Store on 25 July 2026 and describe what each listing documents — not measurements of our own.

clockworks/tiktok-comments-scraper is the most used in this category and the closest structural match: its documented input is postURLs, commentsPerPost and maxRepliesPerComment, and its published output sample shows nine keys — text, diggCount, replyCommentTotal, createTimeISO, uniqueId, videoWebUrl, uid, cid, avatarThumbnail — every one of which this Actor also emits under the same name, so migrating either way needs no remapping. It documents error items identified by an errorCode field with a 17-code reference table, and its README is silent on whether those items are billed. It states pay-per-result pricing of "$5 to scrape 1,000 results". Its listing does not mention de-duplication, image or sticker decoding, duplicate-text grouping or spam scoring — where a listing is silent, that is not documented rather than not supported.

apidojo/tiktok-comments-scraper documents startUrls, includeReplies, maxItems and a customMapFunction transform hook, and its output sample uses a different key layout throughout — parentId, id, text, createdAt, likeCount, replyCount, commentLanguage, awemeId, isAuthorLiked and a nested user object carrying region, bio, hasEmail and hasPhone — so migrating to or from it does mean remapping. Its listing also asks that each input URL fetch at least 10 comments, and states "No Proxy Required". The speed, success-rate and price figures on that listing ("10,000 comments per minute", "100-200 comments per second", "99% success rate", "$0.30 per 1,000 comments") are its own claims and are neither reproduced as fact nor endorsed here.

epctex/tiktok-comment-scraper documents startUrls as required plus includeReplies, endPage, maxItems and customMapFunction, and returns TikTok's raw snake_case payload shape — cid, text, create_time, digg_count, reply_comment_total, comment_language, aweme_id, is_author_digged, reply_id and a nested user object. Its listing prices three separate events: "$0.003 per video" for a comment query, "$0.003 per comment thread" for a reply query, and "$0.0003 per item" for dataset items beyond a stated free allowance.

Observable differences here: comment IDs de-duplicated at two points before anything is pushed or billed; image and sticker comments decoded into mediaUrls and stickerUrl instead of arriving as blank text; repeated texts grouped with a count and an optional collapse; a five-signal spam score with every signal exposed as its own auditable column; a filter chain in which excluded rows are never charged; and an optional model layer on your own key across seven providers, kept strictly additive. What it does not do: no commenter region, bio, email or phone flags, no customMapFunction hook, no error rows, and no de-duplication across runs.

Does it work with Claude, ChatGPT and other AI agent tools, and is the output LLM-ready?

Yes to both. The Actor is callable as a standard HTTP-triggered Apify run, so LangChain, CrewAI, n8n or a hand-written tool definition can start it and read back typed, normalized JSON with consistent field names across runs — no HTML parsing, no selectors, no relative-date strings to resolve. There is no separate signup and no separate API key for the run itself: the Apify token you already have is the only credential. Note that this is a different question from aiEnhancement, which is about the Actor calling a model rather than a model calling the Actor.

What happens when TikTok changes its layout or anti-bot system, and can I run it without managing proxies?

The Actor is maintained, the output schema stays stable on your side, and there is nothing for you to configure. Two design choices reduce the exposure: comments come from a JSON endpoint rather than rendered markup, so a front-end redesign does not move your field names, and block detection keys on response size and body content rather than HTTP status, which is what actually changes when TikTok's WAF adjusts. Anti-bot pushback is handled as escalation rather than failure — retry with backoff, then a datacenter proxy, then residential with fresh exit IPs. There is no CAPTCHA solving and none is claimed. You never install a browser, create a proxy account or rotate an IP yourself; the only browser in the Actor is the headless Chromium used to resolve creator usernames into video URLs, and it is skipped entirely when you supply videoUrls.

Which TikTok comment fields work best for AI training data and RAG indexing?

For RAG indexing, text carries the entire information payload of a comment and chunks cleanly one row per chunk, with commentLanguage, spamRiskLevel and mediaType as filter metadata and commentLink or videoWebUrl as the citation anchor. Filter on spamScore at index time rather than at scrape time so you can retune the cut-off without re-scraping. For training data and analytics, the most structurally consistent fields across records are diggCount, replyCommentTotal, createTime, spamScore, duplicateTextCount, mentionCount and commenterCommentsInRun — all typed primitives written on every row. Three things to presence-check first: text is "" rather than null on picture comments, so check isMediaOnlyComment before treating an empty string as a bad record; commentLanguage is genuinely null when TikTok did not label the comment; and the four AI columns are null on every row unless aiEnhancement was on, so exclude them from any schema that assumes a value.

Scraper NameWhat it extracts
TikTok Profile ScraperA creator's post feed with views, likes, captions and video URLs — the input side of this Actor
Tiktok Video Scraper (Creator Profile & Contact)Single-video data with the creator's profile and public contact details attached
TikTok Trending Hashtags Scraper By Multiple CountriesRising TikTok hashtags across several countries in one run
Instagram Comment Leads ScraperInstagram comment threads with commenter identity attached
LinkedIn Comment Scraper by LanguageLinkedIn post comments with a per-comment language and script verdict
Pinterest Comment Scraper With Commenter Profile EnrichmentPinterest pin comments with the commenter's profile resolved

Which one suits which job: use TikTok Profile Scraper when you need the posts, then hand its webVideoUrl values to this Actor when the audience reaction matters more than the post metrics.

💬 Your feedback

Found a bug, a spam signal that keeps misfiring on real comments, or a field in TikTok's comment payload that is not in this output? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON, the video URL and the cid of the comment involved are the fastest to reproduce and fix.