Bilibili API in Python: videos, comments, popular board (2026)
Four endpoints: Bilibili video search, video detail by BV ID, video comments and the popular board, each a plain GET with an API key, no wbi signature and no buvid3 cookie. The worked example below is 7 calls and costs $0.014; a failed call is not charged.
Updated
Most Python code for Bilibili calls the site's own web endpoints, and it works until it does not. Search wants a wbi signature and a fresh buvid3 cookie, the keys rotate, and a script that ran for weeks starts getting -412 back. The two community projects most of that code was built on, the bilibili-API-collect docs and the bilibili-api Python library, are both archived on GitHub and no longer maintained, so fixes for the next change will not come from there. If you have hit the -412 wall, Bilibili's search keeps returning -412 explains what is going on.
This guide does the same jobs with an API key: search, read a video's numbers by BV ID, page through comments, and read the popular board. Each call is an HTTPS GET; there is no signing code, cookie or login to maintain, and requests is the only library. You end with a CSV of videos and their stats and a CSV of comments.
Step by step
Set the key once: export OKDATA_API_KEY=okd_.... Every answer has code ("ok" on success); lists come in data.items with a page, single objects in data.item.
A note on IDs: every Bilibili video has a BV ID, the BV1… part of its link, as in https://www.bilibili.com/video/BV1Gc411K7wD. That is the id all four endpoints use. The older numeric AV ID still exists and comes back as ext.aid. Getting views, coins and danmaku for a BV ID lists where each number lives.
Step 1: search videos
Bilibili video search takes query and sort: relevance (the default), latest or most_viewed. One call returns about 20 videos for $0.002. Each has the BV ID in id, title, the description in text, published_at, the uploader's name and numeric ID (mid) under author, tags in hashtags, and metrics.views, metrics.likes, metrics.comments and metrics.favorites. With view=full you also get ext.danmaku, ext.duration and ext.section.
curl -G "https://api.okdata.online/v1/bilibili/videos/search" -H "Authorization: Bearer $OKDATA_API_KEY" \
--data-urlencode 'query=相机推荐' --data-urlencode 'sort=most_viewed' --data-urlencode 'view=full'
Step 2: read a video's full stats
Bilibili video detail takes a BV ID as id or the full link as url, for $0.002. Detail adds what search leaves out: metrics.shares, and with view=full, ext.coins, ext.danmaku, ext.aid, ext.cid, ext.duration_s and ext.section. Coins are worth a column of their own: viewers have to spend them, so they say more about a video than likes do.
curl -G "https://api.okdata.online/v1/bilibili/videos/get" -H "Authorization: Bearer $OKDATA_API_KEY" \
--data-urlencode 'id=BV1Gc411K7wD' --data-urlencode 'view=full'
Step 3: page through comments
Bilibili video comments takes the BV ID as id and sort: hot (the default) or latest. About 20 comments come back per page for $0.002, each with text, author.name, author.id (the commenter's mid), likes, replies, published_at and ip_location (the IP属地 label, without the prefix). For the next page, send page.next_cursor back as cursor.
curl -G "https://api.okdata.online/v1/bilibili/videos/comments/list" -H "Authorization: Bearer $OKDATA_API_KEY" \
--data-urlencode 'id=BV1Gc411K7wD' --data-urlencode 'sort=hot'
Step 4: read the popular board
Bilibili popular board takes no parameters and returns the current 综合热门 list, about 50 rows a call, for $0.002. Each row is a trend: rank, name (the video title), heat (its view count) and url (the video link, with the BV ID at the end). The board also pages with cursor if you want more than one screen.
curl -G "https://api.okdata.online/v1/bilibili/trends/list" -H "Authorization: Bearer $OKDATA_API_KEY"
Step 5: the full script
The helper checks body["code"], skips deleted videos, and retries rate limits and busy upstreams with the same Idempotency-Key, so a retry is never charged twice. Paging goes through one generator with a page cap.
import csv
import os
import time
import uuid
import requests
API = "https://api.okdata.online"
KEY = os.environ["OKDATA_API_KEY"]
QUERY = "相机推荐"
TOP_VIDEOS = 2 # detail calls
COMMENT_PAGES = 3 # comment pages under the first video; each page is one call
def call(path, **params):
headers = {"Authorization": f"Bearer {KEY}", "Idempotency-Key": str(uuid.uuid4())}
for attempt in range(4):
body = requests.get(API + path, headers=headers, params=params, timeout=60).json()
if body["code"] == "ok":
return body
if body["code"] == "not_found":
return None # deleted or private: not charged
if body.get("retry") in ("yes", "same_key"):
time.sleep(body.get("retry_after_seconds") or 2 ** attempt)
continue
raise RuntimeError(f"{body['code']}: {body['message']} (request {body['request_id']})")
raise RuntimeError(f"{path}: still failing after 4 tries")
def paged(path, max_pages, **params):
cursor = None
for _ in range(max_pages):
body = call(path, **params, **({"cursor": cursor} if cursor else {}))
if body is None:
return
yield from body["data"]["items"]
if not body["page"]["has_more"]:
return
cursor = body["page"]["next_cursor"]
board = call("/v1/bilibili/trends/list")["data"]["items"]
for row in board[:5]:
print(row["rank"], row["heat"], row["name"], row["url"])
hits = call("/v1/bilibili/videos/search", query=QUERY, sort="most_viewed")["data"]["items"]
videos = []
for hit in hits[:TOP_VIDEOS]:
body = call("/v1/bilibili/videos/get", id=hit["id"], view="full")
if body:
videos.append(body["data"]["item"])
with open("bilibili_videos.csv", "w", newline="", encoding="utf-8-sig") as f:
out = csv.writer(f)
out.writerow(["bvid", "title", "uploader", "views", "likes", "coins", "favorites", "shares", "danmaku", "published_at"])
for v in videos:
m, ext = v["metrics"], v["ext"]
out.writerow([v["id"], v["title"], v["author"]["name"], m["views"], m["likes"], ext.get("coins"),
m["favorites"], m["shares"], ext.get("danmaku"), v["published_at"]])
if videos:
first = videos[0]["id"]
with open("bilibili_comments.csv", "w", newline="", encoding="utf-8-sig") as f:
out = csv.writer(f)
out.writerow(["bvid", "comment_id", "text", "user", "likes", "replies", "ip_location", "published_at"])
for c in paged("/v1/bilibili/videos/comments/list", COMMENT_PAGES, id=first, sort="hot"):
out.writerow([first, c["id"], c["text"], c["author"]["name"], c["likes"], c["replies"], c["ip_location"], c["published_at"]])
That is the worked example: 7 calls. For tracking, the detail call is the one that adds up: refreshing 500 BV IDs every morning for a month is 15,000 calls, $30.00.
Good to know
- Search depth. Bilibili's own search stops after a certain number of pages, and so does this endpoint. For a broad topic, run several narrower keywords.
- Nulls. Search results have no share count (
metrics.sharesis null there; detail has it). Uploader follower counts are null on posts, and there is no Bilibili account endpoint in the catalog yet.meta.unavailablenames each null field. - Comments and replies. You get each comment's reply count, not the reply text. The danmaku themselves are not available either, only their count.
- Multi-part videos. All parts share one BV ID; the counts cover the whole video and
ext.cidis the first part's. - Snapshots. Numbers are read at call time. Keep
meta.fetched_atwith them if you track growth. - Free cases. A deleted video answers
not_found; that, rate limits and every other error cost nothing.
To see the board without code, open the Bilibili hot list tool. For a study of a topic across platforms, content research shows how search, detail and comments fit together.
What the example costs
One search for 相机推荐 (camera recommendations) by views, the full detail of the top 2 videos, 3 pages of hot comments under the first, and one read of the popular board
| Endpoint | Calls | Price | Subtotal |
|---|---|---|---|
| Bilibili video search | 1 | $0.002 | $0.002 |
| Bilibili video detail | 2 | $0.002 | $0.004 |
| Bilibili video comments | 3 | $0.002 | $0.006 |
| Bilibili hot searches | 1 | $0.002 | $0.002 |
| Total | 7 | $0.014 |
Prices in force when this page was built. A failed call costs nothing.
In your AI (MCP)
Connect the MCP server once, then say what you want. The agent finds these endpoints, reads their prices and calls them on your balance.
SaySearch Bilibili for 相机推荐 sorted by views. For the top 2 videos give me views, likes, coins, favorites, shares and danmaku count, then the top comments under the first one. Also show what is on the popular board right now.
What the agent sends to the call tool first
{
"endpoint": "bilibili.videos.search",
"params": {
"query": "Claude Code 教程"
},
"idempotency_key": "<a new UUID>"
}