# Douyin Comment Tracker (`moonweil/douyin-comments-scraper`) Actor

Track Douyin (抖音) video comments on a schedule and export flat CSV / JSON rows — text, author, likes, IP region, timestamps. Delta mode returns only comments new since the last run. Standby HTTP endpoint for one-off lookups. No headless browser. Replies & video metadata with your cookies.

- **URL**: https://apify.com/moonweil/douyin-comments-scraper.md
- **Developed by:** [Aleksandr Jelohhin](https://apify.com/moonweil) (community)
- **Categories:**
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 comments

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Douyin Comment Tracker

**Watch a set of Douyin (抖音) videos and pull their comments on a schedule** — then export flat, CSV-ready rows: comment text, author, likes, reply count, IP region, timestamps, stickers. Douyin is the mainland-China app TikTok is based on. This Actor calls Douyin's web API directly with signed HTTP requests. **No headless browser**, so it is faster, cheaper, and steadier under load than browser-driven scrapers.

Built for **ongoing social listening on Douyin**: brand and campaign monitoring, KOL / influencer tracking, sentiment analysis, and competitive research. Run it once for a bulk export, or point a schedule at it and let **delta mode** feed you only what's new.

### Why this one

Most Douyin comment scrapers do a single bulk dump. This Actor is built to be **run again and again on the same videos** without re-paying for or re-processing comments you already have:

- **Delta mode** — each scheduled run returns only comments new since the previous run (per video), tracked by a high-water mark in the key-value store. A daily "watch these 50 videos" job stays cheap and its dataset stays clean.
- **Standby / HTTP endpoint** — call the Actor as a low-latency `GET /comments?videoUrl=…` service for one-off lookups, no run to start.
- **Flat rows, one dataset** — every row carries a `row_type`; no nested JSON, opens straight in Excel / Sheets / a CSV import.
- **Per-video error isolation** — a private / deleted / comments-off video produces one typed `error` row and the run keeps going. You are **never charged** for error rows, empty results, or delta duplicates.

### What you get

**Without any login — the common case:**

- **Top-level comments** for any public video, with pagination handled for you: `comment_id`, `text`, `create_time` (ISO + epoch), `digg_count` (likes), `reply_count`, `user_id`, `sec_uid`, `nickname`, `avatar_url`, `ip_label` (the region Douyin shows), `is_author_liked`, `sticker_url`.
- Douyin video **URLs, `v.douyin.com` share links, and bare `aweme_id`** all accepted; share links are resolved for you.
- A typed **error row** per video that can't be scraped (private, deleted, comments off, region-locked) — one bad video never fails the run.

**With a `cookies` string from a logged-in douyin.com session:**

- **Nested reply threads** under each comment (`row_type: "reply"`, linked by `parent_comment_id`).
- **Video metadata** rows (`row_type: "video"`) — description, author, play / like / comment / share counts, music.

Douyin gates its reply and video-detail endpoints behind a logged-in session, so these two need cookies. Without them the Actor scrapes top-level comments and logs that it skipped the rest — it does not fail. See **Limitations** below.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `videoUrls` | string\[] | — (required) | Douyin video URLs, `v.douyin.com` share links, or bare numeric `aweme_id`. |
| `maxCommentsPerVideo` | integer | `100` | Top-level comments per video. Replies don't count against this. Capped at 5000. |
| `includeReplies` | boolean | `false` | Fetch nested reply threads. **Needs `cookies`** — skipped without it. |
| `maxRepliesPerComment` | integer | `10` | Replies fetched per comment when `includeReplies` is on. |
| `includeVideoMetadata` | boolean | `true` | Emit one `row_type: "video"` row per video. **Works best with `cookies`.** |
| `deltaMode` | boolean | `false` | Only return comments new since the previous run. |
| `deltaStateKey` | string | `DOUYIN_DELTA_STATE` | Key-value store key holding delta state. Reuse it across scheduled runs of one job. |
| `cookies` | string | — | `name=value; name=value` cookie string from a logged-in douyin.com session. Unlocks replies + metadata. Do not share this value. |
| `proxyConfiguration` | object | `RESIDENTIAL` Apify Proxy | Datacenter IPs are blocked quickly — keep residential. |
| `maxConcurrency` | integer | `3` | Videos in parallel, and the per-host request cap. Capped at 10. |

#### Example input

```json
{
  "videoUrls": [
    "https://www.douyin.com/video/7677636831337576185",
    "https://v.douyin.com/iRxYpVKN/",
    "7677636831337576185"
  ],
  "maxCommentsPerVideo": 200,
  "includeVideoMetadata": true,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] },
  "maxConcurrency": 3
}
```

### Output

One flat row per comment. Every row carries a `row_type` (`comment`, `reply`, `video`, or `error`) so you keep everything in one dataset and filter after export. No nested objects — it opens cleanly in Excel / Google Sheets / a CSV import.

#### Comment row

```json
{
  "row_type": "comment",
  "source": "https://www.douyin.com/video/7677636831337576185",
  "aweme_id": "7677636831337576185",
  "comment_id": "7355800000000000001",
  "parent_comment_id": null,
  "text": "这个视频太有意思了 😂",
  "create_time": "2024-05-01T09:12:30+00:00",
  "create_time_epoch": 1714554750,
  "digg_count": 128,
  "reply_count": 4,
  "user_id": "58312900000",
  "sec_uid": "MS4wLjABAAAA…",
  "nickname": "小明同学",
  "avatar_url": "https://p3-pc.douyinpic.com/…/avatar.jpeg",
  "ip_label": "广东",
  "is_author_liked": true,
  "is_author_reply": false,
  "sticker_url": null,
  "reply_to_reply_id": null,
  "scraped_at": "2026-09-02T12:00:00+00:00"
}
```

A **reply** row (needs `cookies`) is the same shape with `row_type: "reply"` and `parent_comment_id` set to the top-level comment's id.

#### Video-metadata row (needs `cookies`)

```json
{
  "row_type": "video",
  "aweme_id": "7677636831337576185",
  "desc": "周末去了趟海边 #vlog",
  "create_time": "2024-05-01T08:00:00+00:00",
  "create_time_epoch": 1714550400,
  "share_url": "https://www.douyin.com/video/7677636831337576185",
  "duration_ms": 34000,
  "author_user_id": "58312900000",
  "author_sec_uid": "MS4wLjABAAAA…",
  "author_nickname": "旅行的阿May",
  "author_follower_count": 254000,
  "statistics_play_count": 1830000,
  "statistics_digg_count": 96000,
  "statistics_comment_count": 1204,
  "statistics_share_count": 3100,
  "statistics_collect_count": 8800,
  "music_id": "6935000000000000000",
  "music_title": "原声 - 旅行的阿May",
  "music_author": "旅行的阿May"
}
```

#### Error row

```json
{
  "row_type": "error",
  "source": "https://www.douyin.com/video/0000000000000000000",
  "aweme_id": "0000000000000000000",
  "error_code": "VIDEO_NOT_FOUND",
  "error_message": "…",
  "http_status": null,
  "scraped_at": "2026-09-02T12:00:00+00:00"
}
```

Error codes: `SIGNATURE_REJECTED`, `RATE_LIMITED`, `VIDEO_NOT_FOUND`, `VIDEO_PRIVATE`, `COMMENTS_DISABLED`, `PROXY_FAILED`, `PARSE_ERROR`, `INPUT_ERROR`.

### Pricing (pay-per-event)

You are billed per result actually written to the dataset:

| Event | Fires when |
|---|---|
| **Actor start** | Once per run. |
| **Comment** | One top-level comment row is written. |
| **Reply** | One nested reply row is written. |
| **Video metadata** | One video-metadata row is written. |

You are **never charged** for an error row, for an empty result, or — in delta mode — for a comment you already received on a previous run. Set a **maximum cost per run** to cap spend; the Actor stops cleanly at the limit and keeps everything already produced and billed.

Delta mode is the cheapest way to monitor a set of videos: after the first run, each scheduled run only pays for genuinely new comments.

### Standby mode

Enable Standby to call the Actor as a low-latency HTTP endpoint instead of starting a run:

```
GET /comments?videoUrl=<url|aweme_id>&maxComments=50
```

It returns JSON directly, bounded to ~120 comments / a few seconds per request — larger jobs get a clear "use batch mode" error. Standby and batch share the same scraping core, so results match.

### Limitations — read this

- **Top-level comments work without login. Replies and video metadata need the `cookies` input** — Douyin gates its reply and video-detail endpoints behind a logged-in session (a device `uifid` token plus, for metadata, an extra request signature). Without `cookies` the Actor scrapes top-level comments and logs a skip for the rest; it does not fail.
- **Private / friends-only videos** cannot be scraped — you get a `VIDEO_PRIVATE` / `VIDEO_NOT_FOUND` error row.
- **Comments turned off** returns `COMMENTS_DISABLED`. Douyin does not always distinguish "comments off" from "zero comments yet", so a brand-new video can also land here.
- **Region-restricted content**: some videos are only visible from certain regions. Residential-proxy country selection can help; otherwise you get an error row.
- **Rate limits**: Douyin rate-limits aggressively. Residential proxies are effectively required — datacenter IPs are blocked fast. Large jobs run slower because the Actor paces itself and rotates proxy sessions to stay under the radar.
- **Comment ordering** is Douyin's own (a mix of "hot" and recent), not strictly chronological. Delta mode tracks the highest comment id seen rather than assuming order.
- **No historical backfill guarantee**: Douyin's comment API stops paginating at some depth for very high-comment videos.
- Scrapes **public data only** and does not log in on your behalf. Supplying `cookies` is optional and at your own risk. Respect Douyin's terms and applicable law; you are responsible for how you use the data.

### How it works

The Actor generates Douyin's web request signature (`a_bogus`) in-process, attaches the anti-abuse `ttwid` cookie and an `msToken`, and calls the same `https://www.douyin.com/aweme/v1/web/comment/list/` endpoint the Douyin website uses. The signature layer is isolated in one module so it can be updated fast when Douyin rotates the algorithm. See `MAINTENANCE.md`.

***

Douyin, 抖音, Douyin comments, Douyin comment scraper, Douyin comment tracker, Douyin comment monitor, 抖音评论, 抖音评论采集, 抖音评论监控, 抖音数据采集, TikTok China, TikTok China comments, douyin.com scraper, short-video comments, 短视频评论, social listening, 舆情监测, brand monitoring, campaign monitoring, KOL research, 达人分析, influencer research, sentiment analysis, 情感分析, audience insights, comment export, CSV, scheduled scraping, delta scraping.

# Actor input Schema

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

List of Douyin video URLs, share links (v.douyin.com/...), or bare numeric aweme\_id values. Share links are resolved to a canonical aweme\_id before scraping.

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

Upper bound on top-level comments fetched per video. Replies do not count against this. Capped at 5000.

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

Also fetch nested reply threads under each top-level comment. REQUIRES the 'cookies' input — Douyin serves the reply endpoint only to logged-in sessions. Without cookies this is skipped (top-level comments are still scraped).

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

Upper bound on replies fetched per top-level comment when 'Include replies' is on.

## `includeVideoMetadata` (type: `boolean`):

Emit one row\_type="video" row per video with description, author, statistics and music info. Works best with the 'cookies' input — Douyin now gates the video-detail endpoint behind a logged-in session (`uifid`). Without cookies the Actor still tries, and falls back to the page HTML, but may skip metadata; comments are unaffected.

## `deltaMode` (type: `boolean`):

For scheduled monitoring. Reads the highest comment\_id seen on the previous run (per video) from the key-value store, stops paginating once it reaches it, and emits only comments newer than that. Writes the new high-water mark at the end.

## `deltaStateKey` (type: `string`):

Key-value store record key that holds delta-mode state. Use the same key across scheduled runs of the same monitoring job. Ignored unless 'Delta mode' is on.

## `cookies` (type: `string`):

Optional 'name=value; name=value' cookie string from a logged-in douyin.com session. Raises per-session rate limits and comment visibility. Never required. Do not share this value.

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

Proxy settings. Residential proxies are strongly recommended — datacenter IPs are blocked quickly by Douyin.

## `maxConcurrency` (type: `integer`):

Maximum videos scraped in parallel, and the per-host request cap. Higher is faster but more block-prone. Capped at 10.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.douyin.com/video/7677636831337576185",
    "https://v.douyin.com/abcdefg/",
    "7677636831337576185"
  ],
  "maxCommentsPerVideo": 100,
  "includeReplies": false,
  "maxRepliesPerComment": 10,
  "includeVideoMetadata": true,
  "deltaMode": false,
  "deltaStateKey": "DOUYIN_DELTA_STATE",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxConcurrency": 3
}
```

# Actor output Schema

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

All scraped rows in the default dataset. row\_type is one of: comment, reply, video, error.

## `videos` (type: `string`):

The row\_type="video" rows only (present when includeVideoMetadata succeeded).

## `errors` (type: `string`):

The row\_type="error" rows only — one per video that could not be scraped.

# 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.douyin.com/video/7677636831337576185"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("moonweil/douyin-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.douyin.com/video/7677636831337576185"] }

# Run the Actor and wait for it to finish
run = client.actor("moonweil/douyin-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.douyin.com/video/7677636831337576185"
  ]
}' |
apify call moonweil/douyin-comments-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,moonweil/douyin-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/D38blVhtgaUEdaDng/builds/wWcJmpxh3jcp0zUi2/openapi.json
