Bilibili is unique in the global streaming landscape. Unlike Western platforms that often rely on standard HLS (HTTP Live Streaming) or simple MP4 progressive downloads, Bilibili has pioneered an extremely efficient, albeit complex, delivery system based on fragmented DASH (Dynamic Adaptive Streaming over HTTP) and a proprietary authentication layer.
Building a downloader that can pull 4K, 120fps, or HDR content requires more than just a GET request. It requires reverse-engineering the WBI signature system, bypassing referer-based hotlinking protections, and performing hardware-accelerated muxing.
- Understanding the Media Architecture: The Transition from FLV to DASH
Historically, Bilibili used FLV (Flash Video) containers. Today, the platform uses DASH. In a DASH architecture, the media is not a single file but a collection of "representations."
Video-Audio Separation: Bilibili serves the video track and the audio track as separate streams. A high-quality 4K video might have a bitrate of 15 Mbps, while its corresponding Hi-Res audio track is a separate 320kbps stream.
Segmented M4S: These streams are delivered as .m4s files (ISO Base Media File Format). They are essentially fragmented MP4s that must be combined at the client side.
- The Core Workflow: From URL to Binary Data
A professional downloader follows a strictly defined four-stage pipeline:
- Metadata Extraction: Converting a BVID (Base58 ID) into a CID (Content ID).
- Signature Generation (WBI): Signing the API request to prevent 403 Forbidden errors.
- Stream Negotiation: Requesting the
playurlwith specific quality flags (qn) and format flags (fnval). - Parallel Multi-part Download: Using HTTP Range requests to maximize bandwidth.
-
Muxing: Using FFmpeg to merge the elementary streams.
- Deep Dive: Cracking the WBI Signature
Bilibili's most potent anti-scraping measure is the WBI (Web-based Interface) signature. If you call their playurl API without a valid w_rid and wts, the server will return a risk control error.
The Algorithm
The WBI signature is generated using two keys: img_key and sub_key, which are found in the navigation data of the Bilibili homepage.
- Key Extraction: Scrape the
navAPI to get the URLs of the user's avatar and top bar icons. The filenames (minus extensions) are your raw keys. - Shuffling: Bilibili uses a fixed array of indices to shuffle these keys into a new 64-character string.
- Mixing: The parameters of your API call (e.g.,
bvid,cid,qn) are sorted alphabetically and appended with the shuffled key. - Hashing: The resulting string is MD5 hashed to create the
w_rid.
python
import hashlib
import time
def get_wbi_keys(img_url, sub_url):
Extracting the alphanumeric parts of the URLs
img_key = img_url.split('/')[-1].split('.')[0]
sub_key = sub_url.split('/')[-1].split('.')[0]
return img_key + sub_key
def sign_wbi(params, mixin_key):
curr_time = int(time.time())
params['wts'] = curr_time
Sort parameters by key
sorted_params = dict(sorted(params.items()))
query = "&".join([f"{k}={v}" for k, v in sorted_params.items()])
Append the mixin key and hash
w_rid = hashlib.md5((query + mixin_key).encode()).hexdigest()
return w_rid
- Playurl Negotiation: Selecting the Right Quality
When querying the https://api.bilibili.com/x/player/playurl endpoint, the fnval (Function Value) parameter is the most important bitmask.
fnval=1: Legacy FLV (not recommended).
fnval=16: DASH format.
fnval=64: HDR/Dolby Vision support.
fnval=128: 4K Resolution support.
fnval=256: AV1 encoding (superior compression).
To get 4K AV1 video with Dolby Audio, you would set fnval=400 (16 + 128 + 256).
- Overcoming Download Barriers: Headers and Range Requests
Bilibili’s Content Delivery Network (CDN) uses strict Referer Validation. Even with the correct URL, a standard browser request will fail with a 403 Forbidden.
The "Referer" Trick
You must set the Referer header to https://www.bilibili.com/. Without this, the CDN assumes you are hotlinking their assets.
High-Speed Multi-threading
To bypass the per-connection speed limit, we use HTTP Range Requests. By splitting the video.m4s file into 5MB chunks and downloading them in parallel using asyncio and httpx, we can saturate a gigabit connection.
python
async def download_chunk(client, url, start, end):
headers = {
"Range": f"bytes={start}-{end}",
"Referer": "https://www.bilibili.com/"
}
response = await client.get(url, headers=headers)
return response.content
- Post-Processing: Lossless Muxing with FFmpeg
Because Bilibili provides the video and audio as separate streams, the final step is Muxing. We must avoid "Transcoding" (re-encoding), which degrades quality and takes immense CPU power. Instead, we perform a "Stream Copy."
python
import subprocess
def mux_video_audio(video_path, audio_path, output_path):
cmd = [
'ffmpeg', '-i', video_path, '-i', audio_path,
'-c', 'copy', This is the magic flag for lossless muxing
'-map', '0✌️0', '-map', '1🅰️0',
output_path, '-y'
]
subprocess.run(cmd, check=True)
- Scaling and Stability: Handling 403s and Session Data
For 1080P and higher resolutions, Bilibili requires a valid SESSDATA cookie.
Session Management: Your downloader must support cookie persistence.
Rate Limiting: Bilibili employs "Smart IP Blocking." If you request too many Playurls in a short window, your IP will be temporarily blacklisted. Implementing a randomized exponential backoff is mandatory for production use.
- Conclusion
Building a Bilibili downloader is an exercise in modern web engineering. It forces you to handle asynchronous I/O, cryptographic signing, and media stream synchronization. By mastering the DASH protocol and the WBI signature logic, you gain the ability to interface with one of the most sophisticated media platforms in the world.
Top comments (0)