哔哩哔哩(B 站)的 item_search_video 接口是通过关键词批量检索平台视频列表的核心工具,支持按分区、发布时间、播放量、UP 主类型等多维度筛选,返回视频基础信息、互动数据、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
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_search_video.log"), logging.StreamHandler()]
)
# 接口配置(替换为自身的appkey、secret、接口地址)
CONFIG = {
"appkey": "你的appkey",
"secret": "你的secret",
"api_url": "https://api.example.com/bilibili/item_search_video", # 官方/服务商接口地址
"save_path": "B站视频搜索列表.xlsx"
}
def generate_sign(params: dict, secret: str) -> str:
"""生成B站接口签名(MD5 32位大写)"""
# 1. 移除sign字段
params.pop("sign", None)
# 2. 按参数名ASCII升序排序
sorted_params = sorted(params.items(), key=lambda x: x[0])
# 3. 拼接参数字符串并追加secret
param_str = urlencode(sorted_params, encoding="utf-8") + f"&secret={secret}"
# 4. MD5加密
md5 = hashlib.md5()
md5.update(param_str.encode("utf-8"))
return md5.hexdigest().upper()
def standardize_video_data(raw_video: dict) -> dict:
"""标准化视频数据,统一输出格式"""
# 格式化发布时间
pubdate = raw_video.get("pubdate", 0)
pubdate_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(pubdate)) if pubdate else ""
# 格式化视频时长
duration = raw_video.get("duration", 0)
duration_str = f"{duration//60}:{duration%60:02d}"
return {
"搜索关键词": raw_video.get("keyword", ""),
"BV号": raw_video.get("bvid", ""),
"视频标题": raw_video.get("title", ""),
"封面链接": raw_video.get("cover_url", ""),
"视频时长": duration_str,
"发布时间": pubdate_str,
"视频分区": raw_video.get("category", ""),
"视频标签": ",".join(raw_video.get("tags", [])) if raw_video.get("tags") else "",
"版权类型": "原创" if raw_video.get("copyright", 2) == 1 else "转载",
"视频状态": "正常" if raw_video.get("state", 0) == 0 else "已下架/违规",
"近30天播放量": raw_video.get("view", 0),
"点赞数": raw_video.get("like", 0),
"弹幕数": raw_video.get("danmaku", 0),
"收藏数": raw_video.get("favorite", 0),
"投币数": raw_video.get("coin", 0),
"转发数": raw_video.get("share", 0),
"UP主ID": raw_video.get("up_id", ""),
"UP主昵称": raw_video.get("up_name", ""),
"UP主认证类型": raw_video.get("up_type", ""),
"UP主粉丝数": raw_video.get("up_fans", 0),
"请求时间": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
}
def bilibili_item_search_video(
keyword: str,
category: str = None,
time_range: str = "all",
start_date: str = None,
end_date: str = None,
sort_type: str = "relevance",
up_type: str = "all",
copyright: int = 0,
page_no: int = 1,
page_size: int = 20
) -> dict:
"""
调用B站item_search_video接口,获取关键词视频列表
:param keyword: 搜索关键词
:param category: 视频分区
:param time_range: 发布时间范围
:param start_date: 自定义开始日期
:param end_date: 自定义结束日期
:param sort_type: 排序方式
:param up_type: UP主认证类型
:param copyright: 版权类型
:param page_no: 页码
:param page_size: 每页条数
:return: 标准化的视频列表数据
"""
# 1. 校验参数合法性
if time_range == "custom" and not (start_date and end_date):
logging.error("time_range=custom时,start_date和end_date为必填参数")
return {"success": False, "error_msg": "缺少自定义时间参数"}
# 2. 构建基础参数
params = {
"appkey": CONFIG["appkey"],
"keyword": keyword,
"time_range": time_range,
"sort_type": sort_type,
"up_type": up_type,
"copyright": copyright,
"page_no": page_no,
"page_size": page_size,
"timestamp": int(time.time() * 1000)
}
# 3. 补充可选参数
if category:
params["category"] = category
if time_range == "custom":
params["start_date"] = start_date
params["end_date"] = end_date
# 4. 生成签名
params["sign"] = generate_sign(params, CONFIG["secret"])
try:
# 5. 发送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()
# 6. 解析响应结果
if result.get("code") == 0 or result.get("status") == "success":
raw_data = result.get("data", {})
video_list = raw_data.get("item_list", [])
total = raw_data.get("total", 0)
page_total = raw_data.get("page_total", 1)
# 标准化视频数据
standard_videos = []
for video in video_list:
video["keyword"] = keyword # 补充关键词字段
standard_videos.append(standardize_video_data(video))
return {
"success": True,
"data": standard_videos,
"total": total,
"page_no": page_no,
"page_total": page_total,
"error_msg": ""
}
else:
error_msg = result.get("msg", result.get("message", "接口调用失败"))
logging.error(f"接口返回错误(关键词:{keyword}):{error_msg}(code={result.get('code')})")
return {"success": False, "error_msg": error_msg}
except requests.exceptions.RequestException as e:
logging.error(f"网络请求异常(关键词:{keyword}):{str(e)}")
return {"success": False, "error_msg": f"网络异常:{str(e)}"}
except Exception as e:
logging.error(f"数据解析异常(关键词:{keyword}):{str(e)}")
return {"success": False, "error_msg": f"解析异常:{str(e)}"}
def batch_get_video_list(
keyword: str,
max_page: int = 5,
**kwargs
) -> list:
"""批量获取多页视频列表,控制调用频率"""
all_videos = []
page_no = 1
while True:
logging.info(f"正在获取关键词「{keyword}」第 {page_no} 页视频")
result = bilibili_item_search_video(keyword=keyword, page_no=page_no, **kwargs)
if not result["success"]:
logging.error(f"第 {page_no} 页获取失败:{result['error_msg']}")
break
page_videos = result["data"]
if not page_videos:
logging.info(f"第 {page_no} 页无视频数据,批量获取结束")
break
all_videos.extend(page_videos)
logging.info(f"第 {page_no} 页获取成功,新增 {len(page_videos)} 条数据(累计 {len(all_videos)} 条)")
# 终止条件:达到最大页码或总页码
if page_no >= max_page or page_no >= result["page_total"]:
break
page_no += 1
# 控制频率(个人用户间隔12秒,企业用户间隔2秒)
time.sleep(12)
return all_videos
def save_video_list(videos: list, save_path: str = CONFIG["save_path"]):
"""将视频列表保存为Excel文件,支持增量去重"""
if not videos:
logging.warning("无视频数据可保存")
return
df = pd.DataFrame(videos)
# 按BV号去重
df = df.drop_duplicates(subset=["BV号"])
# 增量保存
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__":
# 单页获取示例:搜索“Python教程 零基础”,按播放量排序,知识分区,近30天
single_page_result = bilibili_item_search_video(
keyword="Python教程 零基础",
category="knowledge",
time_range="30days",
sort_type="play",
page_size=20
)
if single_page_result["success"]:
print(f"获取到 {len(single_page_result['data'])} 条视频数据")
for video in single_page_result["data"][:5]: # 打印前5条
print(f"标题:{video['视频标题']} | 播放量:{video['近30天播放量']} | UP主:{video['UP主昵称']}")
else:
print(f"单页获取失败:{single_page_result['error_msg']}")
# 批量获取示例:获取前5页数据并保存
# batch_videos = batch_get_video_list(
# keyword="Python教程 零基础",
# category="knowledge",
# time_range="30days",
# sort_type="play",
# max_page=5
# )
# save_video_list(batch_videos)