# Bilibili 哔哩哔哩 B站 Scraper: All Comments per Video, No Login (`deepurple/bilibili-scraper`) Actor

All comments of any Bilibili (B站, 哔哩哔哩) video: up to 100,000 per video with replies, a sentiment score per comment, daily tracking of only new comments. Plus videos from search, channels or Popular with stats and creator profiles. No login. $6.40 per 1,000 comments.

- **URL**: https://apify.com/deepurple/bilibili-scraper.md
- **Developed by:** [Misha Ershov](https://apify.com/deepurple) (community)
- **Categories:** Social media, Videos, For creators
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 videos

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

## Bilibili Scraper: All Comments per Video, No Login

Bilibili videos from keyword search, a creator's channel or the Popular feed with their stats (views, likes, coins,
favorites, shares, danmaku, comments), comments with replies of a video (20 per page, up to every root comment of a video, no login) and creator profiles. One row per item, with an
**onlyNew** mode that returns only videos and users you have not received in an earlier run. No login, no cookies.

### What you can do

- Collect all comments of a video, up to 100,000 per video (`maxCommentsPerVideo`), with replies.
- Sort them with `commentsOrder`: as Bilibili returns them, most liked first or most replied first.
- Watch progress and time left in the run status ("N/M comments, ~X min left").
- Get a sentiment (positive, negative, neutral) and a score from -1 to 1 for every comment and reply (`analyzeSentiment`).
- Compare videos in one table with a summary row per video (`videoSummary`).
- Scrape several videos in one run (`videoUrls`).
- Track new comments: each scheduled run returns and bills only the comments posted since the previous one (`onlyNewComments`).

### Input

| Field | Default | Meaning |
|---|---|---|
| `mode` | `search` | `search` videos by keyword, `popular` feed, `videos` given BV ids or URLs, `users` creators by keyword or id, `creatorVideos` all videos of the given creators |
| `keywords` | `[]` | search words. Mode `search`: video search. Mode `users`: creator search |
| `searchOrder` | `totalrank` | `totalrank`, `click`, `pubdate`, `dm`, `stow`. Mode `creatorVideos` accepts `pubdate`, `click`, `stow` and falls back to `pubdate` |
| `durationFilter` | `all` | `all`, `under10`, `10to30`, `30to60`, `over60` (minutes) |
| `videoUrls` | `[]` | mode `videos`: BV ids or any URL containing one. Entries without a BV id are skipped and logged |
| `userIds` | `[]` | modes `users` and `creatorVideos`: user ids or `space.bilibili.com/<id>` URLs |
| `maxResults` | `100` | 1 to 1000, per keyword (search, users) or in total (popular) |
| `fetchDetails` | `false` | search and popular: open every video page for coins, shares, tags and uploader followers |
| `includeComments` | `false` | mode `videos` (or search/popular/creatorVideos with `fetchDetails`): add up to `maxCommentsPerVideo` root comments per video, 20 per page, no login. The pinned comment comes first. In search, popular and creatorVideos modes also turn on fetchDetails. |
| `maxCommentsPerVideo` | `20` | with `includeComments`: root comments per video (1-100000); the pinned comment counts toward the limit. 20 = one page = one request per video |
| `commentsSort` | `popular` | with `includeComments`: `popular` (most liked first) or `newest` (latest first) |
| `commentsOrder` | `api` | with `includeComments`: `api` (as Bilibili returns them, see `commentsSort`), `likes` or `replies`. The last two first collect all root comments of a video, then output them sorted, with a `commentRank` place number |
| `analyzeSentiment` | `false` | with `includeComments`: adds `sentiment` and `sentimentScore` to every comment and reply. See [Sentiment](#sentiment) |
| `videoSummary` | `false` | with `includeComments`: one extra row per video with type `videoSummary` (counts, likes, top comment, sentiment shares, time span). Not charged |
| `parallelVideos` | `3` | how many videos are collected at the same time (1-10); each video still loads its pages one by one with a pause. 1 = one video after another |
| `onlyNew` | `false` | skip videos and users from earlier runs with the same mode, keywords, order, length and ids |
| `onlyNewComments` | `false` | with `includeComments`: return only comments posted since the previous run with this option, per video. The first run collects as usual; comments are read newest first. See [Track new comments](#track-new-comments) |
| `maxRepliesPerComment` | `0` | with `includeComments`: also fetch up to this many replies (0-500) under each top comment; full reply threads, no login needed. Each reply is a row with `type: "reply"` and `parentCommentId`, billed as a `comment` event (same price as a comment). 0 = off |
| `proxyConfiguration` | Apify datacenter | for search, popular, comments and users. Video pages always use Apify residential proxy |

Mode `search` needs `keywords`, mode `videos` needs `videoUrls`, mode `users` needs `keywords` or `userIds`, mode `creatorVideos` needs `userIds`.
Otherwise the run ends with exit code 1 and a clear status message, nothing is charged.

Example input for all videos of one creator, newest first:

```json
{"mode":"creatorVideos","userIds":["546195"],"maxResults":60}
```

### Output

Every row has a `type`: `video`, `comment`, `reply` or `user`. Fields Bilibili does not return are `null`, never `0`.
Search results have no coins and shares: they are `null` unless `fetchDetails` is on.

Video row:

```json
{"type": "video", "bvid": "BV1rpWjevEip", "aid": 113006243481679, "url": "https://www.bilibili.com/video/BV1rpWjevEip",
 "title": "【全748集】目前B站最全最细的Python零基础全套教程", "author": "Python官方课程", "authorId": 3546597933714079,
 "authorUrl": "https://space.bilibili.com/3546597933714079", "authorFollowers": 801387, "category": null,
 "tags": ["编程语言", "程序员"], "durationSec": 143894, "publishedAt": "2024-08-22T14:59:18Z",
 "views": 20709271, "likes": 570226, "coins": 406862, "favorites": 876897, "shares": 102796, "danmaku": 132790,
 "comments": 359670, "coverUrl": "http://i2.hdslb.com/bfs/archive/a979056b1a32012cdd00d48fbc3732d253e30620.jpg",
 "source": "video", "keyword": null, "rank": 1, "scrapedAt": "2026-10-13T09:00:00Z"}
```

Comment row:

```json
{"type": "comment", "bvid": "BV1rpWjevEip", "commentId": 247714016448, "authorName": "爱劳动的noise",
 "authorId": 1242205079, "authorLevel": 4, "text": "...", "likes": 7508, "replies": 1148,
 "postedAt": "2024-11-21T13:52:41Z", "videoTotalComments": 359670, "pinned": false}
```

User row:

```json
{"type": "user", "userId": 546195, "name": "老番茄", "url": "https://space.bilibili.com/546195",
 "bio": "新浪微博：_老番茄_", "avatarUrl": "http://i0.hdslb.com/bfs/face/bc5ca101313d4db223c395d64779e76eb3482d60.jpg",
 "level": 6, "followers": 20840932, "videoCount": 681, "likesTotal": 196027733, "verified": true,
 "verifiedTitle": "2025百大UP主、2025年度商业影响力奖UP主、2022年度联合创作奖UP主", "isLive": null, "keyword": null}
```

`pinned` is `true` for the comment the uploader pinned to the top of the video and `false` for all others; the pinned comment comes first.
`title` has Bilibili's highlight tags removed and HTML entities decoded. `publishedAt` and `postedAt` are UTC.
`rank` is the 1-based position in the source list. `durationSec` is in seconds.

### Compare videos

Put several videos in one run and switch on `videoSummary` (and `analyzeSentiment` for the sentiment shares):

```json
{"mode":"videos","videoUrls":["BV1rpWjevEip","BV1GJ411x7h7","BV1xx411c7mD"],"includeComments":true,
 "maxCommentsPerVideo":500,"videoSummary":true,"analyzeSentiment":true}
```

Each video gets one extra row with `type: "videoSummary"` after its comments: `videoTitle`, `videoTotalComments`,
`commentsCollected`, `repliesCollected`, `avgLikes`, `medianLikes`, `topCommentText`, `topCommentLikes`,
`mostRepliedCommentText`, `mostRepliedCommentReplies`, `positiveShare`, `negativeShare`, `neutralShare`,
`firstCommentAt`, `lastCommentAt`, `collectSeconds`. The shares are `null` when sentiment is off. A video with no
comments still gets its row with `commentsCollected: 0`. To see all videos side by side, open the dataset in Console and
choose the **Video summary** view (`videoSummary`).

### Track new comments

Switch on `onlyNewComments` and run the Actor on a schedule (for example once a day). The first run collects as usual
(up to `maxCommentsPerVideo`, newest first) and remembers where it stopped for every video. Each next run reads the comments
newest first, stops as soon as it reaches a comment it has already seen, and returns and bills only the new ones.
Replies, sentiment and `commentsOrder` work as usual on the new comments.

```json
{"mode":"videos","videoUrls":["BV1rpWjevEip","BV1GJ411x7h7","BV1xx411c7mD"],"includeComments":true,
 "onlyNewComments":true,"maxCommentsPerVideo":500,"videoSummary":true}
```

- The record is kept per video, under the key `comments-<BV id>` in the key-value store `bilibili-scraper-state` on your account. It does not depend on the mode or the keywords, so you can change the list of videos and every video keeps its own place.
- With `videoSummary`, the summary row has `newSince` (time of the newest comment of the previous run, `null` on the first run) and `newComments`.
- If more than `maxCommentsPerVideo` new comments appeared since the last run, the newest ones are returned and a warning is logged; raise the limit for busy videos.
- If the run stops at your charge limit in the middle of a video, that video's record is not moved, so the unread new comments come in the next run.
- To start over for a video, delete the key `comments-<BV id>` in the `bilibili-scraper-state` store.
- Do not switch on `onlyNew` for comment monitoring: it skips videos you have already received, and so their new comments.
- `RUN_SUMMARY` has `onlyNewComments`, `videosFirstRun`, `videosWithNew` and `videosNoNew`.

### Export to Excel, CSV or Google Sheets

- **Console:** open the run, go to the Output or Storage tab and use Export to download the dataset as Excel, CSV, JSON or XML.
- **Google Drive:** in the Integrations tab of the Actor add "Upload results to GDrive" and pick the file format; see https://docs.apify.com/integrations/drive.
- **Google Sheets:** connect the Actor through Make or Zapier and use the "Bulk Add Rows" action.

### How long does it take

Requests are paced (0.7 to 1.0 s each, to stay clear of bans), measured on Apify:

- Without replies: about 7 to 9 root comments per second. 5,000 comments take roughly 10 minutes.
- Up to 3 replies per comment (`maxRepliesPerComment` 1-3): almost free in time when Bilibili's comment page already carries
  those replies (it usually sends the first 2 to 3 "hot" replies of a thread). A comment whose page preview is shorter than
  what you asked for costs one extra request.
- More than 3 replies per comment: about 1 second for every comment that has replies. Example: 200 comments, all with replies,
  10 replies each = 200 extra requests at about 0.85 s = about 3 minutes on top of roughly 30 seconds for the comments, so about 3.5 minutes.
- Replies taken from the page are Bilibili's "hot" replies, not the oldest or newest ones; fine for a sample of the thread.
- The number of comments Bilibili shows for a video includes replies. The status therefore reads like
  "BV1...: 300 comments of at most ~336 (Bilibili's 908 includes replies), up to ~1 min left"; the time left is an upper bound.

Videos are collected 3 at a time (change it with `parallelVideos`), so one run with 5 videos takes about as long as 2 videos one after another.
Rows of different videos can be interleaved in the dataset; every row carries its `bvid`, and the order inside one video does not change.
`RUN_SUMMARY` has `parallelVideos` and `wallSeconds` (the whole run), while `commentSeconds` stays the sum over videos.
Measured on Apify: 5 videos, 200 comments each plus up to 2 replies (1,730 rows) took 206 s one video at a time,
84 s with `parallelVideos: 3` (the default) and 60 s with `parallelVideos: 5`. The price per row is the same, whatever you choose.

The default run timeout is 1 hour. If the estimate does not fit, the log says so; raise Timeout in Run options or lower `maxCommentsPerVideo`.
`RUN_SUMMARY` has `repliesFromPreview` (reply rows taken from the comment page) and `replyRequests` (separate reply requests).

### Sentiment

With `analyzeSentiment` every comment and reply gets `sentimentScore` (-1 to 1) and `sentiment`: `positive` from 0.4,
`negative` up to -0.4, `neutral` in between. Chinese text is scored offline with SnowNLP; English text with a small word list
(positive words minus negative words). Emote codes like `[doge]` are ignored; a comment with nothing but emotes gets `null`.
Treat the result as a guide, not as a verdict: SnowNLP was trained on product reviews, so slang and sarcasm are often
misjudged, and the English word list is deliberately simple.

### How the run works

Bilibili answers errors with HTTP 200 and a non-zero `code` (for example `-352`), or with HTTP 412. Any of these
counts as a failed request and is retried up to 3 times, each on a new proxy session. A keyword that still fails is
logged (`keyword X: bilibili code N`), counted in `RUN_SUMMARY.failed` and skipped; the other keywords go on.
If a video page cannot be read after 3 attempts, the row is still returned without details, billed as `video`,
and counted in `detailsFailed`. Search stops at `maxResults`, at the last page or at an empty page; videos are
deduplicated by `bvid` within a run. Requests are paced (about one per second). `RUN_SUMMARY` holds `bySource`
(pages, items and `stoppedBy` per keyword), `pushed` by type, `failed`, `detailsFailed`, `charged` per event and
`stoppedBy` (`maxResults`, `numPages`, `budget` or `end`).

### FAQ

**Do I need a Bilibili account?** No. No login or cookies are used.

**How many comments can I get?** 20 root comments per page, up to 100,000 per video (`maxCommentsPerVideo`), so every root comment of almost any video, sorted by popularity or newest, with no Bilibili login. About 9 comments per second: for thousands of comments raise the run timeout in Run options (5,000 comments take roughly 10 minutes). Replies are fetched separately with `maxRepliesPerComment`. Bilibili may end the list earlier than the total comment count shows.

**Why is `fetchDetails` slower?** Video pages are served to residential IPs only, so each video costs one residential request and its row is billed as `video-details`.

**Pricing:** one event per row: `video` (search or popular, no details), `video-details` (video page opened), `comment`, `user`. Every comment and every reply is one `comment` event; the default of 20 comments per video means up to 20 `comment` events per video, so raise `maxCommentsPerVideo` only if you need more.

Missing something? Open an issue — we add fields on request.

### Development

```
cd partner_actors/dima/bilibili-scraper
python3 -m pytest -q
```

# Actor input Schema

## `mode` (type: `string`):

search: videos for each keyword. popular: the Popular feed. videos: given videos with full stats. users: creators by keyword or user id. creatorVideos: all videos of the given creators.

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

Search words, Chinese or English, for example "python". Mode search: video search. Mode users: creator search.

## `searchOrder` (type: `string`):

Sort order of video search. For creatorVideos: pubdate, click or stow (others fall back to pubdate).

## `durationFilter` (type: `string`):

Keep only videos of this length in video search.

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

Mode videos: BV ids or any URL containing one, for example https://www.bilibili.com/video/BV1rpWjevEip. Invalid entries are skipped.

## `userIds` (type: `array`):

Modes users and creatorVideos: user ids (digits) or space.bilibili.com/<id> URLs. Mode users gives one profile each; creatorVideos gives all videos of each creator.

## `maxResults` (type: `integer`):

Per keyword (search, users) or in total (popular). 1 to 1000.

## `fetchDetails` (type: `boolean`):

Search and popular: open each video page for coins, shares, tags and uploader followers. One residential request per video; the row is billed as video-details.

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

Mode videos, or search/popular/creatorVideos with full video stats: up to maxCommentsPerVideo root comments per video, 20 per page, no login. In search, popular and creatorVideos modes also turn on fetchDetails.

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

With Include comments on: maximum number of root comments per video, 20 per page. The pinned comment counts toward the limit. Every comment is billed as a comment event.

## `commentsSort` (type: `string`):

With Include comments on: popular = most liked first, newest = latest first.

## `commentsOrder` (type: `string`):

With Include comments on: likes or replies first collect all root comments of a video (up to Comments per video), then output them sorted, with a commentRank place number. Replies follow right after their root comment, as before.

## `analyzeSentiment` (type: `boolean`):

With Include comments on: adds sentiment (positive, negative, neutral) and sentimentScore (-1 to 1) to every comment and reply. SnowNLP for Chinese text, a word list for English. An estimate, not a guarantee of accuracy.

## `videoSummary` (type: `boolean`):

With Include comments on: one extra summary row per video (comment counts, likes, top comment, sentiment shares, time span). Handy for comparing videos in one table. Free: not charged.

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

With Include comments on: also fetch up to this many replies under each root comment (full reply threads, no login needed). Each reply is billed as a comment. 0 = off.

## `parallelVideos` (type: `integer`):

How many videos to collect at the same time (comments and details). Each video still loads its pages one by one with a pause. 1 = one video after another.

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

Skip videos and users returned by earlier runs with the same mode, keywords, order, length and ids.

## `onlyNewComments` (type: `boolean`):

With includeComments: return only comments posted since the previous run with this option, per video. The first run collects as usual and remembers where it stopped. Comments are read newest first. Run it on a schedule for daily monitoring.

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

Used for search, popular, comments and user requests (Apify datacenter proxy is enough). Video pages always use Apify residential proxy.

## Actor input object example

```json
{
  "mode": "search",
  "keywords": [
    "python"
  ],
  "searchOrder": "totalrank",
  "durationFilter": "all",
  "videoUrls": [],
  "userIds": [],
  "maxResults": 40,
  "fetchDetails": false,
  "includeComments": false,
  "maxCommentsPerVideo": 20,
  "commentsSort": "popular",
  "commentsOrder": "api",
  "analyzeSentiment": false,
  "videoSummary": false,
  "maxRepliesPerComment": 0,
  "parallelVideos": 3,
  "onlyNew": false,
  "onlyNewComments": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Bilibili rows: videos (views, likes, coins, favorites, shares, tags), comments and creator profiles. The type field tells which.

# 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": [
        "python"
    ],
    "maxResults": 40,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("deepurple/bilibili-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": ["python"],
    "maxResults": 40,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("deepurple/bilibili-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": [
    "python"
  ],
  "maxResults": 40,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call deepurple/bilibili-scraper --silent --output-dataset

```

## MCP server setup

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