仅供学习使用,请勿用于商业或违法用途,否则产生的一切后果由使用者自负。
bilibili互动视频分析展示
本文示范的视频为【喜羊羊与灰太狼互动游戏】重启营救奇猫国之路,奇怪的剧情增加了!
Python所需的库:
- requets(可通过pip安装)
- json(可通过pip安装)
- graphviz(通过pip安装后还需下载安装graphvizr软件并配置环境变量)
互动视频分析
分析互动视频逻辑获取方式
按F12打开开发者工具,然后刷新视频页面,在开发者工具中选择网络(Network),逐个检查请求和响应,发现其中一个响应中包含了选项信息
展开后可看到详细的选项信息
在视频中点击一个选项后再次检查网络选项卡中,可再次看到类似名称的请求
观察请求参数可以发现有这些东西
由此可知edge_id是视频的标识,每个选项都指向一个新的edge_id,因此可以确定使用递归来遍历每种选择路径,同时要考虑到可能会有的重复情况(即出现重复的edge_id)。
当视频结束时,最后一个edgeinfo的请求是这样的。
与之前的比较可以观察到edges里没有questions了,所以可以以此作为视频结束的判断。
保存互动视频逻辑
互动视频逻辑的保存这里使用了字典,键为当前的edge_id,值为【当前视频片段的标题、(选项所指向的edge_id、选项名称)】。
代码如下:
zidian={}
def fenzhi(id=default_edge_id):
res=requests.get(url3%id) # 这里不需要heades也能正常响应
data=json.loads(res.text)['data']
if id=="":
id=data['edge_id']
title=data['title']
global zidian
if 'questions' not in data['edges'].keys():
zidian[id]=[title,'结束']
print('结束')
else:
ch=data['edges']['questions'][0]['choices']
if id not in zidian.keys():
zidian[id]=[title]
for i in ch:
print(i['id'],i['option'])
zidian[id].append((i['id'],i['option']))
fenzhi(i['id'])
else:
print('重复/循环')
从BV号获取edgeinfo的网址
将edgeinfo的请求参数一个个去除并尝试请求。
之后发现bvid和graph_version是必要的,edge_id如果没有就会返回开始界面的视频片段信息。
因此查看edgeinfo之前的请求,找到能获取graph_version的请求。
通过v2这个请求可以获取到graph_version,同理,再次观察v2的请求参数,并逐个去除后测试,发现aid不是必要的。
再次寻找获取cid的请求。
发现pagelist这个请求可以直接通过BV号获取cid,至此,目标已完成。
BV号 -> cid -> graph_version -> edge_info开始递归
def main(bvid):
url1="https://api.bilibili.com/x/player/pagelist?bvid={}&jsonp=jsonp".format(bvid)
url2="https://api.bilibili.com/x/player/v2?cid=%s&bvid={}".format(bvid)
cid=json.loads(requests.get(url1).text)["data"][0]["cid"]
graph_version=json.loads(requests.get(url2%cid).text)["data"]["interaction"]["graph_version"]
global url3
url3=url3.format(bvid,graph_version)
fenzhi()
with open(filename,'w',encoding='utf-8') as f:
f.write(str(zidian))
分析部分完整代码
import requests,json
###################
bvid=""
graph_version=""
buvid="" # buvid是cookies里的,不是必要的
default_edge_id=""
filename=r'互动视频.txt'
url3="https://api.bilibili.com/x/stein/edgeinfo_v2?bvid={}&edge_id=%s&graph_version={}&platform=pc&portal=0&screen=0&choices="
###################
zidian={}
def fenzhi(id=default_edge_id):
res=requests.get(url3%id) # 这里不需要heades也能正常响应
data=json.loads(res.text)['data']
if id=="":
id=data['edge_id']
title=data['title']
global zidian
if 'questions' not in data['edges'].keys():
zidian[id]=[title,'结束']
print('结束')
else:
ch=data['edges']['questions'][0]['choices']
if id not in zidian.keys():
zidian[id]=[title]
for i in ch:
print(i['id'],i['option'])
zidian[id].append((i['id'],i['option']))
fenzhi(i['id'])
else:
print('重复/循环')
def main(bvid):
url1="https://api.bilibili.com/x/player/pagelist?bvid={}&jsonp=jsonp".format(bvid)
url2="https://api.bilibili.com/x/player/v2?cid=%s&bvid={}".format(bvid)
cid=json.loads(requests.get(url1).text)["data"][0]["cid"]
graph_version=json.loads(requests.get(url2%cid).text)["data"]["interaction"]["graph_version"]
global url3
url3=url3.format(bvid,graph_version)
fenzhi()
with open(filename,'w',encoding='utf-8') as f:
f.write(str(zidian))
if __name__=='__main__':
main(bvid)
互动视频展示
由于每个视频片段有各自的标题,每个选项也有名称,因此都应出现在最终的图中,这里将视频片段的标题用椭圆形的框,选项名称用圆角矩形的框,刚开始的视频片段标题用蓝色的框,结束用红色的框。
之后根据字典中保存的内容,遍历并添加节点和边。
展示部分完整代码
from graphviz import Digraph
import json
############
name="互动视频展示"
filename=r'互动视频.txt'
############
def B2Q(uchar): # 有些视频的剧情里的描述带有英文冒号等,保存成gv文件没问题,但是之后解析生成图片会出现问题。
"""单个字符 半角转全角
参考:https://blog.csdn.net/huanghaocs/article/details/90384163"""
inside_code = ord(uchar)
if inside_code < 0x0020 or inside_code > 0x7e: # 不是半角字符就返回原来的字符
return uchar
if inside_code == 0x0020: # 除了空格其他的全角半角的公式为: 半角 = 全角 - 0xfee0
inside_code = 0x3000
else:
inside_code += 0xfee0
return chr(inside_code)
def main():
g=Digraph(name,'comment',None,None,'png',None,"UTF-8",
{'rankdir':'TB'},
{'color':'black','fontcolor':'black','fontname':'Sans','fontsize':'12','style':'rounded','shape':'box'},
{'color':'#999999','fontcolor':'#888888','fontsize':'10','fontname':'WeiRuanYaHei'},None,False)
with open(filename,encoding='utf-8') as f:
zidian=eval(f.read())
start_name=list(zidian.values())[0][0]
full_angle="".join(map(B2Q,start_name)) # 把start_name中的半角转为全角防止解析错误
g.node(full_angle,start_name,shape='ellipse',fontsize='30',color='blue')
for k,v in zidian.items():
full_angle="".join(map(B2Q,v[0])) # 把v[0]中的半角转为全角防止解析错误
g.node(full_angle,v[0],shape='ellipse') # 新建剧情
for i in v[1:]:
if i=='结束': # 剧情指向结束
g.node(str(k)+'结束','结束',shape='circle',color='red')
g.edge(full_angle,str(k)+'结束')
else: # 剧情指向选项
g.node(str(i[0]),i[1],shape='box') # 新建选项
g.edge(full_angle,str(i[0]))
start_id=list(zidian.keys())[0]
for k,v in zidian.items(): # 选项指向剧情
if k!=start_id:
full_angle="".join(map(B2Q,v[0])) # 把v[0]中的半角转为全角防止解析错误
g.edge(str(k),full_angle)
g.view()
if __name__=='__main__':
main()
结合分析部分和展示部分
之后只需要封装和调用就行了。
import 互动视频分析
import 互动视频展示
import sys
if len(sys.argv) > 1:
bvid=sys.argv[1]
else:
bvid=input("输入视频的BV号(带BV):")
互动视频分析.main(bvid)
print("***已分析完,即将生成图片。")
互动视频展示.main()
完整输入和输出展示
输入视频的BV号(带BV):BV1BK4y1e7cR
11914189 A 决定前往奇猫国!
11914196 A 让沸羊羊也当一回主角
11914140 A 成功溜走~赶紧赶路吧!
11914191 A 美羊羊忍不住了!
11914163 A 上车
11914182 A 听一下喜羊羊的悄悄话:哔 ——
11914194 A 进入皇宫
11914185 A 灰太狼
11914164 A 88,先走了
11914154 A 失败了~
11914189 A 决定前往奇猫国!
重复/循环
11914190 B 请皓月吃碗面再说~
11914146 A 给公主再来两碗!
结束
11914147 B 带皓月公主去旅行散散心
11914148 A 打开大门
11914159 A 让沸羊羊当一回主角
11914140 A 成功溜走~赶紧赶路吧!
重复/循环
11914160 B 这次看懒羊羊的吧
11914204 A 先尝一口什么味道嘛
11914206 A 被大老鼠送回去青青草原啦!
11914189 A 决定前往奇猫国!
重复/循环
11914190 B 请皓月吃碗面再说~
重复/循环
11914205 B 莫非是拍在自己头上?
11914145 A 成功打败“大老鼠”,赶紧赶路吧!
11914191 A 美羊羊忍不住了!
重复/循环
11914192 B 算了,还是先上车吧!
11914141 A 听一下喜羊羊的悄悄话..
11914194 A 进入皇宫
重复/循环
11914142 B 喜羊羊突然狂叫!
11914167 A 向左跑
11914202 A 向左跑
11914185 A 灰太狼
重复/循环
11914186 B 喜羊羊
11914199 A 觉得喜羊羊是卧底?
11914201 A 回去增加情感
11914189 A 决定前往奇猫国!
重复/循环
11914190 B 请皓月吃碗面再说~
重复/循环
11914200 B 相信喜羊羊不是卧底?
11914156 A 明日伸出双手,准备...
11914174 A 啊啊,好强!!
结束
11914157 B 这是什么招?迷惑!
结束
11914187 C 明日
11914149 A 明日赶紧伸出双手
11914195 A 莫得你不听~
结束
11914150 B 这是什么招?一脸问号?
结束
11914188 D 沸羊羊
11914175 A 捉住福来!
11914166 A 被遗忘的福来还在等车呢...那卧底是谁?~
11914185 A 灰太狼
重复/循环
11914186 B 喜羊羊
重复/循环
11914187 C 明日
重复/循环
11914188 D 沸羊羊
重复/循环
11914168 B 向右跑
11914151 A 向右跑
11914185 A 灰太狼
重复/循环
11914186 B 喜羊羊
重复/循环
11914187 C 明日
重复/循环
11914188 D 沸羊羊
重复/循环
11914143 C 别问我, 问灰太狼嘛!
11914144 A 无情拖走...
11914185 A 灰太狼
重复/循环
11914186 B 喜羊羊
重复/循环
11914187 C 明日
重复/循环
11914188 D 沸羊羊
重复/循环
11914193 C 万不得已,别这样嘛,美羊羊~
11914203 A 和美羊羊一起飞的去~
11914176 A 沸羊羊
11914175 A 捉住福来!
重复/循环
11914177 B 我不信!
11914149 A 明日赶紧伸出双手
重复/循环
11914150 B 这是什么招?一脸问号?
结束
11914178 C 喜羊羊
11914199 A 觉得喜羊羊是卧底?
重复/循环
11914200 B 相信喜羊羊不是卧底?
重复/循环
11914179 D 灰太狼
11914164 A 88,先走了
重复/循环
11914165 B 我留下来!
11914155 A 让我们一起对抗黑暗吧!
11914149 A 明日赶紧伸出双手
重复/循环
11914150 B 这是什么招?一脸问号?
结束
11914161 C 还是喜羊羊靠谱点
11914169 A 掏出那支神奇的...
11914152 A 围观一下
11914180 A 快回去青青草原,准备好再来吧!
11914158 A 回去青青草原
11914189 A 决定前往奇猫国!
重复/循环
11914190 B 请皓月吃碗面再说~
重复/循环
11914181 B 我不信,我要再看一次!
11914152 A 围观一下
重复/循环
11914153 B 受不了了
11914172 A 哇!村长太厉害了吧!我们赶紧溜喽!
11914191 A 美羊羊忍不住了!
重复/循环
11914192 B 算了,还是先上车吧!
重复/循环
11914193 C 万不得已,别这样嘛,美羊羊~
重复/循环
11914153 B 受不了了
重复/循环
11914170 B 既然躲不过了,那就...
11914173 A 成功打败“大老鼠”,抓紧时间赶路吧!
11914191 A 美羊羊忍不住了!
重复/循环
11914192 B 算了,还是先上车吧!
重复/循环
11914193 C 万不得已,别这样嘛,美羊羊~
重复/循环
11914171 C 请求外援!
11914162 A 哇!村长太厉害了吧!我们赶紧溜喽!
11914191 A 美羊羊忍不住了!
重复/循环
11914192 B 算了,还是先上车吧!
重复/循环
11914193 C 万不得已,别这样嘛,美羊羊~
重复/循环
11914165 B 我留下来!
重复/循环
11914186 B 喜羊羊
重复/循环
11914187 C 明日
重复/循环
11914188 D 沸羊羊
重复/循环
11914183 B 别问我,问一下另一位男主角嘛~
11914144 A 无情拖走...
重复/循环
11914184 C 喜羊羊突然狂叫!
11914167 A 向左跑
重复/循环
11914168 B 向右跑
重复/循环
11914192 B 算了,还是先上车吧!
重复/循环
11914193 C 万不得已,别这样嘛,美羊羊~
重复/循环
11914197 B 这次看懒羊羊的吧!
11914204 A 先尝一口什么味道嘛
重复/循环
11914205 B 莫非是拍在自己头上?
重复/循环
11914198 C 还是喜羊羊来靠谱点
11914169 A 掏出那支神奇的...
重复/循环
11914170 B 既然躲不过了,那就...
重复/循环
11914171 C 请求外援!
重复/循环
11914190 B 请皓月吃碗面再说~
重复/循环
***已分析完,即将生成图片。
>>>