B站的api调用是比较繁琐的,想通过Vercel简化两个api的调用:
想要简化成:
- 弹幕:
/danmaku/?oid=<视频CID>
- 视频播放URL:
/video/<视频AV号或BV号>/playurl
Vercel入门
- 初始化一个项目
npm init
- 创建一个文件夹用作api的根目录,我这里就直接新建为
api
文件夹 - 在该文件夹下创建
index.js
,这样之后可以使用xxx.vercel.app/api
直接访问到 如果是其他名字就需要访问xxx.vercel.app/api/文件名
index.js
文件内容的一个简单演示,标准的 HTTP Handler 的语法:
module.exports = (req, res) => { const { name = 'World' } = req.query res.send(`<h1>Hello ${name}!</h1>`)}
如果想返回json格式,可以使用res.json()
const { name = 'World' } = req.querylet obj = { message:"Hello "+name+"!"}module.exports = (req, res) => { res.json(obj)}
- 配置
Rewrites/Redirects
将api文件夹作为根目录: 在项目根目录创建一个vercel.json
配置如下
{ "rewrites": [ { "source": "/", "destination": "/api" } ]}
实现
目录结构
├─api│ ├─danmaku│ │ ├─dm_pb2.py│ │ ├─dm.proto│ │ ├─index.py│ │ └─requirements.txt│ └─video│ └─playurl.js├─package.json└─vercel.json
vercel.json
{ "rewrites": [ { "source": "/danmaku(.*)", "destination": "/api/danmaku/index.py" }, { "source": "/video/:id/playurl", "destination": "/api/video/playurl.js" } ], "headers": [ { "source": "/(.*)", "headers": [ { "key": "Access-Control-Allow-Credentials", "value": "true" }, { "key": "Access-Control-Allow-Origin", "value": "*" }, { "key": "Access-Control-Allow-Methods", "value": "GET,OPTIONS,PATCH,DELETE,POST,PUT" }, { "key": "Access-Control-Allow-Headers", "value": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" } ] } ]}
获取实时protobuf弹幕
B站弹幕使用了protobuf(一种定义消息格式的语法,它允许定义字段类型、顺序和规则)
-
用python处理是比较简单的,所以danmaku API我决定使用python,这里用到了3个依赖
/api/danmaku/requirements.txt fastapiprotobuf==5.27.2requests -
下载B站弹幕的
.proto
文件,放入/api/danmaku
中:/api/danmaku/dm.proto(下载地址) https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/grpc_api/bilibili/community/service/dm/v1/dm.proto -
下载protobuf对应版本的编译器(我这里是5.27.2)
https://github.com/protocolbuffers/protobuf/releases/tag/v27.2
然后编译dm.proto
文件cd ./api/danmaku/protoc -I=. --python_out=. dm.proto我们就得到一个
dm_pb2.py
文件/api/danmaku/dm_pb2.py # -*- coding: utf-8 -*-# Generated by the protocol buffer compiler. DO NOT EDIT!# NO CHECKED-IN PROTOBUF GENCODE# source: dm.proto# Protobuf Python Version: 5.27.2"""Generated protocol buffer code."""from google.protobuf import descriptor as _descriptorfrom google.protobuf import descriptor_pool as _descriptor_poolfrom google.protobuf import runtime_version as _runtime_versionfrom google.protobuf import symbol_database as _symbol_databasefrom google.protobuf.internal import builder as _builder_runtime_version.ValidateProtobufRuntimeVersion(_runtime_version.Domain.PUBLIC,5,27,2,'','dm.proto')# @@protoc_insertion_point(imports)_sym_db = _symbol_database.Default()DESCRIPTOR = ... -
编写
index.py
,代理https://api.bilibili.com/x/v2/dm/web/seg.so
的请求并响应出json格式/api/danmaku/index.py import sysfrom os.path import dirname, abspathsys.path.append(dirname(abspath(__file__)))import requestsimport google.protobuf.json_format as json_formatimport dm_pb2 as Danmakufrom fastapi import FastAPIfrom fastapi.responses import JSONResponse, Responseapp, subapi = FastAPI() , FastAPI()app.mount("/danmaku", subapi)@subapi.get("/")def get_danmakus(oid, type=1, segment_index=1):url = 'https://api.bilibili.com/x/v2/dm/web/seg.so'params = {'type': type,'oid': oid,'segment_index': segment_index}headers = {'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0"}resp = requests.get(url, params,headers=headers)if resp.status_code == 200:danmaku_seg = Danmaku.DmSegMobileReply()danmaku_seg.ParseFromString(resp.content)return json_format.MessageToDict(danmaku_seg)elif resp.content is not None:return JSONResponse(json_format.MessageToDict(resp.content), resp.status_code)else:return Response(status_code=resp.status_code) -
大功告成!
获取视频流URL
package.json添加如下配置,方便使用import
export
{ "type": "module",}
添加axios依赖
yarn add axios
剩下的只需要一个js文件即可实现
import axios from "axios"
export default async (req, res) => { try { const { id, p = 1 } = req.query const [ aid, bvid ] = id.startsWith("BV") ? [null, id] : [id.substring('av'.length), null]; const { data: { cid, bvid:_bvid } } = (await axios.get(`https://api.bilibili.com/x/web-interface/view`,{params:{aid,bvid}})).data const { data: { durl } } = await (await axios.get(`https://api.bilibili.com/x/player/playurl?cid=${cid}&bvid=${_bvid}&platform=html5&qn=6`)).data res.writeHead(302, { 'Location': durl[p-1].url }); res.end(); } catch (error) { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('404 Not Found'); }}
评论