在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):czp3009/bilibili-api开源软件地址(OpenSource Url):https://github.com/czp3009/bilibili-api开源编程语言(OpenSource Language):Kotlin 98.3%开源软件介绍(OpenSource Introduction):Bilibili API JVM 调用库该项目提供 Bilibili API 的 JVM 调用, 协议来自 Bilibili Android APP 的逆向工程以及截包分析. 使用一台虚拟的 使用compile group: 'com.hiczp', name: 'bilibili-api', version: '0.2.1' 对于 Android 项目, 如需要解析弹幕, 添加 compile group: 'javax.xml.stream', name: 'stax-api' version: last_version
compile group: 'org.codehaus.woodstox', name: 'woodstox-core-asl' version: last_version 技术说明
所有调用从这个类开始, 包括登陆以及访问其他各种 API. 使用协程来实现异步, 由于 kotlin coroutines 为编译器实现, 因此并非所有 JVM 语言都能正确调用 本项目尽可能的兼容其他 JVM 语言和 Android, 不要问, 问就没测试过.
一个客户端下各种不同类型的 API (代理类)都是惰性初始化的, 并且只初始化一次, 因此不需要保存 API 的引用, 例如以下代码是被推荐的: runBlocking {
val bilibiliClient = BilibiliClient().apply {
login(username, password)
}
val myInfo = bilibiliClient.appAPI.myInfo().await()
val reply = bilibiliClient.mainAPI.reply(oid = 44154463).await()
} 如果一个请求的返回内容中的 val code = bilibiliApiException.commonResponse.code 一个错误返回的原始 {
"code": -629,
"message": "用户名与密码不匹配",
"ts": 1550730464
} 每种不同的 API 在错误时返回的 登录和登出(Bilibili oauth2 v3) 登陆和登出均为异步方法, 需要在协程上下文中执行(接下去不会特地强调这一点). runBlocking {
BilibiliClient().run {
login(username, password)
logout()
}
}
BilibiliClient().apply {
this.loginResponse = loginResponse
}
可能的错误返回有两种:
如果仅使用用户名与密码进行登陆并且得到了 原始返回如下所示
自行访问 login(username, password, challenge, secCode, validate)
(注意, 极验会根据滑动的轨迹来识别人机, 所以要为最终用户打开一个 WebView 来进行真人操作而不能自动完成. 极验最终返回的是一个 jsonp, 里面包含以上三个参数, 详见极验接入文档). 注意, 登陆后, 可以访问全部 API(注意, 有一些明显不需要登录的 API 也有可能需要登录). 注意, 即使返回 {
"ts": 1584206212,
"code": 0,
"data": {
"status": 1,
"url": "https://passport.bilibili.com/mobile/verifytel_h5.html?mid\u003d517548681\u0026tel\u003d156****0364\u0026source\u003d2\u0026keepTime\u003d0\u0026appId\u003d878\u0026subId\u003d0\u0026ticket\u003d1"
}
} 需要手动判断 由于各种需要登陆的 API 在未登录时返回的 在真实的客户端上, 每次一打开 APP 就会访问个人信息 API来确定 访问 API不要问文档, 用自动补全(心)来感受. 以下给出几个示例 获取个人信息(首先要登陆) val myInfo = bilibiliClient.appAPI.myInfo().await() 返回用户 ID, vip 信息等. 搜索当我们想看某些内容时, 我们会首先使用搜索功能, 例如 val searchResult = bilibiliClient.appAPI.search(keyword = "刀剑神域").await() 实际上这对应客户端上的 搜索 -> 综合. 如果要搜索番剧则使用 同理, 搜索直播, 用户, 影视, 专栏分别使用 所有的搜索都使用 获取视频播放地址获取视频实际播放地址的 API 比较特殊, 被单独分了出来, 示例如下 val videoPlayUrl = bilibiliClient.playerAPI.videoPlayUrl(aid = 41517911, cid = 72913641).await()
在 Web 端, URL 通常是这样的
实际上就是选择了该 简单的来说, 因此无论是获取视频播放地址, 还是获取弹幕列表, 都要同时传入 而 val view = bilibiliClient.appAPI.view(aid = 41517911).await() 该接口返回对一个视频页面的描述信息(甚至包含广告和推荐), 客户端根据这些信息生成视频页面. 其中 请求视频地址将访问如下结构的内容 {
"code": 0,
"data": {
"accept_description": [
"高清 1080P+",
"高清 1080P",
"高清 720P",
"清晰 480P",
"流畅 360P"
],
"accept_format": "hdflv2,flv,flv720,flv480,flv360",
"accept_quality": [
112,
80,
64,
32,
16
],
"dash": {
"audio": [
{
"bandwidth": 319173,
"base_url": "http://upos-hz-mirrorks3u.acgvideo.com/upgcxcode/18/58/77995818/77995818-1-30280.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&deadline=1551113319&gen=playurl&nbs=1&oi=3670888782&os=ks3u&platform=android&trid=925269b941bf4883ac9ec92c6ab5af4e&uipk=5&upsig=33273eaf403739d9f51304509f55589e",
"codecid": 0,
"id": 30280
},
{
"bandwidth": 67326,
"base_url": "http://upos-hz-mirrorkodou.acgvideo.com/upgcxcode/18/58/77995818/77995818-1-30216.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&deadline=1551113319&gen=playurl&nbs=1&oi=3670888782&os=kodou&platform=android&trid=925269b941bf4883ac9ec92c6ab5af4e&uipk=5&upsig=3d1f9b836430bb8033b2f318faf42f9b",
"codecid": 0,
"id": 30216
}
],
"video": [
{
"bandwidth": 376693,
"base_url": "http://upos-hz-mirrorks3u.acgvideo.com/upgcxcode/18/58/77995818/77995818-1-30015.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&deadline=1551113319&gen=playurl&nbs=1&oi=3670888782&os=ks3u&platform=android&trid=925269b941bf4883ac9ec92c6ab5af4e&uipk=5&upsig=82bc845bce9f22b731b062bf83fa000f",
"codecid": 7,
"id": 16
},
...
{
"bandwidth": 2615324,
"base_url": "http://upos-hz-mirrorcosu.acgvideo.com/upgcxcode/18/58/77995818/77995818-1-30080.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEuENvNC8aNEVEtEvE9IMvXBvE2ENvNCImNEVEIj0Y2J_aug859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859IB_&deadline=1551113319&dynamic=1&gen=playurl&oi=3670888782&os=cosu&platform=android&rate=0&trid=925269b941bf4883ac9ec92c6ab5af4e&uipk=5&uipv=5&um_deadline=1551113319&um_sign=22fef3c0efa0d23388429f6926fad298&upsig=c4768c036beb667ba4648369770f8de8",
"codecid": 7,
"id": 80
}
]
},
"fnval": 16,
"fnver": 0,
"format": "flv480",
"from": "local",
"quality": 32,
"result": "suee",
"seek_param": "start",
"seek_type": "offset",
"timelength": 175332,
"video_codecid": 7,
"video_project": true
},
"message": "0",
"ttl": 1
} (由于内容太长, 去除了一部分内容) 注意, 视频下载地址有好几个(以上返回内容中被折叠成了两个), 但是实际上他们都是一样的内容, 只是清晰度不同. 视频和音频是分开的, 视频和音频都返回
对于番剧来说, 也使用 val bangumiPlayUrl = bilibiliClient.playerAPI.bangumiPlayUrl(aid = 42714241, cid = 74921228).await() 返回内容差不多是一个原理, 这里就不赘述了. 如何获得番剧的 我们在番剧搜索页面可以得到番剧的 然后我们用 val season = bilibiliClient.mainAPI.season(seasonId = 25617).await() 返回值中的 该 API 还可以用 返回值中的 查看视频下面的评论看完了视频当然要看一下傻吊网友都在说些什么. 使用以下 API 获取一个视频的评论. val reply = bilibiliClient.mainAPI.reply(oid = 44154463).await() 这里的 评论是不分 可以额外使用一个 楼层是越翻越小的, 所以 看到了傻吊网友们的评论是不够的, 我们还想看到杠精与其隔着屏幕对喷的场景, 因此我们要获取评论的子评论, 即评论的评论 val childReply = bilibiliClient.mainAPI.childReply(oid = 16622855, root = 1405602348).await() 其中的 每个评论都有自己的 假如一个人在一个评论的子评论里发布了一个评论并且 at 了其他人发的评论, 那么其 如果不满足对应的层级逻辑关系(例如本身为根评论), 用额外的 注意, 子楼层是越翻越大的. 如果一个根评论下面有很多个喷子在互喷, 会导致看不清, 客户端上有一个按钮 "查看对话" 就是解决这个问题的. val chatList = bilibiliClient.mainAPI.chatList(oid = 34175504, root = 1136310360, dialog = 1136351035).await()
用 番剧下面的评论用一样的方式获取. 获得一个视频的弹幕看评论自然不够刺激, 我们想看到弹幕! 获取弹幕非常简单 val danmakuFile = bilibiliClient.danmakuAPI.list(aid = 810872, oid = 1176840).await() 弹幕是一个文件, 可能非常大, 里面是二进制内容. 为了解析弹幕, 我们要用到另一个类 val (flagMap, danmakuList) = DanmakuParser.parser(danmakuFile.byteStream())
弹幕等级在区间 [1, 10] 内, 低于客户端设置的 "弹幕云屏蔽等级" 的弹幕将不会显示出来.
使用以下代码来输出全部弹幕的内容 danmakuList.forEach {
println(it.content)
} 注意, 弹幕的解析是惰性的, 客户端的弹幕屏蔽设置是对弹幕中的 这个字符串是 用户ID 的 众所周知, 一切 hash 算法都有冲突的问题. 这也就意味着, 屏蔽一个用户的同时可能屏蔽掉了多个与该用户 hash 值相同的用户. 在另一方面, 通过这个 如果想获得发送这条弹幕的所有可能的用户的 ID, 可以通过以下方法: val possibleUserIds = danmaku.calculatePossibleUserIds() 返回一个 注意, 第一次使用 Crc32Cracker ( 通常情况下, 一次 由于这是一个比较耗时的操作, 请不要每条弹幕都如此操作(相比较 6000 条弹幕的解析只需要 番剧的弹幕同理. 发送视频弹幕光看不发憋着慌, 我们来发送一条视频弹幕: bilibiliClient.mainAPI.sendDanmaku(aid = 40675923, cid = 71438168, progress = 2297, message = "2333").await() 其中
如果不确定视频的长度, 需要从视频播放地址的 API 中的 获取直播弹幕刚进入直播间时, 立即看到的十条弹幕实际上是最近的历史弹幕, 通过以下方式来获取 bilibiliClient.liveAPI.roomMessage(roomId).await() 接下来的弹幕都是实时弹幕, 直播间实时弹幕通过 val job = bilibiliClient.liveClient(roomId = 3) {
onConnect = {
println("Connected")
}
onPopularityPacket = { _, popularity ->
println("Current popularity: $popularity")
}
onCommandPacket = { _, jsonObject ->
println(jsonObject)
}
onClose = { _, closeReason ->
println(closeReason)
}
}.launch() 服务器推送的
例如一个弹幕数据是这样的( {"cmd":"DANMU_MSG","info":[[0,1,25,16777215,1553417856,1553414245,0,"9e539d78",0,0,0],"记得存档!",[3432444,"喵的叫一声",0,0,0,10000,1,""],[6,"日常","奶粉の日常",35399,5805790,""],[22,0,5805790,">50000"],["",""],0,0,null,{"ts":1553417856,"ct":"87255D9C"}]}
{"cmd":"WELCOME","data":{"uid":110208099,"uname":"霸刀宋壹i","is_admin":false,"svip":1}} 各种 由于
onCommandPacket = { _, jsonObject ->
val cmd by jsonObject.byString
println(
if (cmd == "DANMU_MSG") {
with(DanmakuMessage(jsonObject)) {
"${if (fansMedalInfo.isNotEmpty()) "[$fansMedalName $fansMedalLevel] " else ""}[UL$userLevel] $nickname: $message"
}
} else {
jsonObject.toString()
}
)
} 输出:
更多 注意, 关闭连接 job.cancel() 发送直播弹幕在直播间里发送弹幕也非常简单(必须先登陆) liveClient.sendMessage("我上我也行").await() 注意, 除了弹幕超长(普通用户为 20 个 Unicode 字符, 老爷, 会员可以额外加长)会导致抛出异常, 其他情况都会正常返回( 完全正常返回时(弹幕正确的被发送了), 返回内容中的 如果不为空字符串, 则表示不完全正常 例如返回内容的 其他情况诸如包含特殊字符, 包含不文明词语等均会导致不完全正常的返回. 正常返回时, 就算不完全正常, 客户端也会将这条弹幕显示到屏幕上, 如果不是完全正常的, 那么这条弹幕就只有自己能看见(刷新后也会消失). 需要额外判断返回的 LicenseGPL V3 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论