# Bilibili Danmaku Scraper (`automation-lab/bilibili-timed-danmaku-samples`) Actor

Export public Bilibili timed danmaku text samples and observed-pool per-minute reaction profiles from BV IDs or video URLs. No login or full-history claim.

- **URL**: https://apify.com/automation-lab/bilibili-timed-danmaku-samples.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Videos, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.30 / 1,000 item extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Danmaku Scraper

Measure timed audience reactions with **bilibili danmaku** text samples and observed-pool profiles. Supply public BV IDs or video URLs and get one structured record per video part: timed texts, minute counts, peak minute, pool markers and aggregate unique-sender counts. No login cookies are needed. This is a snapshot of Bilibili's public XML pool, **not a complete comment archive**.

### Who is it for?

Video researchers can locate moments with concentrated audience reactions. Editors can inspect sampled texts near a reaction peak. China-market analysts can compare public reaction pools across supplied videos. Data teams can join snapshots to video metadata by BV ID and content ID.

### Why use this Actor?

You receive a compact video-part profile instead of thousands of independently billed comment rows. Metrics use the whole observed pool even when you request few text samples. Sender hashes are counted only in memory and never exported. The output distinguishes lifetime counters from fetched pool counts rather than presenting a sample as full coverage.

### Getting started

1. Choose a public Bilibili video and copy its BV ID or canonical HTTPS video URL.
2. Enter it in `videos`. Add `?p=2` to a URL to select part 2 of a multipart video.
3. Choose `sampleSize` (0–500) and `maxItems` (1–100).
4. Run the Actor and inspect the default dataset.
5. Export JSON for nested samples or flatten the arrays in your downstream pipeline.

Example input:

```json
{
  "videos": ["BV1xx411c7mD"],
  "maxItems": 10,
  "sampleSize": 100
}
```

### Inputs

| Field | Default | Behavior |
|---|---|---|
| `videos` | Required | 1–100 public BV IDs or HTTPS `bilibili.com/video/BV…` URLs. IDs select part 1. URL `p` selects a part. |
| `maxItems` | 10 | Global maximum unique BV/part profiles in input order, after deduplication. Range 1–100; zero/unlimited are invalid. |
| `sampleSize` | 100 | Maximum timed texts per profile, 0–500. Zero returns metrics with an empty samples array. |

Duplicate BV/part pairs are removed before applying `maxItems`. There is no keyword search, discovery, short-link resolution, av-ID input, cookie input or hidden filter. Unsupported fields and malformed values fail validation. Only supplied video parts are collected; a multipart video is not automatically expanded.

### Bilibili danmaku sampling and coverage

The Actor resolves the selected part's content ID using public metadata, then reads its public XML pool. Entries are sorted by playback position. Text samples are selected at evenly spread **indices** in that sorted sequence, including both endpoints when at least two samples are requested. This is not random sampling or uniform spacing in seconds.

Minute buckets, sender count and playback bounds use every observed entry, independently of `sampleSize`. Missing minute buckets mean zero comments in this observed pool. The highest-count minute wins; ties choose the earliest minute. The overall rate divides observed count by the selected part's reported duration in minutes, not by the sampled time span.

`poolLimit` is the source's `maxlimit` marker. Actual fetched entries can exceed it. `poolCapped` is true when fetched count reaches that marker, or is below a known single-part lifetime total. A false marker does **not** prove full history. A zero pool can be legitimate and still produces a video-part profile.

`totalDanmaku` is the lifetime video counter only for single-part videos. It is null for multipart videos, because a video-wide count cannot honestly be assigned to one selected part. Sender counts are distinct source hashes in the observed pool, not verified unique people.

### Extracted data

| Field | Meaning |
|---|---|
| `bvid`, `videoUrl`, `page`, `cid` | Video identity, selected part and public content ID |
| `title`, `partTitle` | Video title and optional part label |
| `danmakuFetched` | Number of entries in the observed public XML pool |
| `uniqueSenders` | Aggregate distinct hashes in that pool; hashes themselves are excluded |
| `poolLimit`, `poolCapped` | Source pool marker and conservative coverage indicator |
| `totalDanmaku` | Single-part lifetime video counter, otherwise null |
| `perMinute` | Nonzero `{minute, count}` playback buckets |
| `peakMinute` | Highest observed bucket, or null for an empty pool |
| `firstTimeSeconds`, `lastTimeSeconds` | Observed playback bounds, null when empty |
| `durationSeconds`, `danmakuPerMinute` | Selected part duration and observed count per duration-minute |
| `samples`, `sampleCount` | Timed text samples and their count |
| `coverage`, `scrapedAt` | Coverage declaration and UTC snapshot timestamp |

Each sample contains `timeSeconds`, `text`, `mode`, `fontSize`, decimal RGB `color` and UTC `sentAt`. No sender ID, hash or profile is returned.

### Output example

A local snapshot of `BV1xx411c7mD` returned 3,600 pool entries with a 3,000 pool marker. The shortened example below shows the actual shape and aggregate values; text is anonymized and arrays are abbreviated, not the full export.

```json
{
  "bvid": "BV1xx411c7mD",
  "videoUrl": "https://www.bilibili.com/video/BV1xx411c7mD/?p=1",
  "page": 1,
  "cid": 62131,
  "title": "Example video",
  "partTitle": "",
  "danmakuFetched": 3600,
  "uniqueSenders": 2951,
  "poolLimit": 3000,
  "poolCapped": true,
  "totalDanmaku": 128639,
  "perMinute": [{"minute": 0, "count": 3280}],
  "peakMinute": {"minute": 0, "count": 3280},
  "firstTimeSeconds": 0,
  "lastTimeSeconds": 2054.425,
  "durationSeconds": 2055,
  "danmakuPerMinute": 105.1094890510949,
  "samples": [{"timeSeconds": 0, "text": "Example reaction", "mode": 5, "fontSize": 25, "color": 15138834, "sentAt": "2020-04-23T04:22:43.000Z"}],
  "sampleCount": 100,
  "coverage": "observed-public-xml-pool",
  "scrapedAt": "2026-10-08T20:07:44.376Z"
}
```

### How much does it cost to sample Bilibili danmaku?

A run has a $0.005 start event and one pool-profile event per saved video part. Timed texts and minute buckets are included in that profile; they have no separate event charge. The profile price is $0.00884 on BRONZE, $0.010166 on FREE, $0.0068952 on SILVER and $0.005304 on GOLD, PLATINUM and DIAMOND. Spend tiers follow your qualifying aggregate monthly Store spend, not this Actor's individual run volume.

At BRONZE, total run event charges including start are $0.01384 for one saved profile, $0.0934 for ten and $0.889 for 100. FREE estimate: $0.015166 total for one run with one saved profile. These examples concern Actor event charges, not any separate account/platform charges. A valid empty-pool profile is still a profile. Failed requests do not generate profile events; the start event and any already saved profiles remain charged.

### Limits and failure behavior

Public XML pools change over time and may omit historical comments, suppressed texts or restricted videos. This Actor does not download media, subtitles, comment replies, historical segments or sender profiles. Snapshot differences are not guaranteed new-comment feeds, deletions or population trends.

Requests are sequential with a 20-second timeout. Transient network errors, HTTP 429 and 5xx receive at most two retries. Stable 4xx/challenges fail without blind retries. No automatic paid proxy or browser fallback is enabled. Metadata/XML errors fail the run; previously saved rows are partial, not a completed full result.

Each upstream response is capped at 4 MiB and each XML pool at 50,000 entries. Oversized pools fail visibly rather than being silently truncated. Default run timeout is five minutes; choose a smaller input batch when the source is slow. A billing limit can also stop collection; check run status and returned counts.

### Integrations

Use JSON exports for nested samples. Unwind `samples` into spreadsheet rows when individual texts are needed; unwind `perMinute` for reaction charts. Join BV/part keys with [Bilibili Scraper](https://apify.com/automation-lab/bilibili-scraper) for video metadata and top-level comments.

An Apify schedule can repeat the same supplied-video snapshot. Save snapshot times and compare observed counts downstream; do not interpret a changing pool as a complete archive. Webhooks can notify your ETL pipeline when a run succeeds. Make, Zapier and n8n can retrieve the default dataset through Apify's normal integrations.

### API usage

Start a run with your own authorized Apify token:

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~bilibili-timed-danmaku-samples/runs' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"videos":["BV1xx411c7mD"],"sampleSize":100}'
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/bilibili-timed-danmaku-samples')
  .call({ videos: ['BV1xx411c7mD'], sampleSize: 100 });
if (run.status !== 'SUCCEEDED') throw new Error(`Run ${run.id}: ${run.status}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 10 });
console.log(items);
```

Python:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/bilibili-timed-danmaku-samples').call(
    run_input={'videos': ['BV1xx411c7mD'], 'sampleSize': 100})
if run['status'] != 'SUCCEEDED':
    raise RuntimeError(f"Run {run['id']}: {run['status']}")
print(client.dataset(run['defaultDatasetId']).list_items(limit=10).items)
```

### MCP usage

Use this Actor-specific selection with an authorized Apify MCP client:

```bash
claude mcp add --transport http apify \
  'https://mcp.apify.com?tools=automation-lab/bilibili-timed-danmaku-samples'
```

Claude Desktop, Cursor and VS Code: use the equivalent client configuration below (or your client's HTTP MCP configuration UI).

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/bilibili-timed-danmaku-samples"}}}
```

Discover actual names and argument schemas with `tools/list`. Actor selection also adds run/dataset/KV retrieval and abort tools; discovery is not permission to run or abort anything. Example prompt (authorized invocation): “Sample 100 timed danmaku texts from BV1xx411c7mD and report its observed peak minute, clearly stating pool coverage.”

Start once and retain the run ID, status and dataset ID; IDs are not result rows. `call-actor` waits at most 45 seconds (default 30). If still running, check the same run with 2/4/8-second backoff capped at 10 seconds and a 120-second total deadline. Set the client timeout above the chosen wait plus transport margin. On client timeout recover the known run, not a new run; if its ID is unknown report uncertainty. At deadline report pending and stop.

After success, read bounded dataset pages with explicit limit, offset and required fields. Example consumer budget: 20 source rows per page, at most 100 rows or 64 KiB serialized UTF-8 across admitted pages, whichever comes first. Enforce bytes host-side before model injection; omit or summarize an oversized row with disclosure. If your client cannot intercept oversized responses, do not claim a hard byte guarantee. Track source offsets, continuation and partial/truncated status; keep full exports outside model context. Discovery caching does not guarantee token savings.

### Legality, responsible use and diagnostics

Use public content only for a lawful purpose, respect Bilibili's terms, content rights and applicable privacy laws, and avoid re-identifying commenters. Public availability is not a legal license. Texts can contain personal information even though this Actor removes structured sender identifiers. Apply downstream retention and access controls appropriate to your use.

This independent Actor is not affiliated with or endorsed by Bilibili. It uses no AI model at runtime; text is source content, not generated sentiment or summaries. Results use your Apify storage retention settings; delete datasets and runs through Apify controls. There is no persistent private comment cache. For help, use the Actor's Apify Issues tab and include a safe reproduction input and run link.

Failed operations send sanitized diagnostic input, exceptions and Actor/build/run IDs to our private GlitchTip service for repair. Secret fields and URL queries are removed, and reports are retained for 30 days. No diagnostic endpoint or credential is part of the public input.

### FAQ and troubleshooting

**Is this all danmaku ever sent?** No. It is the current observed public XML pool, with bounded text samples.

**Why does fetched count exceed poolLimit?** Bilibili can return more entries than its advertised marker. We report both instead of silently clamping the data.

**Why do counts differ across runs?** Pools and lifetime counters change independently. Compare `scrapedAt`, selected part and coverage before interpreting differences.

**Why is totalDanmaku null?** The selected video has multiple parts; the video-wide total cannot be assigned to a single part.

**What if I see 412 or another upstream failure?** Check video availability and run logs. The run fails honestly rather than exporting an error as a profile. Reduce your batch and try later; no login or paid recovery is silently attempted.

**Can I request no texts?** Set `sampleSize` to zero. Pool metrics remain available and the profile event still applies.

### Related Actor

[Bilibili Scraper](https://apify.com/automation-lab/bilibili-scraper) complements these timed pool profiles with public video metadata, engagement and optional top-level comments. Neither product promises complete comment history.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/bilibili-timed-danmaku-samples/changelog.md

# Actor input Schema

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

Supply 1–100 public BV IDs or HTTPS bilibili.com/video/BV… URLs. IDs use part 1; a URL's p parameter selects that part. Duplicate BV/part pairs are removed in input order. No login cookies, short links, search or av IDs are supported.

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

Global run limit on unique supplied BV/part profiles, in input order after deduplication. Default 10; range 1–100. Zero and unlimited are not supported. Upstream failures fail the run and any previously saved profiles are partial.

## `sampleSize` (type: `integer`):

Maximum timed texts per video-part profile, 0–500 (default 100). Samples are evenly spaced by index after playback-time sorting, not random or evenly spaced in seconds. Zero emits metrics only. Counts and minute profiles use the entire observed XML pool, independent of this sample limit; neither mode promises complete history.

## Actor input object example

```json
{
  "videos": [
    "BV1xx411c7mD"
  ],
  "maxItems": 10,
  "sampleSize": 100
}
```

# Actor output Schema

## `dataset` (type: `string`):

Default dataset of observed public XML pool metrics and timed text samples.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "videos": [
        "BV1xx411c7mD"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/bilibili-timed-danmaku-samples").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "videos": ["BV1xx411c7mD"] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/bilibili-timed-danmaku-samples").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "videos": [
    "BV1xx411c7mD"
  ]
}' |
apify call automation-lab/bilibili-timed-danmaku-samples --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/bilibili-timed-danmaku-samples"
        }
    }
}
```

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/XeoYoKxv2YRfeNHbC/builds/Mo9F2VzIF1eXdFImj/openapi.json
