TwoSec

Scrape TikTok comments and replies with Python

Collect the comments on any public TikTok video, and the replies under them, as JSON: text, likes, reply count, language, time and whether the creator liked it. Then save them to a CSV. One request returns a page of comments, usually 33–42.

TikTok's official APIs don't give companies other creators' comments: the Research API is for academic and not-for-profit research only. Collecting them yourself means building a scraper and keeping it working as TikTok changes. The TikTok Video & Creator API returns them as clean JSON instead.

Setup

You need Python 3, requests (pip install requests) and a key from the RapidAPI listing. Put the key in an environment variable called RAPIDAPI_KEY. The script below makes at most 15 requests, so the free plan's 100 requests are enough to try it.

import csv
import os
import time
from collections import Counter
from urllib.parse import quote

import requests

BASE = "https://tiktok-video-creator-api.p.rapidapi.com"
HEADERS = {
    "X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"],
    "X-RapidAPI-Host": "tiktok-video-creator-api.p.rapidapi.com",
}


def get(path, **params):
    """GET a path and return the JSON. Retries rate limits and upstream errors."""
    for attempt in range(4):
        response = requests.get(BASE + path, headers=HEADERS, params=params, timeout=30)
        if response.status_code in (429, 502, 503) and attempt < 3:
            time.sleep(2 ** attempt)
            continue
        response.raise_for_status()
        return response.json()

Pick the video

Use the video ID, the long number in a link like https://www.tiktok.com/@zachking/video/7655004069190192398. The endpoints also accept the whole tiktok.com link, URL-encoded, in place of the ID. Short links (vm.tiktok.com/…) aren't resolved; open them in a browser first to get the full link.

video = "7655004069190192398"
# or: video = quote("https://www.tiktok.com/@zachking/video/7655004069190192398", safe="")

Read one page

curl --request GET \
  --url 'https://tiktok-video-creator-api.p.rapidapi.com/v1/tiktok/videos/7655004069190192398/comments' \
  --header "X-RapidAPI-Key: $RAPIDAPI_KEY" \
  --header 'X-RapidAPI-Host: tiktok-video-creator-api.p.rapidapi.com'

Response shape, shortened to one comment. Counts are from a live response; the commenter's details and the text are placeholders

{
  "video_id": "7655004069190192398",
  "total": 105316,
  "count": 1,
  "has_more": true,
  "next_cursor": "50",
  "comments": [
    {
      "id": "7655354445920699138",
      "video_id": "7655004069190192398",
      "text": "This is amazing",
      "created_at": "2026-06-25T15:25:17Z",
      "likes": 1824941,
      "reply_count": 818,
      "language": "en",
      "is_pinned": false,
      "liked_by_creator": false,
      "parent_id": "",
      "reply_to_id": "",
      "author": {
        "id": "7000000000000000001",
        "username": "example_commenter",
        "nickname": "Example Commenter",
        "avatar": "https://p16-common-sign.tiktokcdn-us.com/…",
        "verified": false,
        "sec_uid": "MS4wLjABAAAA…",
        "url": "https://www.tiktok.com/@example_commenter",
        "stats": null
      }
    }
  ]
}

total is TikTok's count of all comments on the video, replies included, so it's higher than the number of top-level comments you'll collect.

Save as you go

Open the CSV first and write each page to it as soon as it arrives. If a request fails for good later, for example because your plan's quota ran out, the script stops with an error but the file keeps every comment collected before it.

FIELDS = ["id", "parent_id", "reply_to_id", "created_at", "likes", "reply_count",
          "language", "liked_by_creator", "is_pinned", "username", "text"]

out = open(f"comments_{video}.csv", "w", newline="", encoding="utf-8")
writer = csv.DictWriter(out, fieldnames=FIELDS, extrasaction="ignore")
writer.writeheader()


def save(rows):
    """Write rows to the CSV right away, so a later error can't lose them."""
    for c in rows:
        writer.writerow({**c, "username": (c.get("author") or {}).get("username", "")})
    out.flush()

Rows with an empty parent_id are top-level comments; the others are replies.

Get the comments

Pass next_cursor back as cursor while has_more is true. Each page is one request, and max_pages caps how many the loop makes: 5 pages are about 200 comments.

def collect(path, max_pages):
    """Follow next_cursor for up to max_pages requests, saving each page."""
    rows, cursor = [], None
    for _ in range(max_pages):
        params = {"cursor": cursor} if cursor else {}
        data = get(path, **params)
        page = data.get("comments") or []
        save(page)
        rows.extend(page)
        cursor = data.get("next_cursor")
        if not data.get("has_more") or not cursor:
            break
    return rows


top_level = collect(f"/v1/tiktok/videos/{video}/comments", max_pages=5)
print(len(top_level), "comments")

Add the replies

Replies live under each comment. Fetch them only for comments with reply_count above 0; each page is one request. In a reply, parent_id is the top-level comment and reply_to_id is the reply it answers (empty when it answers the top-level comment).

all_rows = list(top_level)
most_replied = sorted(top_level, key=lambda c: c["reply_count"], reverse=True)[:5]
for c in most_replied:
    if c["reply_count"]:
        path = f"/v1/tiktok/videos/{video}/comments/{c['id']}/replies"
        all_rows.extend(collect(path, max_pages=2))
out.close()
print(len(all_rows), "rows saved")

This reads up to 2 pages of replies for the 5 most-replied comments: at most 10 requests. With the 5 comment pages, the whole script makes at most 15 requests. On a paid plan, raise both max_pages values and the number of threads; big videos have thousands of replies.

Quick analysis

# Most-liked comments
for c in sorted(top_level, key=lambda c: c["likes"], reverse=True)[:5]:
    print(c["likes"], c["text"][:100])

# Which languages the audience writes in
print(Counter(c.get("language") or "?" for c in all_rows).most_common(5))

# Comments the creator liked
print(sum(c["liked_by_creator"] for c in top_level), "liked by the creator")

Things to know

Next steps

Plans

Every page of comments or replies is one request; the script above makes at most 15. A free plan covers testing. Plans, monthly quotas and rate limits are listed on the RapidAPI listing. For more volume or a custom plan, use Contact provider on the listing.