哔哩哔哩(B 站)作为国内领先的视频社区,其item_get_video接口是获取单条视频精细化详情的核心工具,可返回视频播放地址、分集信息、互动数据、UP 主信息等关键内容,广泛应用于视频聚合平台、内容数据分析、二次创作素材调研、舆情监测等场景。
一、接口核心认知:功能与适配场景
1. 接口定位与核心价值
2. 核心参数与返回字段
(1)请求参数(必填 + 可选,按优先级排序)
(2)返回核心字段(按业务场景分类)
3. 接口限制与注意事项
二、对接前准备:权限与环境搭建
1. 获取接口权限(两种接入方式)
2. 技术环境准备
(1)支持语言与协议
(2)必备工具与依赖
三、实操步骤:接口对接全流程(Python 示例)
步骤 1:理解 B 站接口签名规则
签名示例
步骤 2:完整代码实现(Python)
(1)依赖安装
(2)核心代码(含签名生成、接口调用、数据解析)
import requests
import hashlib
import time
import json
import pandas as pd
from urllib.parse import urlencode
from jsonpath_ng import parse
import logging
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
# 日志配置(记录接口调用与错误信息)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[logging.FileHandler("bilibili_item_get_video.log"), logging.StreamHandler()]
)
# 接口配置(替换为自身的appkey、secret、接口地址)
CONFIG = {
"appkey": "你的appkey",
"secret": "你的secret",
"api_url": "https://api.example.com/bilibili/item_get_video", # 官方/服务商接口地址
"save_path": "B站视频详情.xlsx"
}
def generate_sign(params: dict, secret: str) -> str:
"""生成B站接口签名(MD5 32位大写)"""
# 移除sign字段(若存在)
params.pop("sign", None)
# 按参数名ASCII升序排序
sorted_params = sorted(params.items(), key=lambda x: x[0])
# 拼接参数字符串并追加secret
param_str = urlencode(sorted_params, encoding="utf-8") + f"&secret={secret}"
# MD5加密并转大写
md5 = hashlib.md5()
md5.update(param_str.encode("utf-8"))
return md5.hexdigest().upper()
def parse_bilibili_data(raw_data: dict) -> dict:
"""解析B站视频原始数据,标准化输出格式"""
# 提取UP主信息
up_info = raw_data.get("owner", {}) or raw_data.get("up_info", {})
# 提取分P信息
page_info = raw_data.get("pages", [])
page_count = len(page_info) if page_info else 1
page_titles = [page.get("part", "") for page in page_info] if page_info else [raw_data.get("title", "")]
# 格式化时间
pubdate = raw_data.get("pubdate", 0)
pubdate_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(pubdate)) if pubdate else ""
return {
"BV号": raw_data.get("bvid", ""),
"AV号": raw_data.get("aid", ""),
"视频标题": raw_data.get("title", ""),
"封面链接": raw_data.get("pic", raw_data.get("cover_url", "")),
"总时长(分:秒)": f"{raw_data.get('duration', 0)//60}:{raw_data.get('duration', 0)%60:02d}",
"发布时间": pubdate_str,
"视频分区": raw_data.get("tname", raw_data.get("category", "")),
"视频标签": ",".join(raw_data.get("tags", [])),
"视频简介": raw_data.get("desc", ""),
"版权类型": "原创" if raw_data.get("copyright", 2) == 1 else "转载",
"视频状态": "正常" if raw_data.get("state", 0) == 0 else "审核中" if raw_data.get("state") == -1 else "已下架",
"播放量": raw_data.get("view", 0),
"弹幕数": raw_data.get("danmaku", 0),
"评论数": raw_data.get("reply", 0),
"收藏数": raw_data.get("favorite", 0),
"投币数": raw_data.get("coin", 0),
"点赞数": raw_data.get("like", 0),
"转发数": raw_data.get("share", 0),
"充电数": raw_data.get("charge", 0),
"分P总数": page_count,
"分P标题": "|".join(page_titles),
"UP主ID": up_info.get("mid", ""),
"UP主昵称": up_info.get("name", up_info.get("uname", "")),
"UP主头像": up_info.get("face", ""),
"UP主粉丝数": up_info.get("follower", 0),
"UP主等级": up_info.get("level", 0),
"认证类型": up_info.get("official_verify", {}).get("type", -1),
"播放链接": raw_data.get("play_url", "需跳转B站站内"),
"请求时间": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}
def bilibili_item_get_video(
video_id: str,
need_page: int = 1,
need_up_info: int = 1,
need_danmu: int = 0
) -> dict:
"""
调用B站item_get_video接口,获取视频详情
:param video_id: BV号/AV号/接口video_id
:param need_page: 是否返回分P信息
:param need_up_info: 是否返回UP主详情
:param need_danmu: 是否返回弹幕数据
:return: 标准化的视频详情
"""
# 1. 构建基础参数
params = {
"appkey": CONFIG["appkey"],
"video_id": video_id,
"need_page": need_page,
"need_up_info": need_up_info,
"need_danmu": need_danmu,
"timestamp": int(time.time() * 1000)
}
# 2. 生成签名
params["sign"] = generate_sign(params, CONFIG["secret"])
try:
# 3. 发送POST请求
response = requests.post(
url=CONFIG["api_url"],
data=json.dumps(params),
headers={"Content-Type": "application/json"},
timeout=15,
verify=True
)
response.raise_for_status() # 抛出HTTP异常
result = response.json()
# 4. 解析响应
if result.get("code") == 0 or result.get("status") == "success":
raw_video = result.get("data", result.get("result", {}))
if not raw_video:
logging.warning(f"无视频数据返回(视频ID:{video_id})")
return {"success": False, "error_msg": "无视频数据"}
standard_data = parse_bilibili_data(raw_video)
return {
"success": True,
"data": standard_data,
"error_msg": ""
}
else:
error_msg = result.get("msg", result.get("message", "接口调用失败"))
logging.error(f"接口返回错误(视频ID:{video_id}):{error_msg}(code={result.get('code')})")
return {"success": False, "error_msg": error_msg}
except requests.exceptions.RequestException as e:
logging.error(f"网络请求异常(视频ID:{video_id}):{str(e)}")
return {"success": False, "error_msg": f"网络异常:{str(e)}"}
except Exception as e:
logging.error(f"数据解析异常(视频ID:{video_id}):{str(e)}")
return {"success": False, "error_msg": f"解析异常:{str(e)}"}
def batch_get_video_detail(video_ids: list, **kwargs) -> list:
"""批量获取多个视频的详情"""
all_videos = []
for idx, vid in enumerate(video_ids):
logging.info(f"正在获取第{idx+1}个视频(ID:{vid})")
result = bilibili_item_get_video(vid,** kwargs)
if result["success"]:
all_videos.append(result["data"])
logging.info(f"视频{vid}获取成功")
else:
logging.error(f"视频{vid}获取失败:{result['error_msg']}")
# 控制调用频率(个人用户间隔12秒,企业用户间隔2秒)
time.sleep(12)
return all_videos
def save_video_detail(videos: list, save_path: str = CONFIG["save_path"]):
"""将视频详情保存为Excel"""
if not videos:
logging.warning("无视频数据可保存")
return
df = pd.DataFrame(videos)
# 增量保存,避免覆盖历史数据
try:
history_df = pd.read_excel(save_path, engine="openpyxl")
df = pd.concat([history_df, df], ignore_index=True).drop_duplicates(subset=["BV号"])
except FileNotFoundError:
pass
df.to_excel(save_path, index=False, engine="openpyxl")
logging.info(f"视频详情已保存至{save_path}(共{len(df)}条数据)")
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
# 调用示例
if __name__ == "__main__":
# 单视频获取示例
single_video_result = bilibili_item_get_video(video_id="BV1xx411c7m9")
if single_video_result["success"]:
print("B站视频详情:")
for k, v in single_video_result["data"].items():
print(f"{k}:{v}")
else:
print(f"获取失败:{single_video_result['error_msg']}")
# 批量获取示例
# video_list = ["BV1xx411c7m9", "BV1yt411g7qK", "av123456"]
# batch_result = batch_get_video_detail(video_list)
# save_video_detail(batch_result)