Blog/开发者工具/

用一个 API 做 B 站创作者调研:一套实用工作流 | SandBase

搭一套 B 站创作者调研工作流:读趋势、搜话题、给创作者建档——一把 SandBase key,不用 B 站登录。

深色电影质感画面:B 站趋势榜解析成搜索结果和一个创作者资料,汇入 agent 核心

在 B 站做创作者调研,归根到底是三个动作:看什么在热、搜一个话题的视频和创作者、给内容背后的创作者建档。这篇教程用 SandBase B 站 API 把这三个动作串成一套 B 站创作者调研工作流——不用 B 站登录,不用爬虫。端点 API 参考是每个参数和响应信封的权威来源;下面展示的业务载荷字段名只是一个示例结构、并非保证的 schema,请以你所调端点的一份真实响应为准核对。

如果你想先看完整的端点全景,从 B 站公开数据 API hub 开始。这一篇是落地的工作流。

先说结论

  • 三步:hot-search(趋势)→ search-all(话题)→ user-profile(创作者)。
  • 每次都是 POST /v1/api/bilibili/<path>,一把 SANDBASE_API_KEY;响应共用 { id, status, model, outputs } 信封。
  • B 站端点把载荷包在一个上游的 { code, data, message } 对象里,所以有用的字段在 data.data 下。
  • 只是公开、只读数据,你这边也不能发帖。这个公开数据流程不需要 Bilibili 登录或 OAuth,但仍需要一把 SandBase API key。

工作流全貌

步骤端点输入你拿到
1. 读趋势bilibili/web/hot-search必填 limit(整数)排名的热门关键词列表
2. 搜话题bilibili/app/search-allkeyword匹配的视频和创作者
3. 给创作者建档bilibili/web/user-profileuid昵称、等级、签名

SandBase B 站端点参考,展示本工作流用到的热搜和创作者端点 端点的 API 参考是每个参数名和响应路径的权威来源。

第 1 步 —— 读趋势榜

先写一个判 status 并解包上游 data 的帮助函数,再读热搜榜:

import os
import requests

BASE = "https://api.sandbase.ai/v1/api/bilibili"
HEADERS = {
    "Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}",
    "Content-Type": "application/json",
}


def call(path: str, payload: dict) -> dict:
    resp = requests.post(f"{BASE}/{path}", headers=HEADERS, json=payload, timeout=90)
    resp.raise_for_status()
    body = resp.json()
    if body.get("status") != "completed":
        raise RuntimeError(body.get("error", {}).get("message", "request did not complete"))
    # 参考只保证信封,业务字段随端点而定,以真实响应为准;
    # 因此防御式解包。
    data = body["outputs"][0]["data"]
    # B 站把载荷包在一个上游 { code, data, message } 对象里
    return data.get("data", {})


trending = call("web/hot-search", {"limit": 10}).get("trending", {}).get("list", [])
for item in trending[:5]:
    print(item.get("keyword"), item.get("heat_score"))

每一项带一个关键词和一个热度分。字段名并非保证的 schema——以一份真实响应为准核对,因为榜单一直在变。

第 2 步 —— 搜一个话题

挑一个关键词,拉匹配的视频和创作者。search-all 接一个 keyword,在 data.item 下返回一个列表:

results = call("app/search-all", {"keyword": trending[0].get("keyword")})
# 参考只保证信封,业务字段随端点而定,以真实响应为准;因此用 .get() 取值
items = results.get("item", [])
# 每一项带一个 goto/type、一个 uri 和作者信息;
# 读 schema 并看一份真实响应,对准你需要的字段
print(len(items), "results")

search-all 的响应把列表嵌在 data.item 下,并带一个 pagination 块用于下一页。迭代之前先看一份真实响应,弄清视频 id 和创作者 id 各自在哪。

SandBase B 站 search-all API 参考,展示 keyword 参数和响应 schema search-all 在 data.item 下返回一个列表——迭代前先读 schema。

第 3 步 —— 给创作者建档

对你在第 2 步浮现出的一个创作者 id(uid),附上账号上下文。user-profile 接一个 uid:

creator = call("web/user-profile", {"uid": "946974"})
# 参考只保证信封,业务字段随端点而定,以真实响应为准;因此用 .get() 取值
print(creator.get("name"), creator.get("level"))

资料读取返回像 name、level、sign 和 sex 这样的字段。下面是一个示例响应结构——字段名并非保证的 schema,请把字段名和数值当作示例,以一份真实响应为准核对:

{
  "id": "cbc4c9c1-84e7-4ea6-9e3f-f5e759c11475",
  "status": "completed",
  "model": "bilibili/web/user-profile",
  "outputs": [
    {
      "data": {
        "code": 0,
        "data": { "mid": "946974", "name": "…", "level": 6, "sign": "…" }
      }
    }
  ]
}

注意双层嵌套:outputs[0].data 是 SandBase 信封载荷,而有用的资料在它内层的 data 下。上面的 call 帮助函数已经帮你解开一层。

SandBase B 站 user-profile API 参考,展示 uid 参数和响应 schema user-profile 在上游 data 对象下返回昵称、等级和签名。

把它串起来

一次最小的创作者调研过程长这样:

# 参考只保证信封,业务字段随端点而定,以真实响应为准;因此用 .get() 取值
trending = call("web/hot-search", {"limit": 10}).get("trending", {}).get("list", [])
report = []

for topic in trending[:10]:
    hits = call("app/search-all", {"keyword": topic.get("keyword")})
    report.append({
        "topic": topic.get("keyword"),
        "heat": topic.get("heat_score"),
        "result_count": len(hits.get("item", [])),
    })
    # 对你从 hits 里提取的创作者 uid,按 schema 调 user-profile

因为每次调用共用同一个信封和同一个 call 帮助函数(包括 data.data 的解包),加重试或速率退避是一处改动的事。当你需要的不止这些读取时,查线上 B 站列表找到合适的端点,接入前先确认它的参数。

处理粗糙的边角

  • 注意双层嵌套。 B 站透传一个上游 { code, data, message } 对象,所以有用的载荷在 data.data 下。在帮助函数里解开一层。
  • 迭代前先看 search-all。 它的列表在 data.item 下,是结构化的,不是扁平的——从真实响应里对准字段,用它的 pagination 块翻页。
  • 对 status 分支。 failed 或 timeout 的请求带 error 而没有 outputs。call 帮助函数已经强制这一点。
  • 尊重速率限制。 作为客户端韧性措施,遇到 HTTP 429 这类瞬时错误时用退避重试。
  • 只是公开数据。 不登录、不发帖,也拿不到私密/仅账号可见的内容。

组合这套工作流

同样的统一信封让它可组合。把趋势关键词换成任意话题,再加第四次读取——比如某个视频的评论——它就用同一个 call 帮助函数、同一套判状态和解包接进来。你也可以把中间那步铺开:对前 N 个趋势关键词,各跑一次 search-all,把数量收进一张表,这样一次过程就给你一份”什么在热、每个话题承载多少内容”的排名快照。因为这些读取共用一种结构,从一个快速脚本走到一个定时任务,基本上只是加个退避和一个存每轮结果的地方。

为什么在 API 层做这件事

你当然可以在浏览器里打开 B 站手动抄数字,但那不 scale,也给不了你可以做趋势的结构化数据。把这三次调用排成定时任务,就把定性的浏览变成了可度量的信号:能逐时画图的热度分、能去重的话题、以及能按等级和粉丝量加权的创作者。因为调用返回的是命名的 JSON 字段(在一次一致的解包之后),每一轮都能干净地落进一张表,再和上一轮做 diff——新的趋势话题、某个关键词上新冒出的创作者,以及平台在看什么的变化。

常见问题

我需要 B 站登录或 OAuth 吗? 不需要。你用自己的 SANDBASE_API_KEY 向 SandBase 鉴权,这三次读取都不需要你这边有 B 站账号或 OAuth。调用它们时你仍然要提供一把 SandBase API key。

为什么 hot-search 要传 limit,又要解包 data.data? hot-search 用一个 limit 参数决定返回多少条趋势项,所以要显式传。而因为 B 站透传一个上游 { code, data, message } 对象,有用的载荷在 data.data 下——在你的 call 帮助函数里解开这一层。两者都以端点参考为准核对。

search-all 的结果怎么翻页? 下一次调用时带上 search-all 接受的分页请求参数,不要假设固定的每页大小。具体参数名以端点参考为准,迭代前先对着一份真实响应核对。

下一步

你现在有了一套可复用的创作者调研工作流,建立在三次公开、只读的调用上。