本文最后更新于 大约 2 年前,文中所描述的信息可能已发生改变。
前言
本篇记录下载b站《地下交通站》弹幕的经过,地下交通站在21年初下架,我在20年末发现此番剧已经被隐藏,以防万一保存了弹幕的备份,结果这几天发现居然保存的弹幕文件全是乱码,尝试了几种恢复的方式依旧不行,无奈只能尝试研究b站弹幕的api,看能不能找到历史弹幕的接口尝试保存下来
实时弹幕接口
API接口来自仓库bilibili-API-collect,实时弹幕接口提供了一个proto文件,然而我之前并不太了解proto是什么,浅浅的理解为不是用python语言写的脚本,但是可以编译成python调用,so…
编译proto
仓库内提供了一个在线编译的网站protogen.marcgravell,只需要把proto代码复制然后选择python就可以生成python文件,需要注意的是要把编译后的文件发在一个后缀是*_pb2.py*的文件中才可以调用,我这里直接将其命名为bilibili_pb2.py
调用
仓库内很详细的写了请求方式和请求参数,唯一要说的是segment_index
,为1指的是获得前六分钟的弹幕,所以如果我想获取一个时常为48分钟视频的所有弹幕,则要在请求参数里设定segment_index的值从1到8,并将所有的response合在一起,后面的解析方式直接用的仓库作者给的示例:
def getDanmuk(oid, type=1, segment_index=1):
params = {
'oid': oid,
'type': type,
'segment_index': segment_index
}
cookies = {
'SESSDATA':'a050121a%2C1676791340%2C17a59%2A81'
}
resp = requests.get(url, params, cookies=cookies)
data = resp.content
danmaku_seg = Danmaku.DmSegMobileReply()
danmaku_seg.ParseFromString(data)
return danmaku_seg
在return的danmaku 中包含一个elems类,elems中的每个DanmakuElem就包含每个弹幕的各个参数,但如果想用在弹弹play这样的播放器中通常需要使用xml格式的弹幕文件
弹幕xml解析
一条弹幕示例:
<d p="0,1,25,16777215,1312863760,0,eff85771,42759017">前排占位置</d>
- 弹幕出现的时间,以秒为单位
- 弹幕的模式:1~3 滚动弹幕 4 底端弹幕 5 顶端弹幕 6 逆向弹幕 7 精准定位 8 高级弹幕
- 字号:12 非常小 16 特小 18 小 25 中 36 大 45 很大 64 特别大
- 字体的颜色:将 HTML 六位十六进制颜色转为十进制表示,例如 #FFFFFF 会被存储为 16777215
- Unix 时间戳,以毫秒为单位,基准时间为 1970-1-1 08:00:00
- 弹幕池:0 普通池 1 字幕池 2 特殊池(注:目前特殊池为高级弹幕专用)
- 发送者的 ID,用于「屏蔽此弹幕的发送者」功能
- 弹幕在弹幕数据库中 rowID,用于「历史弹幕」功能
将弹幕存储为xml格式
弹幕接口返回的是这种:
id: 711923911
progress: 47880
mode: 1
fontsize: 18
color: 10092288
midHash: "59417e95"
content: "世界第一电击公主殿下,遇到你是我一生最美好的风景!吾炮赛高,永生不离!唯我超电磁炮永世长存! "
ctime: 1418799826
weight: 6
idStr: "711923911"
attr: 1
需要注意的是其中ctime返回的是单位为秒的时间戳,progress返回的是单位为毫秒的时间,并且小数位为五位,所以需要转换:
def add_ms(timestamp, ms):
return timestamp*1000+ms
def change_ms_to_s(ms):
return '{:.5f}'.format(ms/1000)
本来我不了解如何在python中创建一个xml文件,便尝试让copilot帮我写一个出来,不知道是表述的有问题,还是xxx,copilot写出了一个简单粗暴的方法
def store_to_xml(elems, oid):
with open('{}.xml'.format(oid), 'a') as f:
for elem in elems:
f.write(' <d p="{},{},{},{},{},{},{},{}">'.format(change_ms_to_s(elem.progress), elem.mode, elem.fontsize, elem.color, add_ms(elem.ctime, elem.progress), elem.pool, elem.midHash, elem.id))
f.write(elem.content)
f.write('</d>\n')
可以,很粗暴,不过也能用就是了
除此之外xml还有开头和结尾,干脆也也以这种方式加上去算了
def downloadDanmuk(oid):
with open('{}.xml'.format(oid), 'w') as f:
f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
f.write('<i>\n')
f.write(' <chatserver>chat.bilibili.com</chatserver>\n')
f.write(' <chatid>{}</chatid>\n'.format(oid))
f.write(' <mission>0</mission>\n')
f.write(' <maxlimit>6000</maxlimit>\n')
f.write(' <state>0</state>\n')
f.write(' <real_name>0</real_name>\n')
f.write(' <source>DF</source>\n')
for i in range(1, 9):
danmaku = getDanmuk(oid, 1, segment_index=i)
store_to_xml(danmaku.elems, oid)
with open('{}.xml'.format(oid), 'a') as f:
f.write('</i>\n')
具体也有优化空间,不过我也懒得搞,反正就用一次的脚本,地下交通站有28集,所以调用28次就可以全部下载了
def main():
for i in range(10948917, 10948917+28):
danmaku = downloadDanmuk(i)
print('Downloaded {}'.format(i))
下载后的弹幕很全,平均一集的xml文件大小在1~2m左右
结尾
最后才发现明明已经下架的剧集,居然可以用实时弹幕接口下载,因为在使用之前的利用comment.bilibili.com+oid
的方法已经出现404, 不过仓库还提供了一个历史弹幕的接口,方法也差不多,既然已经下载好了,就不再折腾了
最后供上已经下好的弹幕:1
欢迎来到 弹幕保护计划QQ群:495877205