# Douyin (抖音) Hot Search Scraper - Trending Words + Heat Value (`lanternlab318/douyin-hot-search`) Actor

The live Douyin (抖音) hot search board as a signed-out visitor sees it: rank, word, heat value, badge, event time, video count, cover and search URL, plus Douyin word and event IDs. Also the rising strip. No login, no cookie. Cut-short boards are marked partial, never reported as empty.

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

## Pricing

$3.99 / 1,000 hot words

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 (抖音) Hot Search Scraper - Live Trending Words + Heat Value

The Douyin hot search board (抖音热搜榜) as a signed-out visitor sees it: one row per trending word with its rank, Douyin's own heat value, the badge, the event time, how many videos sit under it, the cover image and the search URL that opens it - plus Douyin's own word and event IDs so you can join runs over time. No Douyin login, no cookies, no account of your own. **$3.99 per 1,000 words, no start fee.** See [Pricing](#pricing).

Input:

```json
{
  "boards": ["hot", "rising"]
}
```

Output - one row per word (`recordType: "hot_word"`); per-board status rows and the run summary are free. Sample from a real run (long signed URLs shortened):

```json
{
  "recordType": "hot_word",
  "board": "hot",
  "rank": 1,
  "word": "骑行四千五百公里赴赛里木湖",
  "hotValue": 11453793,
  "label": 3,
  "labelUrl": "https://lf3-static.bytednsdoc.com/obj/eden-cn/vjl_avo_up…",
  "eventTime": "2026-10-23T08:23:58.000Z",
  "publishedAt": "2026-10-23T08:23:58.000Z",
  "videoCount": 1,
  "discussVideoCount": 1,
  "coverUrl": "https://p26-sign.douyinpic.com/tos-cn-i-0629/ocbTHDibbEBdqwAA2…",
  "url": "https://www.douyin.com/search/%E9%AA%91%E8%A1%8C%E5%9B%9B%E5…",
  "sentenceId": "2709968",
  "groupId": "7697991401896744201",
  "sentenceTag": 10000,
  "wordType": 1,
  "maxRank": 2,
  "boardActiveTime": "2026-10-24 14:07:46",
  "partial": false,
  "partialReason": null,
  "collectedAt": "2026-10-24T06:10:27.463Z"
}
```

Limits:

- **Public data only.** The board a visitor sees, read without a login, a cookie or an account. No personal data: the board carries words, not people.
- **The board is as long as Douyin makes it** - about 50 words on `hot` and about 5 on `rising`. There is no paging past it, so one run is one snapshot. Run it on a schedule to build a history.
- **The signed-in-browser board can be one or two words longer.** Compared against Douyin's own hot page at the same board snapshot (2026-10-24 14:07:46), the page showed 50 ranked words plus one promoted entry pinned on top, while the public board endpoint served 48 to a visitor who sends no cookie and no browser signature - the page's top word and one mid-list word were not in the public answer at all, and its `position` numbers are counted over the 48 it did serve. So `rank` is the position Douyin served, which can sit one below the rank on screen. Everything the public answer does carry matched the page exactly: 60 of 60 field comparisons over five words, heat value included. The same endpoint also varies between reads at some snapshots (48 or 49 words), so this Actor reads it twice and keeps the fuller answer whole - it never merges two reads, which would carry a ranking Douyin never served, and `boardSampleSizes` in the run summary shows what each read carried. Follow a word across runs by `sentenceId`, and use `maxRank` for Douyin's own best-rank value.
- **Douyin publishes no heat value on the rising strip**, so `hotValue` is `null` there rather than `0`. The same holds for the entry it pins above the hot board.
- **Entertainment, society and challenge boards are not included**: Douyin's public board endpoint ignores the board parameter and the dedicated paths are closed to signed-out visitors, so there is nothing to read them from.
- **Related videos per word are not included in this version** - the word's `url` opens Douyin's own search page for it.
- **Every board gets a status row saying what happened.** A board Douyin answered with nothing is an empty result; a board that was refused or cut short is `partial` with the reason - the two are never confused.

### What it does

- Reads both lists Douyin's own hot page shows - the hot search board (`word_list`) and the rising strip (`trending_list`) - out of the **same** response, so asking for both costs the same as asking for one.
- Reads the board twice and keeps the more complete answer, because the endpoint varies by a word between reads of the same snapshot (see [Limits](#limits-and-boundaries)).
- Keeps Douyin's ranking (`rank`, and `maxRank`, the best rank the word has reached), its heat value, and its own identifiers (`sentenceId` for the word, `groupId` for the event behind it).
- Keeps the platform's own event time (`eventTime`, mirrored as `publishedAt`), Douyin's board refresh stamp (`boardActiveTime`, its own Beijing-time wording) and our collection time (`collectedAt`) as separate fields.
- Deduplicates by board and word inside the run, so one board never writes the same word twice.
- Says what happened per board. A board Douyin answered with nothing is recorded as an empty result; a board that was refused, capped or cut short is marked `partial: true` with the reason. The two are different records and are never confused.
- Caps itself: `maxUpstreamRequests` is a hard ceiling on requests to Douyin for the whole run, derived from your input when you leave it empty, so a run is never unbounded.
- Charges only word rows. Empty results, per-board status rows and the run summary are free.

### What it does not do

- No videos, comments, authors or music charts - the board's words, not the content under them.
- No login-only or app-only boards, no region-specific boards, no historical board (Douyin serves the current snapshot only).
- No video downloads and no images stored: `coverUrl` is Douyin's own URL, and it expires.
- No scheduling or monitoring built in; use Apify schedules.

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `boards` | `["hot"]` | `hot` (the hot search board, about 50 ranked words) and/or `rising` (the "rising now" strip, about 5 words). Both come from the same response. |
| `maxWordsPerBoard` | `500` | Upper bound on rows per board. The board is shorter than this by default; a board cut short by your setting is marked `partial` with `word_limit_reached`. |
| `maxUpstreamRequests` | derived (4) | Hard ceiling on requests to Douyin for the whole run, retries included. The default pays for two board reads with one retry each. Set it to 1 for a single read. |
| `proxyConfiguration` | Apify Proxy | The proxy the request goes through. The datacenter default is what this Actor is measured on. |

### Output

One row per word, `recordType: "hot_word"`:

| Field | Meaning |
| --- | --- |
| `board` | `hot` or `rising` - which board the row came from. Part of the dedupe key. |
| `rank` | Douyin's own `position` on that board, counting from 1. An entry Douyin pins above the ranking carries `null` rather than an invented number, and the rising strip, which has no `position` at all, is numbered in the order Douyin sent. |
| `word` | The trending word, exactly as Douyin writes it. |
| `hotValue` | Douyin's heat value for the word, or `null` when Douyin publishes none (the whole rising strip). |
| `label` / `labelUrl` | Douyin's badge code for the word and the badge image it serves. The codes are Douyin's own and are passed through, not translated. |
| `eventTime` | When the event behind the word started, as Douyin dates it, in ISO 8601. |
| `publishedAt` | The same instant under this repo's shared field name. |
| `videoCount` / `discussVideoCount` | Videos Douyin counts under the word, and how many of them are discussion videos. `null` when Douyin sends none. |
| `url` | The public Douyin search page the word opens. |
| `coverUrl` | Douyin's own cover image for the word. A signed URL that expires. |
| `sentenceId` / `groupId` | Douyin's identifiers for the word and for the event behind it - use them to follow a word across runs. |
| `sentenceTag` / `wordType` | Douyin's own category and type codes, passed through without interpretation. |
| `maxRank` | The best rank the word has reached, when Douyin reports it. |
| `boardActiveTime` | Douyin's "board last refreshed" stamp, in its own Beijing-time wording. |
| `partial` / `partialReason` | Always `false` / `null` on a word row: the row exists because the word was collected. |
| `collectedAt` | Our own collection time, separate from `eventTime`. |

One row per board, `recordType: "board_status"`, never charged:

| Field | Meaning |
| --- | --- |
| `wordsReturned` / `wordsCharged` | Rows this board wrote and charged. |
| `duplicatesSkipped` | Words skipped because this board had already written them. |
| `wordsAvailable` | How many words the board itself showed, before your own limits. |
| `boardActiveTime` | Douyin's board refresh stamp. |
| `partial` / `partialReason` | `true` with `refused`, `upstream_request_cap`, `charge_limit_reached`, `word_limit_reached`, `upstream_error` or `not_collected` when the board's words are incomplete. |
| `statusText` | The same thing in a sentence, so the dataset explains itself. |

The last row is `recordType: "RUN_SUMMARY"` - words collected and charged per board, duplicates skipped, empty boards, partial boards by reason, requests sent, bytes moved, and why collection stopped. The same object is also in the run's key-value store under `RUN_SUMMARY`.

#### Partial is not empty

`wordsReturned: 0` with `partial: false` means Douyin showed no word on that board. `partial: true` means the board's words are incomplete and there may well be more. A run that collected nothing *and* had partial boards fails rather than reporting an empty success.

### Limits and boundaries

- Only the public board. No login, no cookies, no account, no personal data.
- Douyin refuses a request by answering with an empty body rather than an error. This Actor treats that as a refusal, retries it once on a fresh exit IP, and marks the board `partial: true` with `refused` if it keeps happening - it is never reported as an empty board.
- `coverUrl` and `labelUrl` are Douyin's own signed URLs and stop working after a few hours. Fetch them promptly if you need the image.
- `eventTime` and `hotValue` are Douyin's own values and are not re-derived or estimated.

### Pricing

Pay per event, no start fee:

| Event | Price | Charged for |
| --- | --- | --- |
| `result` | $0.00399 ($3.99 per 1,000) | Each word row written. Per-board status rows, empty results and the run summary are free. |

Set a maximum charge for the run and the Actor stops before going over it.

# Actor input Schema

## `boards` (type: `array`):

Which Douyin board to read. "hot" is the hot search board itself (about 50 words, ranked). "rising" is the short "rising now" strip Douyin shows above it (about 5 words, no heat value published). Both come out of the same response, so asking for both costs the same as asking for one. Douyin's entertainment, society and challenge boards have no signed-out entry point and are not offered here.

## `maxWordsPerBoard` (type: `integer`):

Upper bound on the rows one board may produce. Douyin publishes about 50 words on the hot board and about 5 on the rising strip, and offers no way to page past them, so leaving this alone returns the whole board. A board cut short by this setting is marked partial with partialReason "word_limit_reached", so a short list is never mistaken for the end of the board.

## `maxUpstreamRequests` (type: `integer`):

Hard ceiling on the HTTP requests this run sends to Douyin, retries included. At the ceiling collection stops - the boards not read are written with partialReason "upstream_request_cap" and the run then ends normally. Leave empty and the Actor works a ceiling out from your input: two board reads, of which the fuller one is kept, with one retry each. Set it to 1 to take a single read.

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

The proxy the board request goes through. The default (Apify Proxy, datacenter) is the cheapest and is what this Actor is measured on; Douyin answers its public board endpoint over it without a login, a cookie or a block.

## Actor input object example

```json
{
  "boards": [
    "hot"
  ],
  "maxWordsPerBoard": 500,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/douyin-hot-search").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/douyin-hot-search").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 '{}' |
apify call lanternlab318/douyin-hot-search --silent --output-dataset

```

## MCP server setup

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

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/hyyKbOIoZgAFwIFA1/builds/FnG4Wd39qdLsFf0B0/openapi.json
