原文:B站直播间数据爬虫,欢迎转载项目地址:bilibili-live-crawlerPDD等知名主播的吃鸡亮点发布,关注人数超级快。所以当我想做这样一个UP的时候,遇到的第一个问题就是材质。精彩瞬间需要从直播录像中手动剪辑,效率很低。用户习惯我经常看直播,很少发弹幕送礼物。只有当主播耍花样或者说有趣的事情的时候,我才会发弹幕互动,送礼物支持。经常看直播的室友也是这样。根据这个用户习惯,不难推断,在直播间的弹幕高峰期或者礼物高峰期,主播应该会做一些有趣的事情,比如吃鸡,或者全军覆没……这些时刻可以用作重要时刻材料。你能写个程序自动截取这些素材吗?答案是肯定的。实现效果弹幕抓取数据统计基于弹幕和礼物高峰生成的精彩片段,实现思路是利用爬虫抓取B站直播间的数据,找出弹幕出现的时间点画面激增,使用FFmpeg自动剪辑时间点前后的视频。.本文代码:GitHub>bilibili-live-crawler$tree-L2.├──README.md├──config.php#配置文件:配置FFmpeg可执行文件所在位置,录像保存路径├──const.php#常量文件:API地址,定义数据库用户名和密码,弹幕暴涨判断参数等├──crawler.php#连接并从弹幕服务器抓取数据├──cut_words│└──seg.php#分词脚本:对弹幕进行分词,可用于生成本次直播的词图├──db.sql#数据存储├──edit.php#剪辑脚本├──functions.php#公共函数└──visual_data.php#直播数据可视化文件脚本准备API以B站站长为例,进入他的528直播间,打开Chrome的开发者工具,看看Network,很容易找出这些API:有将是直播间原始信息中的2位人气主播房号:好记的简短房号,原始长房号,获取主播原始直播房间信息的API:Request:https://api.live.bilibili.com/room/v1/Room/room_init?id=528Response:{"code":0,"data":{"room_id":5441,//开直播时的原房间号,后面会用到"short_id":528,//短房间号...}}弹幕服务器信息直播时房间加载完毕,会请求弹幕服务器的地址,也就是我们要爬取数据的服务器:Request:https://api.live.bilibili.com/api/player?id=cid:5441//5441为原房号Response:...2243broadcastlv.chat.bilibili.com...直播房间视频流中会有3~4个地址,选择第一个主路由会更稳定:Request:https://api.live.bilibili.com/room/v1/Room/playUrl?cid=5441Response:{"code":0,"data":{“durl”:[{“顺序”:1,“长度”:0,“url”:“https://bvc.live-play.acgvideo.com/live-bvc/671471/live_322892_3999292.flv?wsSecret=55083259fbc34c4227691ca0feb9c4b8&wsTime=1522465545"//flv视频格式的推送地址},...}协议分析B站和斗鱼一样,自己设计了协议头来传输直播数据。需要使用Wireshark抓包分析协议细节,以检测爬虫请求伪装成浏览器请求,连接弹幕服务器爬取直播间数据。找出弹幕服务器的IP地址:211.159.194.115查看请求弹幕服务器的数据包:ip.addr==211.159.194.115前三个数据包是我(10.0.1.34)和弹幕服务器(211.159).194.115)三次握手包建立TCP连接。请求的打包解码参考了2016.3的博客:B站直播弹幕协议详解,现在抓包的协议头和博客里的不一样。B站重新修改了一下,不过应该是为了兼容吧。这个旧协议标头仍然可用。请求协议头下部是客户端打开直播间时请求弹幕服务器的请求协议头,响应协议头类似:0000000000000035001000010000000700000001...5。......#数据包长度#未知含义#请求类型:7进入直播间#数据包类型,1为数据包#2为心跳包000000107b22726f6f6d6964223a313031362c22{"roomid":1016,"00000020756964223a3135353937333638353732uid":15597368572000000303831363160}7输入直播间要求打包生成的数据连接服务器的协议头://$roomID为直播房间的长房间号//$uid为当前登录用户的uid,访客的为随机数functionpackMsg($roomID,$uid){$data=json_encode(['roomid'=>$roomID,'uid'=>$uid]);//大端,使用参数N(4字节)和n(2字节)打包请求//占用4字节数据包长度:16字节协议头长度+请求数据长度//2的含义bytes不清楚:0010//2bytes含义不清楚:0001//4bytes的请求类型:00000007//占4bytes的包类型:00000001returnpack('NnnNN',16+strlen($数据),16,1,7,1)。$data;}响应数据服务返回的JSON包协议头如下:000008357b22696e666f223a5b5b302c312c3235{"info":[[0,1,25000008452c31363737373231352c313435373935,16777215,145795...000008E55d2c22636d64223a2244414e4d555f4d],"cmd":"DANMU_M002308F7解码数据}响应主体:函数decodeMessage($socket){while(socket_last_error($socket)){while($out=socket_read($socket,16)){$res=@unpack('N',$out);if($res[1]!=16){break;}}$message=@socket_read($socket,$res[1]-16);$resp=json_decode($message,true);开关($resp['cmd']){case'DANMU_MSG'://弹幕消息//info[1]弹幕内容//info[2][1]发件人昵称echo$resp['info'][2][1].":".$resp['info'][1].PHP_EOL;中断;case'SEND_GIFT'://直播间礼物信息$data=$resp['data'];//uname发送者的昵称//giftName礼物的名称//unum礼物的数量//price礼物的价值echo$data['uname'].'礼物'。$数据['数量']。'分享'。$data['giftName'].PHP_EOL;休息;case'WELCOME'://欢迎消息break;default://Unknownmessagetype}}socket_close($socket);}心跳包如果客户端突然断开等异常情况,服务端会继续推送数据,保持这种半开的TCP连接会浪费服务端的资源客户端可以每隔一小段时间向服务器发送心跳包以保持存活。如果服务器在一定的超时时间内没有收到某个客户端的心跳包,就会主动断开连接。B站的弹幕服务器也有类似的机制。随便打开一个没有播过的直播间,抓包看到每隔30s左右就会向服务器发送一个心跳包。协议头第四部分的值由7改为2即可。如果没有发送心跳包,弹幕服务器会在1~2分钟内主动断开连接。//发送心跳包functionsendHeartBeatPkg($socket){//将包类型从数据包7改为心跳包2$str=pack('NnnNN',16,16,1,2,1);socket_write($socket,$str,strlen($str));}记录编辑精彩瞬间录播:直接用FFmpeg保存推流地址的视频,然后编辑:根据每条弹幕数量的变化minute,如果有峰值,取峰值前后一分钟视为亮点。判断峰值的标准:对于挠痒痒导演这样的大主播:直播间人多,玩这种闹剧的操作弹幕会暴涨很多,比如3倍前一分钟。对于小主播:一般人少,弹幕数量波动不大,有精彩操作时涨幅也不大。上面加粗的判断粒度和判断标准可以根据自己喜欢的主播进行修改。具体可以参考edit.php的实现。还可以针对精彩瞬间加入弹幕判断,比如分析是否有大量的666、233等单词,基本操作,学不会的东西。弹幕分析是指口吃分词的算法,可以用来生成直播词图,分析粉丝的成语等等。教程我参考:口吃分词1——口吃分词系统介绍口吃分词2——基于前缀字典和动态规划实现分词总结开发遇到两个难点协议头:逆向官方C#版客户端代码B站参考博客,分析协议组成,感谢博主lyyyuna分词算法:参考的是口吃分词的前缀字典和动态规划算法,算法能力有待提高:(看斗鱼,有开放使用《斗鱼弹幕服务器第三方接入协议》,协议不会修改,出于兴趣,我写了B站的爬虫。这种根据巅峰弹幕剪辑视频的思路应用到斗鱼上,估计是会更有价值:)