# Weibo Scraper (`lanternlab318/weibo-scraper`) Actor

Search Weibo posts by keyword or pass post links, signed out, with optional comments and reply threads.

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

## Pricing

$4.99 / 1,000 post or comments

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

## Weibo Scraper

Search Weibo (微博) posts by keyword, or pass post links directly, and optionally collect each
post's comments and reply threads. Everything is read the way a visitor who is not signed in
reads it: no account, no cookie of yours, nothing behind a login.

Every row says whether its collection was complete. When Weibo stops serving a visitor part of a
list, the rows say so in `partial` and `partialReason` — **an incomplete collection is never
written as "no results"**.

### Input

| Field | Default | What it does |
| --- | --- | --- |
| `keywords` | — | Words or phrases to search for, one search each. Chinese works best. |
| `postUrls` | — | Posts to collect directly: `m.weibo.cn/detail/<id>`, `weibo.com/<user id>/<code>`, or a bare post ID. |
| `maxPostsPerKeyword` | `20` | Posts to keep per keyword. |
| `includeComments` | `false` | Also collect each post's comments, one row per comment. |
| `maxCommentsPerPost` | `50` | Comment rows per post, replies included. |
| `includeReplies` | `true` | Follow reply threads. Each reply carries its `parentCommentId`. |
| `maxUpstreamRequests` | derived | Hard ceiling on requests to Weibo. Left empty, it is worked out from your other settings; a run is never unlimited. |
| `requestDelayMs` | `700` | Pause between two requests to Weibo. |

Give at least one keyword or one post link. A post two keywords both return is written once.

```json
{
    "keywords": ["咖啡"],
    "maxPostsPerKeyword": 20,
    "includeComments": true,
    "maxCommentsPerPost": 50
}
```

### Output

One row per post (`recordType: "post"`) and one per comment or reply
(`recordType: "comment"`), plus a `keyword_summary` row per keyword and a `post_not_collected`
row for any link that has no public post behind it.

A post row carries `postId`, `mid`, `bid`, `url`, the full `text` (the complete text is fetched
when Weibo cut it off), `publishedAt` (the platform's publish time), `publishedRegion`,
`author` (`uid`, `name`, `verified`, `followersCount`), `stats` (`reposts`, `comments`, `likes`),
`images`, `repostOfPostId`, the `keyword` and `rank` it was found at, `collectedAt` (when this
Actor read it) and, when comments were asked for, `commentsCollected` and
`commentsPartialReason`.

A comment row carries `commentId`, `postId`, `isReply`, `parentCommentId`, `rootCommentId`,
`text`, `likes`, `replyCount`, `floor`, `publishedAt`, `collectedAt` and `author` (`uid`, `name`,
`ipLocation`). Only commenter fields Weibo shows publicly on the page are kept. A field Weibo did
not give is `null` — never a filled-in guess.

Weibo's two comment lists describe a reply differently, and the output keeps that visible rather
than flattening it. The hot list returns top-level comments only; the reply list returns the
replies of one comment, so their parent is known. The numbered list returns both mixed together
and never says what a reply answers — such a row is `isReply: true` with `parentCommentId: null`
and is marked `partial` with that reason, so it is never mistaken for the start of a thread. A
comment that turns up in two lists is written once, from the copy that knows its parent.

A post row is marked `partial` when its own text was cut off **or** when its comments came up
short, so counting `partial` needs no second field; `commentsPartialReason` says which part fell
short. A `keyword_summary` row is marked `partial` when the keyword did not finish, including when
the run stopped at a ceiling part-way through it.

The run's own totals are in the key-value store under `RUN_SUMMARY`: rows by type, how many were
partial, duplicates skipped, every request sent to Weibo and what it answered, the request
ceiling and where it came from, and whether the run stopped early and why.

### What a visitor can and cannot read

Measured on 2026-10-15, signed out:

- **Keyword search pages** — readable; checked to 10 pages, 98 distinct posts for one keyword.
  Weibo's search widens a query it finds little for rather than returning nothing, so a keyword
  almost always comes back with posts; read `keyword` and the post text to judge relevance.
- **Post detail and full text** — readable.
- **Comments** — Weibo serves a visitor the hot comment list and the first two numbered comment
  pages, roughly the first 35 to 40 comments of a post. Both lists are read and merged, because
  they return different comments. Past that, Weibo asks for a sign-in; this Actor stops there and
  marks the post's comments `partial` with the reason.
- **Reply threads** — readable for the comments collected above.

This is a limit of what Weibo shows the public, not an error, and it is reported as such in every
row and in `RUN_SUMMARY`.

### When something goes wrong

A refusal, a missing post and a ceiling are three different things, and the output keeps them
apart. A post Weibo has no public page for becomes a `post_not_collected` row with the reason. A
list Weibo stops serving a visitor part-way marks the rows `partial` with the reason. Reaching
`maxCommentsPerPost`, `maxPostsPerKeyword` or `maxUpstreamRequests` is named as the limit it was,
and whether `maxUpstreamRequests` came from the input or from this Actor's own default is in
`RUN_SUMMARY.upstreamCapSource`. Nothing is written as "no results" because collection was cut
short.

### Local checks

```sh
npm install
npm test
```

No token and no network: the tests drive the HTTP client with their own fetch.

# Actor input Schema

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

Search Weibo for these words or phrases, one search per line. Chinese works best. Leave empty if you only want the post links below.

## `postUrls` (type: `array`):

Weibo posts to collect directly: m.weibo.cn/detail/<id>, weibo.com/<user id>/<short code>, or a bare post ID.

## `maxPostsPerKeyword` (type: `integer`):

How many posts to keep for each keyword. Weibo serves about 9 to 20 posts per search page to a visitor who is not signed in.

## `includeComments` (type: `boolean`):

Collect the comments of every post as well, each as its own row. This multiplies the number of rows, so it is off by default.

## `maxCommentsPerPost` (type: `integer`):

How many comment rows to keep per post, replies included. Signed out, Weibo serves roughly the first 35 to 40 comments of a post; what is missing is marked on the rows, never reported as zero comments.

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

Follow reply threads under the comments. Each reply row carries the comment it replies to in "parentCommentId". Replies count towards the comments-per-post limit.

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

Hard ceiling on the requests one run may send to Weibo, retries included. Leave empty and this Actor works one out from your keywords, links and per-post limits; a run is never unlimited.

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

Milliseconds to wait between two requests to Weibo. Lower is faster; higher is gentler on the site.

## Actor input object example

```json
{
  "keywords": [],
  "postUrls": [],
  "maxPostsPerKeyword": 20,
  "includeComments": false,
  "maxCommentsPerPost": 50,
  "includeReplies": true,
  "requestDelayMs": 700
}
```

# 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 = {
    "keywords": [],
    "postUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/weibo-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 = {
    "keywords": [],
    "postUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/weibo-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 '{
  "keywords": [],
  "postUrls": []
}' |
apify call lanternlab318/weibo-scraper --silent --output-dataset

```

## MCP server setup

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