# Toutiao News Scraper – 今日头条 China News, Hot Board & Comments (`deadc0dex/toutiao-scraper`) Actor

Toutiao scraper API (今日头条, ByteDance news app), no login: Toutiao hot board/hot list = Chinese hot search, trending topics (头条热榜), Toutiao news & keyword search (头条新闻), article details + full text, channel feeds, Toutiao comments, replies, authors, followers, video URLs. $0.004/row, $0.0015/comment.

- **URL**: https://apify.com/deadc0dex/toutiao-scraper.md
- **Developed by:** [AsiaData](https://apify.com/deadc0dex) (community)
- **Categories:** News, Social media, AI
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 result (article, topic or profile)s

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

## Toutiao Scraper – 今日头条 News, Hot Board & Comments

**What it returns:** China news from Toutiao (今日头条 / Jinri Toutiao, ByteDance's Chinese news app) as flat JSON rows, no login or cookies: the **Toutiao Hot Board (头条热榜 hot list, Chinese hot search / trending topics)**, **keyword news search** (English keywords auto-translated to Chinese), **channel news feeds**, **Toutiao articles with full text**, **comments and replies**, **author profiles (with follower counts), their articles and micro-posts (微头条)**, **video stream URLs (MP4)** and **related articles**.
**New in v2.0 – Toutiao keyword research in one call:** `{"operation":"keywordResearch","keywords":["新能源汽车"]}` → the top articles for the keyword (filters + `sortBy`), each with **full text**, followed by its **comments** (+ replies), plus one free **insights** row (top terms, comment sentiment labelled *heuristic*, engagement medians, top sources, commenter IP regions). Every row now carries **trust fields** (`isPartial`, `unavailableFields`, `collectionWarnings`) and articles carry **engagement metrics** (`engagementRate`, `likesPer1kReads`, …); `trackMetrics: true` turns any scheduled run into a **snapshot tracker** (reads / hot value / rank deltas vs the previous run).
**What you give it:** nothing (Hot Board), or `keywords[]`, `urls[]` / `ids[]`, `users[]`, `channels[]`. You control what is fetched (`include`), what is returned (`fields`, `outputMode` incl. the token-efficient **`lean`** mode), filters (`dateFrom`, `minReads`, `textContains`, `sortBy`…), monitoring (`onlyNew`) and the spend (`maxItems`, `maxCostUsd`, free `dryRun`).
**Price:** $0.004 per row (topic, article, profile; full text included), $0.0015 per comment or reply. Errors, plan and summary rows are free.
**Speed (measured on Apify):** a default call (20 Hot Board rows) finishes in ~2 s; 20 search results or 20 channel-feed articles in ~3–4 s; 50 related articles in ~6 s; 20 articles with full text in ~8 s. Unofficial; not affiliated with Toutiao or ByteDance.

### For AI agents

- **Toutiao MCP tool:** `deadc0dex--toutiao-scraper` (Apify MCP server `https://mcp.apify.com`). Call with `call-actor`, then read rows with `get-dataset-items`. Default calls finish in 2–8 s (well inside the 30 s wait).
- **One call, many inputs:** every input is an array; in `operation: "auto"` all filled inputs run (one task each).
- **Never a silent failure:** bad inputs come back as `type: "error"` rows with `errorCode`, `message` and a concrete `fix`; the run still ends SUCCEEDED. The **last row is always `type: "summary"`** (counts, billed events, cost, warnings, `nextSteps`).
- **Token-efficient output:** `{"outputMode":"lean"}` returns only `type`, `id` and the fields agents use most, no null/empty keys, dates as `2026-10-10T10:02Z`, text cut to 300 chars: on average ~70 % fewer tokens than the default rows (41–87 % by row type) (see *Token-efficient mode* below). Add `"leanFormat":"table"` for one TSV table in the summary row.
- **Free discovery:** `{"operation":"capabilities"}` returns a machine-readable manifest (operations, fields, include options, filters, prices, limits). `{"dryRun": true, ...}` returns one free `plan` row per input with estimated rows, cost and seconds.

#### Copy-paste inputs (one per operation)

| operation | input |
|---|---|
| `hotBoard` (hot list / hot search) | `{}` or `{"operation":"hotBoard","maxItems":50}` |
| `search` (keyword search) | `{"keywords":["比亚迪","artificial intelligence"],"maxItems":20}` |
| `feed` | `{"channels":["news_tech","news_finance"],"maxItems":20}` |
| `articleDetails` (article details + full text) | `{"urls":["https://www.toutiao.com/article/7694967967913607743/"]}` |
| `comments` | `{"operation":"comments","ids":["7694967967913607743"],"maxItemsPerInput":20,"repliesPerComment":3}` |
| `replies` | `{"operation":"replies","ids":["<commentId from a comment row>"],"maxItems":20}` |
| `userProfile` | `{"operation":"userProfile","users":["1755235951852024"]}` |
| `userArticles` | `{"users":["1755235951852024"],"profileTab":"all","maxItems":10}` |
| `related` | `{"operation":"related","ids":["7694967967913607743"],"maxItems":30}` |
| video URLs | `{"ids":["7694967481320718848"],"include":["video"]}` |
| lean output | `{"keywords":["华为"],"outputMode":"lean","maxItems":10}` |
| `keywordResearch` (keyword → articles → full text → comments → insights) | `{"operation":"keywordResearch","keywords":["新能源汽车"],"maxArticles":10,"maxCommentsPerArticle":20,"sortBy":"mostCommented"}` |
| comment analysis for chosen words | `{"operation":"keywordResearch","keywords":["BYD"],"maxArticles":5,"maxCommentsPerArticle":50,"matchTerms":["电池","续航"]}` |
| hot board trend tracking (schedule hourly) | `{"operation":"hotBoard","trackMetrics":true,"deltaKey":"hot-board"}` |
| cost check | `{"keywords":["华为"],"maxItems":200,"dryRun":true}` / `{"operation":"capabilities"}` |

#### Operations (`operation`)

| operation | input | returns (row `type`) | billed event |
|---|---|---|---|
| `auto` (default) | whatever is filled: urls/ids → articleDetails, users → userArticles, keywords → search, channels → feed; nothing → hotBoard | – | – |
| `hotBoard` | none | `hotTopic`: rank, title, hotValue, label, searchQuery, categories (top 50) | result |
| `search` | `keywords[]` | `article`: title, abstract, source, authorId, publishedAt, readCount, likeCount, commentCount | result |
| `feed` | `channels[]` (news_hot, news_tech, news_finance, news_world, …) | `article` (latest articles of the channel) | result |
| `articleDetails` | `urls[]` / `ids[]` (article, video, 微头条) | `article` with full `text`, all `imageUrls`, author, exact time, all counts | result |
| `comments` | `urls[]` / `ids[]` | `comment` rows (+ `reply` rows with `repliesPerComment`) | comment |
| `replies` | `ids[]` = commentId values | `reply` rows | comment |
| `userProfile` | `users[]` (numeric id or `/c/user/<digits>/` URL) | `user`: name, bio, verification, media account, area, IP location, created | result |
| `userArticles` | `users[]` (+ `profileTab`: `articles` default, `posts` = 微头条 micro-posts, `all`) | `article` rows of that author, newest first, with reads | result |
| `related` | `urls[]` / `ids[]` | `article` rows Toutiao recommends under the article | result |
| `keywordResearch` | `keywords[]` + `maxArticles` (10), `maxCommentsPerArticle` (20), `repliesPerComment` (0) | per keyword: `article` rows with full text, each followed by its `comment` / `reply` rows (`articleTitle`), then one free `insights` row | result + comment |
| `capabilities` | none | one free `capabilities` row | free |

#### Control what is fetched and returned

| input | values | effect |
|---|---|---|
| `include[]` | `fullText`, `images`, `video`, `author`, `comments` | opt-in enrichment per article row: full text + images + exact time (~0.2–0.5 s/item, 8 in parallel, same price); all image URLs (only rows without a working image are opened); **video** = playable MP4 `videoUrl` + `videoStreams` for video rows (links expire ~1 h, `videoUrlExpiresAt`); nested author profile (free); nested top comments (`commentsPerItem`, billed per comment) |
| `fields[]` | any field names, e.g. `["title","url","publishedAt","readCount"]` | return only these fields (`type` always kept) |
| `outputMode` | `compact` (default) / `lean` / `full` | lean = fewest tokens (curated fields, no nulls, envelope only in the summary); full adds every field plus v0.x names (`mode`, `query`, `groupId`, `scrapedAt`) |
| `leanFormat` | `rows` (default) / `table` | table also writes key-value record `LEAN_TABLE` (TSV, header once per row type) and copies it into the summary row when < 20 KB; same billing |
| `maxTextChars` | default 300 in lean, no cut otherwise | cut `text`, `abstract`, `description` to N chars (ends with …, row gets `truncated: true`) |
| `textContains` | e.g. `["比亚迪","BYD"]` | keep rows whose title/abstract/text contains any word, applied before billing |
| `onlyNew` + `memoryName` | `true`, `"tech-feed"` | monitoring: skip (and do not bill) rows earlier runs with the same memoryName already returned (ids kept 30 days in named key-value store `toutiao-seen-<memoryName>` in your account) |
| `dateFrom`, `dateTo` | `2026-10-01`, ISO date-time, unix seconds, `24h`, `7d`, `4w` | publish-time window, applied before billing |
| `minReads`, `minLikes`, `minComments` | integers | minimum counts, applied before billing |
| `sortBy` | `relevance` (default), `newest`, `mostRead`, `mostLiked`, `mostCommented` | order within each input |
| `contentType` | `any`, `article`, `video`, `micro-post` | keep one kind (videos come from feed, userArticles, related) |
| `maxItems` | default 20, max 10000 | total billable rows (comments included) |
| `maxItemsPerInput` | default maxItems ÷ inputs | cap per keyword / channel / article / user |
| `maxCostUsd` | e.g. `0.05` | stop before charges exceed it (summary says `stopReason`) |
| `expandQuery` | `true` (default) | English → Chinese with a curated ~950-entry dictionary (BYD → 比亚迪, Xiaomi → 小米, Elon Musk → 马斯克, Federal Reserve → 美联储); rows show `queryUsed` |
| `dryRun` | `true` | free plan, nothing fetched or billed |
| `maxArticles`, `maxCommentsPerArticle` | default 10 / 20 (max 100 / 500) | keywordResearch depth per keyword; `maxItems` defaults to keywords × maxArticles × (1 + comments) |
| `matchTerms` | e.g. `["电池","续航"]` | keep only comments/replies containing any word; the insights row counts each word. Applied after each page is fetched, so it **lowers charges, not requests** (Toutiao has no server-side comment search). Filter articles with `textContains` |
| `insights` | default on for keywordResearch, else off (`include: ["insights"]`) | one free `insights` row: n-gram top terms, lexicon sentiment (labelled heuristic), engagement medians, top articles/comments/sources, commenter IP regions. No LLM |
| `trackMetrics` + `deltaKey` | `true`, `"hot-board"` | snapshot mode: rows get `snapshotAt`, `previousSnapshotAt`, `isNewSinceLastSnapshot` and deltas vs the previous run with the same key (compact metrics kept 30 days in key-value store `toutiao-snap-<deltaKey>` in your account) |

#### Output fields (compact default)

Every data row: `type`, `operation`, `input`, `queryUsed`, `id`, `url`, `fetchedAt`, then:

- `hotTopic`: rank, title, hotValue, label, searchQuery, categories, isPinned
- `article`: rank, title, abstract, contentType, source, authorName, authorId, publishedAt, readCount, likeCount, commentCount, shareCount, coverImageUrl (+ text, textLength, imageUrls, authorFollowerCount, authorVerification, favoriteCount, originalUrl, videoPlayCount with full text; + `author` / `comments` objects when included)
- `comment` / `reply`: rank, commentId, groupId (article id), parentCommentId, text, authorName, authorId, likeCount, replyCount, publishedAt, ipLocation
- comment rows in `outputMode: "full"` or via `fields` also have `authorVerifiedReason` (commenter's verification text) and `hasAuthorReplied` (true when the article's author replied to that comment).
- `user`: userId, name, description, verified, verification, isMediaAccount, area, ipLocation, createdAt, avatarUrl, followerCount
- with `include: ["video"]`, video `article` rows add: videoUrl, videoUrlExpiresAt, videoWidth, videoHeight, videoDefinition, videoStreams (all renditions)
- v2.0 on every compact/full data row: `isPartial`, `unavailableFields`, `collectionWarnings` (trust); articles: `likesPer1kReads`, `commentsPer1kReads`, `sharesPer1kReads`, `engagementRate` (computed); comments: `commentReplyRatio` (computed); keywordResearch comments: `articleTitle`. Lean rows add them only with `include: ["metrics"]` / `["trust"]` or `fields`.
  Unknown values are `null`, lists are `[]`, ids are strings, counts are integers, times are UTC ISO-8601. Full list: `outputMode: "full"` or the capabilities row.

**Which fields each source fills (audited v1.2, checked against the Toutiao article page):** search rows have reads/likes/comments and exact time but no share/favorite counts; channel-feed rows from the PC pool have only comment counts and the feed time (`feedTime`, when Toutiao pushed the item, not `publishedAt`) – add `include: ["fullText"]` for exact time, reads, likes, shares, author id; userArticles (articles tab) reads are rounded ("11万" → 110000) and the time is relative (`relativeTime`); `readCount` is Toutiao's live view counter and `hotValue` the live heat score (both move by the minute); comment rows from `replies` have no `groupId`/`url` (the article is not known from a comment id). Details: `AUDIT_FIELDS_v1.2.md` in the source.

#### Cost

| what | price |
|---|---|
| row: hot topic, article/video/微头条, author profile (full text, images, nested author included) | **$0.004** |
| comment or reply (also nested via `include: ["comments"]`) | **$0.0015** |
| actor start | $0.00005 |
| error, summary, plan (dryRun), capabilities rows | free |

Default call (20 rows) = $0.08. Hot Board top 50 = $0.20. 20 comments = $0.03.

#### Example calls

```json
{"keywords": ["BYD", "华为"], "maxItems": 10, "dateFrom": "7d", "sortBy": "mostRead"}
```

```json
{"operation": "comments", "urls": ["https://www.toutiao.com/article/7694967967913607743/"], "maxItemsPerInput": 20, "repliesPerComment": 3}
```

```json
{"users": ["1755235951852024"], "include": ["fullText"], "fields": ["title", "url", "publishedAt", "readCount", "text"], "maxItems": 5}
```

#### Error codes (`type: "error"` rows, never billed)

| errorCode | meaning | typical fix |
|---|---|---|
| `MISSING_INPUT` | the operation needs an input that was empty | the `fix` field contains a working example input |
| `INVALID_INPUT` | unknown channel, comment id not numeric | use a listed channel / numeric commentId |
| `INVALID_URL` | not a Toutiao article/user URL or id | pass `https://www.toutiao.com/article/<id>/` or the id |
| `NOT_FOUND` | deleted, private or not public; unknown `/c/user/token/…` profile link | check the id / link |
| `NO_RESULTS` | nothing found, or filters removed everything | shorter / Chinese keyword, relax filters |
| `BLOCKED` | captcha / rate limit after all retries | retry later or use RESIDENTIAL proxy |
| `UPSTREAM_ERROR` | unexpected Toutiao response | retry |
| `COST_LIMIT` | `maxCostUsd` or run charge limit reached; rows so far are valid | raise the limit |
| `RATE_LIMITED` | HTTP 429 / too many requests after all retries; rows so far are valid | retry later, raise `requestDelayMs` |
| `PARTIAL` | a task stopped early because a later page failed (search page, comment page, author list); its rows are valid, the summary `tasksReport` says where it stopped | retry later for the rest |

### Toutiao keyword research: keyword → articles → full text → comments → insights

One input instead of chaining search, articleDetails and comments:

```json
{"operation": "keywordResearch", "keywords": ["新能源汽车", "BYD"], "maxArticles": 10, "maxCommentsPerArticle": 20, "repliesPerComment": 0, "sortBy": "mostCommented", "dateFrom": "7d", "maxCostUsd": 1}
```

For each keyword (English names are translated, see `queryUsed`) the actor ranks Toutiao news results with your filters (`dateFrom`/`dateTo`, `minReads`, `minLikes`, `minComments`, `textContains`, `sortBy`), opens the top `maxArticles` for full text and exact counts, and writes each article row followed by up to `maxCommentsPerArticle` comment rows (Toutiao's hot order; `repliesPerComment` adds replies). Comment pages are fetched in parallel with the article pages. Then one **free `insights` row** per keyword:

- `articleTopTerms` / `commentTopTerms`: most frequent 2–4 character Chinese n-grams and Latin words (by number of rows containing them; no word segmentation, read them as topic hints)
- `commentSentiment`: positive / negative / neutral counts from a ~80-word lexicon with simple negation, `label: "heuristic"`, net label only with ≥ 3 signals – a triage signal, not a classifier
- `termCounts` (for `matchTerms`), `engagement` (median reads / likes / comments / engagementRate), `topArticles`, `topComments`, `topSources`, `commenterIpLocations` (Toutiao's IP属地), `publishedRange`, `caveats`
  Cost = articles × $0.004 + comments × $0.0015 (10 articles + 200 comments = $0.34; most articles have fewer comments, so runs usually cost less – `dryRun` gives the upper bound). Measured on Apify: 3 articles + 11 comments + insights in ~4 s.

### Trust, engagement metrics and snapshot tracking

- **Null vs zero:** `null` = Toutiao did not provide the value for that source (listed in the row's `unavailableFields`); `0` = Toutiao reported zero. `isPartial: true` + `collectionWarnings` when a sub-request for that row failed (e.g. the article page could not be opened, so only list fields are present). The summary has `tasksReport` (per input: requested vs returned, comments, pages fetched, stop reason), `pagesFetched`, `isPartial` and `nullRule`. Pages that fail after earlier pages worked produce a free `PARTIAL` row – never a silent cut.
- **Computed metrics (documented, null-safe):** `engagementRate = (likes + comments + shares) / reads` (4 decimals), `likesPer1kReads`, `commentsPer1kReads`, `sharesPer1kReads` (per 1,000 reads, 2 decimals), comments `commentReplyRatio = replies / max(likes, 1)`. Null when reads are unknown or 0. Formulas are also in the summary (`computedFields`).
- **Snapshot mode:** schedule any operation with `trackMetrics: true` and a `deltaKey`; rows get `readsDelta`, `likesDelta`, `commentsDelta`, `sharesDelta`, `engagementRateDelta` (articles), `hotValueDelta` and `rankDelta` (> 0 = moved up; hot topics), `followerCountDelta` (profiles), `likesDelta` / `replyCountDelta` (comments). Only compact numbers (no text) are stored, 30 days, in your own key-value store.

### Token-efficient mode (lean output for AI agents)

`outputMode: "lean"` is made for LLM context windows:

- each row keeps `type` and `id` plus a curated set: hotTopic `rank, title, hotValue, label, searchQuery` (searchQuery omitted when it equals the title); article `title, source, publishedAt, readCount, likeCount, commentCount, contentType` (only when not "article"), `videoDurationSeconds`/`videoPlayCount` (videos), `text` (full text calls); comment `text, authorName, likeCount, replyCount, publishedAt`; reply adds `parentCommentId`; user `name, description, verification, isMediaAccount, ipLocation, createdAt`;
- keys whose value is null, empty, `[]` or `false` are left out (zero counts stay);
- `operation`, `input`, `queryUsed`, `fetchedAt` appear once in the summary row (`tasks`); runs with several inputs keep `input` on each row;
- `url` is left out when it is the standard pattern (summary `urlPatterns`: article `https://www.toutiao.com/article/{id}/`, micro-post `/w/{id}/`, hot topic `/trending/{id}/`, user `/c/user/{id}/`) and kept when it points elsewhere;
- dates are compact UTC ISO `YYYY-MM-DDTHH:MMZ`; text is cut to `maxTextChars` (300) with … and `truncated: true`;
- `fields[]` still picks any field; `leanFormat: "table"` adds one TSV table (header once) – the fewest tokens.

Measured with tiktoken cl100k_base on real rows (v1.2, avg tokens per row, compact → lean → table): hot topic 143 → 50 → 35; search article 384 → 79 → 57; feed article 371 → 87 → 67; article with full text 3,012 → 396; comment 194 → 79 → 58; reply 178 → 85; profile 340 → 201. Billing is identical in every mode.

```json
{"type":"article","id":"7694977286570787379","title":"美媒感慨：通用、福特都没法立足的日本市场，比亚迪却有戏？","source":"观察者网","publishedAt":"2026-10-10T10:02Z","readCount":3020,"likeCount":54,"commentCount":21}
```

### Toutiao keyword search – Toutiao news search by keyword (头条新闻 / 头条搜索, English → Chinese)

`{"keywords": ["Tesla", "特斯拉"], "maxItems": 20}` searches Toutiao news (so.toutiao.com news tab, then the all tab; up to ~100–160 unique results per keyword for large `maxItems`, 3 pages fetched in parallel). English names are translated with a curated dictionary (BYD → 比亚迪, Federal Reserve → 美联储); each row shows `queryUsed`. Add `dateFrom: "7d"`, `sortBy: "mostRead"` or `minReads` to narrow; filters run before billing and paging stops as soon as enough rows passed them.

### Toutiao hot board / hot list (今日头条热榜) – Toutiao hot search & trending topics

Empty input (or `{"operation":"hotBoard"}`) → what is trending in China right now: up to 50 topics with `hotValue`, `label` and `searchQuery` (~2 s). Pass `searchQuery` values to `search` for the articles behind a topic. Channel feeds work the same way: `{"channels": ["news_finance", "news_tech"], "maxItems": 20}`.

### Toutiao channel feed (头条频道)

`{"channels": ["news_tech", "news_finance"], "maxItems": 20}` → the latest articles of a Toutiao channel feed (24 channels; English names like `tech` and Chinese names like 科技 work). Three Toutiao pools are read in one parallel round (~50 unique rows per channel per run).

### Toutiao related articles

`{"operation": "related", "ids": ["7694967967913607743"], "maxItems": 30}` → the "related reading" articles, videos and micro-posts Toutiao shows under an article (with reads, likes, exact time).

### Toutiao video URLs

`{"ids": ["7694967481320718848"], "include": ["video"]}` (or any feed / related / userArticles call with `include: ["video"]`) → playable MP4 `videoUrl` of the best rendition, `videoUrlExpiresAt` (about 1 h), width / height / definition and all renditions in `videoStreams`, plus `videoPlayCount` and duration. Same price per row.

### Toutiao article details and full text (article scraper)

`{"urls": ["https://www.toutiao.com/article/7694967967913607743/"]}` (or `ids`) → full plain text (one line per paragraph), all image URLs, author, exact publish time and all counts. Add `include: ["fullText"]` to any search/feed/userArticles/related call to get the same for every row (same price). Use `fields` to get only what you need, e.g. `["title","url","publishedAt","text"]`.

### Toutiao comments and replies

`{"operation": "comments", "ids": ["7694967967913607743"], "maxItemsPerInput": 50}` → top comments with author, likes, reply count, IP location (50 per request, ~2 s). `repliesPerComment: 3` adds replies; `operation: "replies"` with `commentId` values pages through all replies. Nested comments on article rows: `include: ["comments"]` + `commentsPerItem`.

### Toutiao user profile, followers, user articles and micro-posts

`{"operation": "userProfile", "users": ["1755235951852024"]}` → name, bio, verification, media account, area, IP location and **followerCount** (read from the author's newest article/post; the profile API itself encrypts its counters). `users` also accepts `https://www.toutiao.com/c/user/<id>/` and **`/c/user/token/<token>/`** share links (resolved to the numeric id automatically). `{"users": ["1755235951852024"], "maxItems": 10, "sortBy": "newest"}` → that author's latest articles with reads (头条号 media accounts).

### Which Toutiao data do you need? (single-purpose jobs, one actor)

| job | input |
|---|---|
| Toutiao hot board / hot search tracker | `{"operation":"hotBoard","trackMetrics":true,"deltaKey":"hot"}` (hourly schedule) |
| Toutiao keyword search | `{"keywords":["比亚迪"],"maxItems":50}` |
| Toutiao article text scraper | `{"urls":["https://www.toutiao.com/article/<id>/"]}` |
| Toutiao comments scraper (+ replies) | `{"operation":"comments","ids":["<id>"],"maxItemsPerInput":100,"repliesPerComment":5}` |
| Toutiao user / author feed | `{"users":["<user id or profile URL>"],"profileTab":"all","maxItems":20}` |
| Topic research with comments + sentiment | `{"operation":"keywordResearch","keywords":["<topic>"]}` |

### Pricing

$0.004 per topic/article/profile row (full text, images and nested author profile included), $0.0015 per comment or reply, $0.00005 per run start. Error, summary, plan (`dryRun`) and `capabilities` rows are free. Set `maxItems` and `maxCostUsd` to cap spend; the run stops before exceeding them.

### Example output rows

```json
{"type": "hotTopic", "operation": "hotBoard", "input": null, "queryUsed": null, "id": "7695707626033954852", "url": "https://www.toutiao.com/trending/7695707626033954852/", "fetchedAt": "2026-10-12T17:35:37.084Z", "rank": 1, "title": "评论员：日本是铁了心要介入台海", "hotValue": 44939912, "label": null, "searchQuery": "评论员：日本是铁了心要介入台海", "categories": ["military"], "isPinned": false}
```

```json
{"type": "article", "operation": "search", "input": "BYD", "queryUsed": "比亚迪", "id": "7695781314229781035", "url": "https://www.toutiao.com/article/7695781314229781035/", "fetchedAt": "2026-10-12T17:35:54.719Z", "rank": 1, "title": "外媒实测两周攻破比亚迪Shark6皮卡，厂方承认软件缺陷", "abstract": "澳大利亚媒体9月播出的一则实测，记录下这样一幕：记者驾驶比亚迪Shark 6行驶在堪培拉郊外公路上，坐在路边的网络安全研…", "contentType": "article", "source": "皮卡车市.PiKM", "authorName": "皮卡车市.PiKM", "authorId": "62005434531", "publishedAt": "2026-10-12T14:02:18.000Z", "readCount": 16, "likeCount": 0, "commentCount": 0, "shareCount": null, "coverImageUrl": null}
```

```json
{"type": "comment", "operation": "comments", "input": "7694967967913607743", "queryUsed": "7694967967913607743", "id": "7695356474414957370", "url": "https://www.toutiao.com/article/7694967967913607743/", "fetchedAt": "2026-10-12T17:35:52.625Z", "rank": 1, "commentId": "7695356474414957370", "groupId": "7694967967913607743", "parentCommentId": null, "text": "我胃癌晚期，治疗意义不大，医生说还有八个月的生存期，现在已过了十八个月，自我感觉良好，不知道还能生存多久？", "authorName": "忆江南ooo", "authorId": "53304838182", "likeCount": 25, "replyCount": 34, "publishedAt": "2026-10-11T10:33:25.000Z", "ipLocation": "浙江"}
```

```json
{"type": "summary", "operation": "auto", "operationsRun": ["search"], "inputs": 1, "rowsByType": {"article": 5}, "billableRows": 5, "billedEvents": {"result": 5, "comment": 0}, "costUsd": 0.02, "stopReason": "maxItems (5) reached", "warnings": [], "nextSteps": ["For full text: include [\"fullText\"] or operation \"articleDetails\" with ids = id values. For reactions: operation \"comments\" with the same ids."]}
```

(Real rows from test runs on 2026-10-12; long texts cut with … here only.)

### Speed and limits

- v1.3 (measured on Apify, 256 MB, same inputs as v1.2.5): channel feed 20 rows 8.3 → 3–4 s (all three pools in one parallel round), related 50 rows 9.7 → 6.1 s (pages in parallel), search 50 rows 5.2 → ~4.1–4.7 s; Hot Board, details, comments ~1.5–2 s; peak memory 20–37 MB. Upstream round trips from Apify to Toutiao are 1–2.5 s each (sometimes 4–5 s) – that, not the actor, is the floor.
- v1.3 removed limits: `/c/user/token/…` profile links now resolve; profiles return `followerCount`; `include: ["video"]` returns playable MP4 URLs; search goes deeper (all tab paged to its end: ~99 instead of 83 rows for 人工智能, 160 for 新能源汽车).
- v1.2: one pre-bundled file, a third channel pool (~50 unique rows per channel), lean output (−41 to −87 % tokens per row), two data fixes from the field audit (rounded 19-digit ids; dead image links dropped).
- Light by design: no browser, no Apify SDK, one HTTP dependency; default run memory 256 MB.
- Keyword search uses so.toutiao.com; Toutiao sometimes answers with a slide captcha or an empty page, the actor retries with a fresh session/IP automatically.
- Channel-feed rows from the PC pool carry no working image URL (Toutiao only sends unsigned links there); `include: ["images"]` opens just those rows to add a signed image.
- Not available without login: keyword **video** search (Toutiao search returns articles; get videos via feed / userArticles / related with `contentType: "video"`), following / like / publish counts of profiles (encrypted by Toutiao), more than ~50 unique rows per channel per run, Q\&A (wukong.com is closed). Video URLs expire about an hour after the run.

### Use via API

```bash
curl -X POST "https://api.apify.com/v2/acts/deadc0dex~toutiao-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H 'Content-Type: application/json' -d '{"keywords":["比亚迪"],"maxItems":5}'
```

Python: `ApifyClient(token).actor("deadc0dex/toutiao-scraper").call(run_input={"keywords": ["BYD"]})`.

### Backward compatibility (v0.x inputs)

`mode` (hotBoard, search, feed, articleDetails), `searchKeywords`, `articleUrls`, `feedChannels`, `maxItemsPerQuery` and `fetchDetails: true` still work (mapped to `operation`, `keywords`, `urls`, `channels`, `maxItemsPerInput`, `include: ["fullText"]`). `outputMode: "full"` adds the old output names `mode`, `query`, `groupId`, `scrapedAt`.

### Is it legal to scrape Toutiao?

The actor reads only public pages and public JSON endpoints that any visitor can open, without login. Personal data (commenter names, author bios) may be protected by laws such as GDPR or China's PIPL: collect only what you need and have a legitimate purpose.

### FAQ

**Is Toutiao the same as ByteDance news?** Toutiao (今日头条, Jinri Toutiao) is ByteDance's Chinese news and information app; this actor reads its public web data. It does not cover Douyin.
**Do I need a Toutiao account or cookies?** No.
**Can I search in English?** Yes: known English names are translated (see `queryUsed`); unknown words stay as typed. Chinese keywords give the best recall.
**How do I monitor the Hot Board or a channel for new items only?** Schedule the actor with `onlyNew: true` and a `memoryName` per monitor; each run returns and bills only rows not seen in earlier runs.
**How do I keep costs predictable?** Set `maxItems` and `maxCostUsd`, or run `dryRun: true` first. Filters are applied before billing.
**Is the sentiment AI-generated?** No. The insights row counts words from a fixed lexicon (with simple negation) and n-grams; it is labelled `heuristic` and makes no claims beyond those counts. No LLM is called and nothing is translated.
**Does `matchTerms` make runs cheaper?** It lowers the charge (only matching comments are billed) but not the number of requests: Toutiao has no server-side comment search, so pages are fetched and then filtered.
**Spellings:** people also search for this as toutao scraper, toutiao scrapper, tou tiao, jinritoutiao or 头条 爬虫 – it is the same Toutiao (今日头条) data.
**Keywords:** Toutiao scraper, Toutiao API, Jinri Toutiao, 今日头条, 头条热榜, 头条搜索, 头条评论, 头条号, China news API, Chinese news scraper, ByteDance news.

# Changelog

This Actor's version history is a separate document: https://apify.com/deadc0dex/toutiao-scraper/changelog.md

# Actor input Schema

## `operation` (type: `string`):

auto (default): urls/ids -> articleDetails, users -> userArticles, keywords -> search, channels -> feed, nothing -> hotBoard. hotBoard = 头条热榜 top 50. search = keyword news. keywordResearch = keyword -> top articles + full text + comments + free insights row. feed = channel news. articleDetails = full text. comments / replies. userProfile / userArticles. related. capabilities = free manifest. Use these exact names (Apify rejects other values before the run starts).

## `keywords` (type: `array`):

Keywords for operation search (头条搜索), one per item. Chinese works best; English names are auto-translated with expandQuery (BYD -> 比亚迪, Xiaomi -> 小米). Each row shows queryUsed. A Toutiao URL placed here is treated as a URL. Example: \["比亚迪", "artificial intelligence"].

## `urls` (type: `array`):

Toutiao article/video/微头条 URLs for articleDetails, comments or related (toutiao.com/article/…, /group/…, /w/…, /video/…, m.toutiao.com/i…/), or author profile URLs (toutiao.com/c/user/<digits>/) for userProfile/userArticles. Invalid ones return an INVALID_URL error row (free). Example: \["https://www.toutiao.com/article/7694967967913607743/"].

## `ids` (type: `array`):

Numeric ids: article ids (the id field of article rows) for articleDetails/comments/related, or comment ids (commentId of comment rows) for operation replies. Example: \["7694967967913607743"].

## `users` (type: `array`):

Toutiao author ids (authorId of any article row), https://www.toutiao.com/c/user/<digits>/ or /c/user/token/<token>/ profile URLs (resolved automatically), for userProfile (incl. follower count) or userArticles. Example: \["1755235951852024"].

## `channels` (type: `array`):

Channel ids for operation feed (latest articles): news_hot=热点, news_tech=科技, news_finance=财经, news_world=国际, news_entertainment=娱乐, news_sports=体育, news_military=军事, **all**=推荐, news_game=游戏, news_health=健康, news_edu=教育, news_house=房产, news_history=历史, news_culture=文化, news_travel=旅游, news_food=美食, news_fashion=时尚, news_baby=育儿, news_regimen=养生, news_essay=美文, news_story=故事, news_agriculture=三农, news_astrology=星座, news_comic=动漫. Names like tech/科技 also work. Example: \["news_tech"].

## `maxItems` (type: `integer`):

Maximum billable rows for the whole run (articles, topics, profiles, comments, replies all count). Default 20; keywordResearch default = keywords x maxArticles x (1 + maxCommentsPerArticle). Range 1-10000. Free rows (error, summary, plan, insights) never count.

## `maxItemsPerInput` (type: `integer`):

Cap per keyword, channel, article (comments) or user. Default: maxItems split evenly between inputs. Max 1000.

## `maxArticles` (type: `integer`):

keywordResearch: how many top articles per keyword (after filters and sortBy) are opened for full text and comments. Default 10, range 1-100 (larger values are capped).

## `maxCommentsPerArticle` (type: `integer`):

keywordResearch: comments returned per article (Toutiao hot order), billed per comment. Default 20, range 0-500; 0 = articles only. Replies: repliesPerComment. Articles with fewer comments return what exists.

## `include` (type: `array`):

Extra data per article row. fullText = open each article: full text, images, exact time, all counts (same price). images = all image URLs. video = MP4 URL (expires ~1 h). author = nested profile (free). comments = nested top comments (billed per comment). insights = free heuristic analysis row. metrics / trust = add engagement metrics / trust fields in lean mode. Example: \["fullText"].

## `fields` (type: `array`):

Projection: keep only these output fields per data row (type is always kept). Empty = the default set of the outputMode. Names: id, url, title, abstract, text, source, authorName, authorId, publishedAt, readCount, likeCount, commentCount, shareCount, videoPlayCount, hotValue, searchQuery, commentId, replyCount, name, … (full list: operation capabilities). Example: \["title","url","publishedAt","readCount"].

## `outputMode` (type: `string`):

compact (default) = flat core fields, null when unknown. lean = token-efficient for LLM agents: only type, id and the most useful fields, no null/empty/false keys, operation/input/fetchedAt only once in the summary row, dates as 2026-10-10T10:02Z (UTC), text cut to maxTextChars (300). full = every field plus v0.x names (mode, query, groupId, scrapedAt).

## `sortBy` (type: `string`):

Order within each input. relevance = Toutiao's order (default). newest = publish time. mostRead / mostLiked / mostCommented = by count. Non-default sorting scans up to 3x more candidates first.

## `dateFrom` (type: `string`):

Keep only items published on/after this time. Formats: 2026-10-01 (Beijing time), ISO date-time, unix seconds, or relative 24h / 7d / 4w. Applied before billing. Example: "7d".

## `dateTo` (type: `string`):

Keep only items published on/before this time. Same formats as dateFrom.

## `minReads` (type: `integer`):

Keep only items with at least this many reads (readCount). Feed items without a read count need include \["fullText"]. Applied before billing.

## `maxCostUsd` (type: `number`):

Stop before the run's charges would exceed this amount (e.g. 0.05). The run then ends SUCCEEDED with the rows so far and a COST_LIMIT note. Works together with Apify's max cost per run.

## `dryRun` (type: `boolean`):

true = fetch nothing and bill nothing: returns one free plan row per input (detected operation, queryUsed, estimated rows, cost and seconds) plus a summary. Use it to check an input before paying.

## `matchTerms` (type: `array`):

Keep only comments/replies whose text contains any of these words (case-insensitive); insights counts them per word. Applied after each page is fetched: lowers charges, not requests. Works for keywordResearch, comments, replies. Filter articles with textContains. Example: \["电池","续航"].

## `expandQuery` (type: `boolean`):

Latin-script keywords are mapped to Chinese with a curated dictionary of ~950 brands, companies, people, products and news topics (BYD -> 比亚迪, Elon Musk -> 马斯克, Federal Reserve -> 美联储); unknown words stay as typed. Rows show queryUsed. false = search exactly as typed.

## `trackMetrics` (type: `boolean`):

true = compare with the previous run that used the same deltaKey: rows get snapshotAt, previousSnapshotAt, isNewSinceLastSnapshot and deltas (readsDelta, likesDelta, commentsDelta, sharesDelta, engagementRateDelta; hot topics hotValueDelta, rankDelta). Compact metrics only, kept 30 days in your account.

## `deltaKey` (type: `string`):

Runs with the same deltaKey share one metrics snapshot (trackMetrics). One key per tracker, e.g. "hot-board" or "byd-news". Letters, digits, dashes. Default "default".

## `insights` (type: `boolean`):

true = add one free type=insights row: top terms, lexicon sentiment (labelled heuristic), engagement medians, top articles/comments, sources, commenter IP regions. Default: on for keywordResearch, off otherwise (false turns it off). No LLM.

## `repliesPerComment` (type: `integer`):

Operations comments and keywordResearch: also return up to this many replies per comment as type=reply rows (billed as comment events). Default 0, max 100.

## `minLikes` (type: `integer`):

Keep only items/comments with at least this many likes (likeCount). Applied before billing.

## `minComments` (type: `integer`):

Keep only items with at least this many comments (commentCount; for comments: replyCount). Applied before billing.

## `textContains` (type: `array`):

Keep only rows whose title, abstract or text contains at least one of these words (case-insensitive), checked before billing. Works for search, feed, userArticles, related, comments (comment text) and hotBoard (title). Example: \["比亚迪","BYD"].

## `commentsPerItem` (type: `integer`):

With include \["comments"]: top comments nested in each article row. Default 5, max 100. Each comment is billed as a comment event.

## `leanFormat` (type: `string`):

Only with outputMode lean. rows (default) = normal dataset rows. table = also write key-value record LEAN_TABLE: one tab-separated table per row type with the header once (fewest tokens), copied into the summary row when under 20 KB. Rows are still pushed and billed the same; the table is a free extra.

## `maxTextChars` (type: `integer`):

Cut text, abstract, description and comment text to this many characters (ends with …, row gets truncated: true). Default 300 in lean mode, no cut in compact/full. 0 = never cut; range 0–100000. Example: 1000.

## `contentType` (type: `string`):

List operations: keep only one kind. any (default), article, video, micro-post (微头条). Videos come from feed, userArticles and related; keyword search returns articles only.

## `profileTab` (type: `string`):

Which part of an author's page userArticles reads. articles (default) = 头条号 articles, newest first, reads as rounded numbers. posts = micro-posts (微头条) with exact time, reads, likes, comments. all = both merged.

## `searchType` (type: `string`):

search only. news (default) = News (资讯) tab, continuing in the All tab when it runs out. all = All (综合) tab only.

## `includePinned` (type: `boolean`):

hotBoard only: also return the pinned official item above the ranked list (rank 0).

## `includeHtml` (type: `boolean`):

articleDetails / include fullText: add raw article HTML as contentHtml.

## `dedupe` (type: `boolean`):

Drop rows already returned earlier in the run (same type + id), e.g. when two keywords find the same article.

## `onlyNew` (type: `boolean`):

true = skip (and do not bill) rows already returned by earlier runs that used the same memoryName, e.g. a scheduled Hot Board or feed run every hour returns only new topics/articles. Seen ids are kept 30 days in named key-value store toutiao-seen-<memoryName> in your account.

## `memoryName` (type: `string`):

Runs with the same memoryName share one memory of seen rows (onlyNew). Use one name per monitor, e.g. "tech-feed" or "hot-board". Letters, digits and dashes.

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

Apify Proxy (default, recommended): sessions rotate automatically on rate limits or search captchas.

## `maxRequestRetries` (type: `integer`):

Retries with a fresh session/IP on rate limits, captchas or server errors (0-10).

## `requestDelayMs` (type: `integer`):

Minimum delay between request starts to the same Toutiao host, to stay polite (0-10000). Default 100.

## Actor input object example

```json
{
  "operation": "hotBoard",
  "outputMode": "compact",
  "sortBy": "relevance",
  "dryRun": false,
  "expandQuery": true,
  "trackMetrics": false,
  "repliesPerComment": 0,
  "commentsPerItem": 5,
  "leanFormat": "rows",
  "contentType": "any",
  "profileTab": "articles",
  "searchType": "news",
  "includePinned": false,
  "includeHtml": false,
  "dedupe": true,
  "onlyNew": false,
  "memoryName": "default",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxRequestRetries": 4,
  "requestDelayMs": 100
}
```

# Actor output Schema

## `results` (type: `string`):

All dataset rows. Filter by type; the last row is type summary.

## `articles` (type: `string`):

Article rows as a table (articles view).

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

Comment and reply rows.

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

RUN_SUMMARY record: rowsByType, billedEvents, costUsd, stopReason, warnings, notes, nextSteps.

## `leanTable` (type: `string`):

Only with outputMode lean + leanFormat table: all rows as one tab-separated table per row type (header once). Fewest tokens for LLMs.

# 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 = {
    "operation": "hotBoard",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("deadc0dex/toutiao-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 = {
    "operation": "hotBoard",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("deadc0dex/toutiao-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 '{
  "operation": "hotBoard",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call deadc0dex/toutiao-scraper --silent --output-dataset

```

## MCP server setup

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