@bryan-kuang/bilibili-audio-extractor
v0.1.0
Published
Extract audio stream URLs and metadata from Bilibili videos using the native web API — WBI request signing included, no yt-dlp required.
Maintainers
Readme
@bryan-kuang/bilibili-audio-extractor
Extract audio stream URLs and metadata from Bilibili videos using the native web API. TypeScript, zero dependencies, no yt-dlp or headless browser required.
npm install @bryan-kuang/bilibili-audio-extractorimport { BilibiliAudioExtractor } from '@bryan-kuang/bilibili-audio-extractor';
const extractor = new BilibiliAudioExtractor();
const audio = await extractor.extract('https://www.bilibili.com/video/BV1xx411c7BF');
audio.title; // video title
audio.duration; // seconds (the played part, for multi-part videos)
audio.audioUrl; // direct CDN URL for the best audio streamAccepts desktop URLs, mobile URLs, b23.tv short links (resolved automatically), and bare BV... / av... ids.
What it handles for you
- WBI request signing. The
playurlendpoint requires requests signed with rotating keys and Bilibili's mixin-key permutation. Keys are fetched (works logged-out) and cached for ~12 hours. - Anonymous identity bootstrap. Bilibili's risk control rejects requests with a made-up
buvid3cookie (HTTP 412). The extractor obtains a server-issued one from the fingerprint SPI endpoint, once, automatically. - Stream selection. Prefers direct HTTP(S) URLs over HLS,
mp4acodecs over others, then highest bandwidth, from the DASH manifest. - Multi-part (分P) videos.
durationis the default part's length — the one the audio URL plays — not the sum of all parts that the raw API reports.
Streaming the audio
audioUrl is a signed, expiring CDN link, and the CDN rejects requests without the right headers. Use the streamHeaders that come with the result:
const res = await fetch(audio.audioUrl, {
headers: {
Referer: audio.streamHeaders.referer,
'User-Agent': audio.streamHeaders.userAgent,
},
});For FFmpeg: -headers "Referer: https://www.bilibili.com/" and a matching -user_agent. Extract again when a URL expires (typically after ~2 hours) — extraction is two fast API calls (see result.timing).
Higher quality with a logged-in session
Anonymous extraction tops out at standard-quality audio. Passing a logged-in session unlocks the higher-bandwidth streams your account can access:
// Netscape cookie file (e.g. exported from a browser)
new BilibiliAudioExtractor({ cookiesFile: '/path/to/bilibili_cookies.txt' });
// or a raw Cookie header
new BilibiliAudioExtractor({ cookieHeader: 'SESSDATA=...; bili_jct=...' });URL utilities
import { isValidBilibiliUrl, extractVideoId, normalizeUrl } from '@bryan-kuang/bilibili-audio-extractor';
extractVideoId('https://m.bilibili.com/video/av170001'); // { type: 'AV', id: '170001' }
normalizeUrl('BV1xx411c7BF'); // 'https://www.bilibili.com/video/BV1xx411c7BF'Options
| Option | Default | |
|---|---|---|
| userAgent | desktop Chrome UA | Sent on API calls and echoed in streamHeaders |
| cookiesFile | — | Netscape-format cookie file |
| cookieHeader | — | Raw Cookie value; wins over cookiesFile |
| timeoutMs | 15000 | Per-request timeout |
| baseURL | https://api.bilibili.com | API origin override |
| fetchJson, resolveRedirect | global fetch | Injectable HTTP, for testing |
Caveats
- Undocumented API. Bilibili changes these endpoints without notice; pin a version and expect occasional maintenance releases. Issue reports with a failing BV id are welcome.
- Region matters. Some content is mainland-only; the API answers but returns no streams (or region-locked errors) from some networks.
- Personal use. This does what your browser does when you press play — fetch the same streams with the same API. Respect Bilibili's terms and uploaders' rights; don't use it to redistribute content.
Provenance
Extracted from FM-Hachimi, a Discord music bot where this extraction path serves production traffic daily. MIT.
