I build Qovia, the API used in this post. The method here (search a topic on each platform, pull comments, compare) is generic; Qovia is just where the JSON comes from.
The same topic gets discussed very differently on Bilibili and YouTube: different audiences, different languages, different comment cultures. In this tutorial we'll pick one topic, find a popular video about it on each platform, pull the comments, and compare them side by side in a small pandas table. We won't claim anything about the platforms in general; one topic and a handful of videos is a sample, not a study.
Setup
pip install requests pandas
export QOVIA_KEY=sk-qovia-...
Get a key from Qovia. Requests authenticate with Authorization: Bearer <key>.
import os
import requests
BASE = "https://api.qovia.ai"
HEADERS = {"Authorization": f"Bearer {os.environ['QOVIA_KEY']}"}
spent = 0.0
def get(path, **params):
global spent
r = requests.get(f"{BASE}{path}", headers=HEADERS, params=params, timeout=60)
r.raise_for_status()
body = r.json()
spent += float(body.get("meta", {}).get("charged_usd") or 0)
return body["data"] or {}
Step 1: find one video per platform
A topic needs a keyword in each platform's main language. I'll use TSMC as the example: 台积电 on Bilibili and TSMC on YouTube.
Bilibili search (GET /v1/raw/bilibili/search) takes keyword, page, page_size and order (totalrank, click, pubdate, dm, stow or scores). Results carry the BV id in extra.source_id:
b = get("/v1/raw/bilibili/search", keyword="台积电", order="click", page_size=10)
for i, v in enumerate(b["items"]):
print(i, v["metrics"]["plays"], v["metrics"]["comments"], v["extra"]["title"])
bvid = b["items"][0]["extra"]["source_id"]
YouTube search (GET /v1/raw/youtube/search) takes keyword, upload_date (hour, today, week, month, year; the catalog says the default is this month) and a cursor. The video id is also in extra.source_id:
y = get("/v1/raw/youtube/search", keyword="TSMC", upload_date="year")
for i, v in enumerate(y["items"]):
print(i, v["metrics"]["plays"], v["extra"]["title"])
video_id = y["items"][0]["extra"]["source_id"]
Note that the two search sorts are not the same thing: here Bilibili is ordered by plays, while YouTube's results are in the platform's own order within the upload-date window. Choose videos by hand if you want a fair comparison, and write down how you chose them.
Step 2: video-level numbers
Video detail gives engagement counts. The catalog lists Bilibili's as plays, likes, coins, saves, comments, shares and danmaku (BV ids only), and YouTube's as likes, comments and views, plus channel subscribers.
bv = get("/v1/raw/bilibili/video", bvid=bvid)
yv = get("/v1/raw/youtube/video", video_id=video_id)
print(bv["metrics"])
print(yv["metrics"])
Bilibili's coins and danmaku (on-screen bullet comments) have no YouTube equivalent, so only compare the fields both sides have.
Step 3: pull comments
Both comment endpoints return items, a cursor and has_more:
-
GET /v1/raw/bilibili/comments:bvid,cursor. Top-level comments, hottest first, 20 per page. Pass the previous cursor back unchanged. IP location is not available. -
GET /v1/raw/youtube/comments:video_id,sort(topornewest),cursor. About 20 per page.
def pages(path, id_param, id_value, max_pages=5, **extra):
out, cursor = {}, None
for _ in range(max_pages):
params = {id_param: id_value, **extra}
if cursor:
params["cursor"] = cursor
page = get(path, **params)
for c in page.get("items", []):
out[c["cid"]] = c
if not page.get("has_more"):
break
cursor = page["cursor"]
return list(out.values())
bili = pages("/v1/raw/bilibili/comments", "bvid", bvid)
yt = pages("/v1/raw/youtube/comments", "video_id", video_id, sort="top")
Both sides use top/hottest-first ordering here, so we are comparing the most visible comments, not a random sample. That choice shapes every number below.
Step 4: compare
Each comment has text, likes, reply_count and created_at on both platforms, so a shared table is easy:
import pandas as pd
def frame(comments, platform):
df = pd.DataFrame(
{"platform": platform, "text": c["text"], "likes": c["likes"], "replies": c["reply_count"]}
for c in comments
)
df["chars"] = df["text"].str.len()
return df
df = pd.concat([frame(bili, "bilibili"), frame(yt, "youtube")])
print(df.groupby("platform")[["likes", "replies", "chars"]].agg(["count", "median", "max"]))
Character counts are not directly comparable across languages (a Chinese character carries more meaning than an English one), so read chars as a rough length indicator within a platform, not across them. For content-level comparison, the next step is to run the same analysis on both: translate or use a multilingual model for sentiment, extract the most common terms, or just read the top ten by likes on each side:
for name, g in df.groupby("platform"):
print(f"\n{name}")
print(g.sort_values("likes", ascending=False).head(10)[["likes", "text"]].to_string(index=False))
Illustrative output shape (not real data):
likes replies chars
count median max count median max count median max
platform
bilibili <n> <x> <y> <n> <x> <y> <n> <x> <y>
youtube <n> <x> <y> <n> <x> <y> <n> <x> <y>
Caveats worth writing in your own report
- Sample size and ordering. A few pages of top comments from one video per platform says little about either platform. Use several videos per side.
- Time. YouTube comment times are UTC, converted from YouTube's relative "3 days ago" and only as precise as the unit named; Bilibili's are Beijing time. Don't compare timing at fine granularity.
- Language. Any text comparison needs translation or a multilingual model, with its own error.
- Fields differ. Bilibili has no comment IP location here; YouTube comments carry a creator-heart flag. Compare shared fields only.
Cost
Per the catalog: Bilibili search, video and comments are $0.5 per 1,000 calls each; YouTube search, video and comments are $1 per 1,000 calls each. Calls with no results aren't charged. The spent counter above adds up meta.charged_usd so you can see what each run cost. More on the pricing page.
This article was written with the help of AI.
Top comments (0)