# Douyin 抖音评论 Comments Scraper - Replies, No Login (`lanternlab318/douyin-comments`) Actor

抖音评论 from public Douyin (抖音) videos: top-level comments and reply threads (up to about 50 replies each), with each commenter's public IP region. No login. Incomplete threads are marked partial, never hidden. $4.99 per 1,000 results + $0.04 per run.

- **URL**: https://apify.com/lanternlab318/douyin-comments.md
- **Developed by:** [Lantern Lab](https://apify.com/lanternlab318) (community)
- **Categories:** Social media, Videos
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.99 / 1,000 results

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

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Douyin 抖音评论 Comments Scraper

Collect the comments (抖音评论) under public Douyin (抖音) videos - top-level comments, their reply threads, and the IP region Douyin shows next to each commenter - from video links or IDs. No login, no cookies, no account of your own; the Actor only reads what Douyin shows a signed-out visitor. **$4.99 per 1,000 results, plus $0.04 per run.** See [Pricing](#pricing).

Input:

```json
{
  "videos": ["https://www.douyin.com/video/7357472760722951433"],
  "maxCommentsPerVideo": 200,
  "includeReplies": true,
  "maxRepliesPerComment": 50
}
```

Output - one row per comment or reply (`recordType: "comment"` / `"reply"`), plus a free `video_summary` row per video. Sample from a real run; nickname and comment text masked:

```json
{
  "recordType": "comment",
  "platform": "douyin",
  "videoId": "7552116854651833639",
  "videoUrl": "https://www.douyin.com/video/7552116854651833639",
  "commentId": "7552935887613920060",
  "parentId": null,
  "replyToReplyId": null,
  "text": "说了半天…",
  "authorNickname": "c***",
  "ipLabel": "山东",
  "likeCount": 5,
  "replyCount": 0,
  "repliesCollected": null,
  "publishedAt": "2025-09-22T15:28:49+00:00",
  "collectedAt": "2026-10-23T16:39:37+00:00",
  "source": "comment_list",
  "accessMode": "direct",
  "collectionStatus": "complete",
  "partialReason": null
}
```

Limits:

- **Public data only.** What a signed-out visitor sees; the Actor never logs in, and it collects less than the comment count Douyin displays (see [How much you get](#how-much-you-get)).
- **Reply threads are capped by Douyin itself** at about the first 50 replies per comment; longer threads are marked `partial` with the reason (see [Reply completeness](#reply-completeness)).
- **Every short collection is named, never hidden.** A thread or video cut short carries `collectionStatus` and `partialReason`; a deleted, private or withheld video is reported as such - never as "0 comments".
- **Your limits stop collection and appear in the row**: `maxCommentsPerVideo`, `maxRepliesPerComment` and the maximum cost per run (see [Pricing](#pricing)).

### What it does

- Reads the top-level comments of each video, page by page, up to your limit.
- Reads each comment's reply thread: the first page Douyin serves visitors, up to about 50 replies per comment, up to your limit per thread.
- Records the commenter's public nickname and IP region (`ipLabel`, e.g. 山东) exactly as Douyin shows them.
- Writes one `video_summary` row per input: video status, how many comments Douyin displays, how many were collected, and why anything is missing.
- Runs without a proxy by default. If Douyin refuses a video's comment list, that one video is retried through an Apify residential proxy exiting in China (limited per run, can be turned off).

### What it does not do

- **No search.** Douyin requires a logged-in account to search, and this Actor never logs in. Bring video links or IDs.
- **No login-only content.** Only what a signed-out visitor can see.
- **No user profiles.** No user IDs, `sec_uid`, avatars or profile links - only the nickname and IP region shown beside the comment.
- **Long reply threads are cut short.** Douyin serves visitors about the first 50 replies of a thread; longer threads are marked `partial`. See [Reply completeness](#reply-completeness).
- **Not every comment counted on the video.** Douyin counts more comments than it shows a signed-out visitor. See [How much you get](#how-much-you-get).
- No video downloads, no live comments (danmaku), no scheduling built in.

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `videos` | - | Video links (`https://www.douyin.com/video/...`), share links (`https://v.douyin.com/...`), links with `modal_id=...`, or numeric video IDs. |
| `maxCommentsPerVideo` | 200 | Top-level comments per video. |
| `includeReplies` | true | Also collect reply threads. |
| `maxRepliesPerComment` | 50 | Replies per thread. |
| `proxyConfiguration` | no proxy | Standard Apify proxy setting for all requests. |
| `residentialFallback` | true | Retry a refused video through a residential proxy in China. |
| `maxFallbackVideos` | 3 | Videos per run that may use that fallback, at most 10. 0 turns it off. |

### Output

One dataset, three kinds of rows (`recordType`): `comment`, `reply` and `video_summary`. Two views are predefined: **Comments and replies** and **Video summaries**. A `RUN_SUMMARY` record in the key-value store has run totals.

Comment and reply fields:

| Field | Meaning |
| --- | --- |
| `recordType` | `comment` (top level) or `reply`. |
| `videoId`, `videoUrl` | The video. |
| `commentId` | The comment or reply ID. |
| `parentId` | For replies: the top-level comment's `commentId`. |
| `replyToReplyId` | For a reply to another reply in the same thread: that reply's ID. |
| `text` | Comment text. |
| `authorNickname` | Public display name shown on the page. |
| `ipLabel` | IP region shown by Douyin, or null if none is shown. |
| `likeCount` | Likes. |
| `replyCount` | Top-level comments: replies Douyin reports for the thread. |
| `repliesCollected` | Top-level comments: replies of this thread in the dataset. |
| `publishedAt` | Publication time (UTC). |
| `collectedAt` | Collection time (UTC). |
| `source` | `comment_list`, `reply_api` (the thread's own reply list) or `reply_preview` (a reply Douyin shows inline under the comment). |
| `accessMode` | `direct`, `custom_proxy` or `residential_fallback`. |
| `collectionStatus` | `complete` or `partial` for the thread this row belongs to. |
| `partialReason` | Why the thread is partial. |

`video_summary` fields: `input`, `videoId`, `videoStatus`, `videoTitle`, `videoPublishedAt`, `displayedCommentCount`, `topLevelCollected`, `threadsWithReplies`, `expectedReplies`, `repliesCollected`, `threadsComplete`, `threadsPartial`, `records`, `accessMode`, `collectionStatus`, `partialReason`, `elapsedMs`.

#### Video status

| `videoStatus` | Meaning |
| --- | --- |
| `available` | Public video, comments read. |
| `deleted` | Douyin says the video was deleted. |
| `private` | The author made the video private or friends-only. |
| `unavailable` | Douyin refuses to show the video to visitors (removed, restricted or never existed) and gives its reason in `partialReason`. |
| `invalid_input` | The input is not a Douyin video link or ID. |
| `unknown` | Douyin did not say; comments were still attempted. |

For `deleted`, `private`, `unavailable` and `invalid_input` the counts are **null**, not 0, and `collectionStatus` is `not_collected`.

#### What `partial` means

`collectionStatus` on a `video_summary`:

- `complete`: every top-level comment Douyin returns was collected, and every reply thread was read to its end.
- `partial`: something is missing. `partialReason` lists what, with a count per reason, for example:
  - `cap:maxCommentsPerVideo=200`, `cap:maxRepliesPerComment=50`: your limits.
  - `replies:later_pages_refused x3`: three threads are longer than the first page of replies Douyin serves to visitors (see below).
  - `replies:fewer_than_reply_count x1`: a thread was read to its end but holds fewer replies than `replyCount`.
  - `replies:preview_only x2`: Douyin refused the thread's reply list, or the page that requests it stopped answering; only the replies it shows inline under the comment are included (`source: reply_preview`).
- `blocked`: Douyin refused the comment list even after the fallback.
- `not_collected`: see video status.

On a comment or reply row, `collectionStatus` describes that comment's thread.

`displayedCommentCount` is the number Douyin prints on the video. It counts replies too, and it is higher than what a signed-out visitor is shown (see [How much you get](#how-much-you-get)). A thread is `complete` only when it holds at least its `replyCount`; a thread that ends with fewer is `partial` with `replies:fewer_than_reply_count (x of y)`, even when Douyin served nothing more, because Douyin's `replyCount` also counts replies it does not serve to visitors.

### How much you get

Measured on 10 public videos on 2026-10-09, 8 of them about fertilizer and crop nutrition. Douyin changes its web client often, so results after a change may differ.

- **Replies:** up to about 50 replies per comment.
- **Threads with up to 50 replies:** about 99.6% of their replies were collected.
- **Threads with more than 50 replies:** only about the first 50 are returned, and the comment and its replies are marked `partial` with the reason.
- **Against the count shown on the video:** on the 9 videos whose comment list was read to the end, top-level comments plus replies collected came to **about 65%-88%** of the comment count Douyin displays. The rest is not shown to a signed-out visitor; Douyin's page says more comments can be seen after logging in, and why each one is hidden was not checked. Every `video_summary` row shows the displayed count next to what was collected.

### Reply completeness

Douyin serves signed-out visitors **the first page of each reply thread, up to about 50 replies**. Later pages are refused ("blocked") whatever the page size, the way the request is sent or the exit country, so the replies of a longer thread beyond the first 50 cannot be collected without logging in, and this Actor never logs in. After the first such refusal in a run, the Actor stops asking for later pages and marks those threads `partial` with `replies:later_pages_refused`.

Measured on 10 public videos, 8 of them about fertilizer and crop nutrition (2026-10-09, no proxy, up to 500 top-level comments per video; 677 threads, 3,696 replies reported by Douyin):

| Thread length (`replyCount`) | Threads | Complete threads | Replies collected |
| --- | --- | --- | --- |
| 1-3 | 464 | 460 | 668 / 672 (99.4%) |
| 4-9 | 103 | 100 | 580 / 583 (99.5%) |
| 10-20 | 96 | 91 | 1,208 / 1,213 (99.6%) |
| 21-50 | 10 | 10 | 266 / 266 (100%) |
| more than 50 | 4 | 0 | 198 / 962 (20.6%) |
| **all threads** | 677 | 661 | **2,920 / 3,696 (79.0%)** |

- **Threads with up to 50 replies: about 99.6% of their replies were collected.**
- **Threads with more than 50 replies: only about the first 50 replies are returned, marked `partial`.**
- The overall share depends on how many long threads a video has: 79.0% above, where 4 threads of 124-512 replies hold 26% of the replies Douyin reports.

Every comment row carries `replyCount` and `repliesCollected`, and every `video_summary` carries `expectedReplies` and `repliesCollected`, so you can see per thread and per video what is missing.

Reply pages are requested through a live Douyin page. That page sometimes stops answering partway through a run (it was seen moving on to another video, hanging or crashing); the Actor then retries the thread with a fresh visitor straight away, which adds about a minute to the run. If Douyin refuses even the first page of replies for a visitor, the Actor also tries a fresh visitor. A run gets up to two fresh visitors for replies; after that it keeps only the replies Douyin shows inline under each comment (`source: reply_preview`, thread `partial`).

### Pricing

Pay per event: **$4.99 per 1,000 results, plus $0.04 per run.** Apify platform usage, including the residential fallback, is included; you pay only these two events.

| Event | Price | Charged for |
| --- | --- | --- |
| `result` | $0.00499 ($4.99 per 1,000) | Each comment or reply row in the dataset. `video_summary` rows are free. |
| `run-start` | $0.04 | Once per run, when it starts. |

Example: one video with 300 comments and replies costs $0.04 + 300 x $0.00499 = $1.54.

To cap a run, set **Maximum cost per run** in the run options. When the cap is reached, the Actor stores no further rows, stops before the next video, marks a video it cut short `partial` with `cap:maxTotalChargeUsd`, and records `"fatal": "max total charge reached"` in `RUN_SUMMARY`. Videos after it get no rows.

### Resources

- Runs with **1 GB memory** by default; you can choose 1-8 GB in the run options. Reply requests need a live Douyin page in the background, and below 1 GB that page crashes and the run collects nothing. The run fee is the same at any memory.
- A run opens a visitor session first (about 30 seconds), then takes roughly 10-120 seconds per video depending on its size; the 10-video run above took 14 minutes for 4,405 rows.

### Notes

- Respect Douyin's terms and local law, and only collect what you need. Nicknames and IP regions are personal data in many jurisdictions.
- Douyin changes its web client often; a change can break reply collection before it breaks anything else. The `RUN_SUMMARY` record shows request outcomes per endpoint.

# Actor input Schema

## `videos` (type: `array`):

Public Douyin videos, one per line: a video link (https://www.douyin.com/video/...), a share link (https://v.douyin.com/...), a link with modal_id=..., or the numeric video ID. Search is not supported.

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

Stop reading a video's top-level comments after this many. A video cut off here is marked partial.

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

Also collect the replies under each top-level comment.

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

Stop reading one reply thread after this many replies. A thread cut off here is marked partial.

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

Proxy for all requests. Off by default: Douyin answers guests without a proxy.

## `residentialFallback` (type: `boolean`):

When Douyin refuses a video's comment list (captcha, repeated empty answers, block status), retry that video once through an Apify residential proxy exiting in China. Residential traffic costs more than direct requests.

## `maxFallbackVideos` (type: `integer`):

Upper limit per run on videos moved to the residential proxy. 0 turns the fallback off.

## `requestDelaySecs` (type: `number`):

Minimum pause between two requests from the same guest identity.

## `msTokenWaitSecs` (type: `integer`):

How long a new guest page may take to receive its security token (msToken) before reply requests start. Longer waits make reply requests more likely to be answered.

## Actor input object example

```json
{
  "videos": [
    "https://www.douyin.com/video/7357472760722951433"
  ],
  "maxCommentsPerVideo": 200,
  "includeReplies": true,
  "maxRepliesPerComment": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "residentialFallback": true,
  "maxFallbackVideos": 3,
  "requestDelaySecs": 1,
  "msTokenWaitSecs": 20
}
```

# Actor output Schema

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

No description

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

No description

## `runSummary` (type: `string`):

No description

# 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 = {
    "videos": [
        "https://www.douyin.com/video/7357472760722951433"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/douyin-comments").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 = {
    "videos": ["https://www.douyin.com/video/7357472760722951433"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/douyin-comments").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 '{
  "videos": [
    "https://www.douyin.com/video/7357472760722951433"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call lanternlab318/douyin-comments --silent --output-dataset

```

## MCP server setup

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

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/JLb5tcWMy7vxxsMiQ/builds/VI1AhaZQR2rS0M5Me/openapi.json
