原文:B 站直播间数据爬虫, 欢迎转载
项目地址:bilibili-live-crawlerphp
去年在 B 站发现一个后期超强的 UP 主:修仙不倒大小眼,专出 PDD 这样知名主播的吃鸡精彩集锦,涨粉超快。因而想怎么作这样的 UP,遇到的第一个问题即是素材,精彩时刻须要手动从直播录播中剪辑,很低效。html
我常常看直播,可是不多发弹幕和送礼物,只有在主播玩出很溜的操做或讲很好玩的事情时,才会发弹幕互动、送礼物支持,常常看直播的室友也是如此。git
基于这个用户习惯,不难推断出在直播间的弹幕高峰或礼物高峰期,主播应该作了些好玩的事情,好比吃到鸡了,或者全队被歼灭之类的…这些时刻均可以做为精彩时刻的素材。能写程序自动截取这些素材吗?答案是确定的。github
经过爬虫抓取 B 站直播间数据,找出弹幕激增的时间点,使用 FFmpeg 自动剪辑时间点先后的视频便可。算法
本文代码:GitHubsql
> bilibili-live-crawler $ tree -L 2 . ├── README.md ├── config.php # 配置文件:配置 FFmpeg 可执行文件的位置,录像的保存路径 ├── const.php # 常量文件:API 地址,定义数据库用户名和密码、弹幕激增的断定参数等 ├── crawler.php # 链接并抓取弹幕服务器的数据 ├── cut_words │ └── seg.php # 分词脚本:将弹幕作分词处理,可用于生成本次直播的词图 ├── db.sql # 数据存储 ├── edit.php # 剪辑脚本 ├── functions.php # 公用函数 └── visual_data.php # 直播数据可视化文件脚本
以 B 站欠王痒局长为例,进入他的 528 直播间,打开 Chrome 的开发者工具,看 Network 容易找出这些 API:shell
热门主播会有 2 个房间号:易识记的短房间号、原始长房间号,获取主播原始直播间信息的 API:数据库
Resquest: https://api.live.bilibili.com/room/v1/Room/room_init?id=528 Response: { "code": 0, "data": { "room_id": 5441, // 开通直播间时的原始房间号,后边会用到 "short_id": 528, // 短房间号 ... } }
直播间在加载时,会请求弹幕服务器的地址,便是咱们要去爬取数据的服务器:json
Request: https://api.live.bilibili.com/api/player?id=cid:5441 // 5441 即原始房间号 Response: ... <dm_port>2243</dm_port> <dm_server>broadcastlv.chat.bilibili.com</dm_server> ...
直播间会有 3~4 个视频推流地址,选用第一个主路线会更稳定:api
Request: https://api.live.bilibili.com/room/v1/Room/playUrl?cid=5441 Response: { "code": 0, "data": { "durl": [ { "order": 1, "length": 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站从新修改过了,不过应该是为了兼容,这种旧协议头还能用。
下边这个是打开进入直播间时,客户端请求弹幕服务器的请求协议头,响应协议头相似:
00000000 00 00 00 35 00 10 00 01 00 00 00 07 00 00 00 01 ...5.... ........ # 数据包长度 # 意义不明 #请求类型:7进入直播间 # 包类型,1是数据包 # 2是心跳包 00000010 7b 22 72 6f 6f 6d 69 64 22 3a 31 30 31 36 2c 22 {"roomid ":1016," 00000020 75 69 64 22 3a 31 35 35 39 37 33 36 38 35 37 32 uid":15597368572 00000030 38 31 36 30 7d 8160} # 请求的数据
进入直播间,打包生成链接服务器的协议头:
// $roomID 是直播间的长房间号 // $uid 是当前登陆用户的 uid,游客的是随机数 function packMsg($roomID, $uid) { $data = json_encode(['roomid' => $roomID, 'uid' => $uid]); // 大端字节序,使用参数 N (4字节) 和 n(2字节) 打包请求 // 占4字节的数据包长度:16字节协议头长度 + 请求数据长度 // 占2字节意义不明:00 10 // 占2字节意义不明:00 01 // 占4字节的请求类型:00 00 00 07 // 占4字节的包类型:00 00 00 01 return pack('NnnNN', 16 + strlen($data), 16, 1, 7, 1) . $data; }
服务返回的 JSON 数据包的协议头以下:
00000835 7b 22 69 6e 66 6f 22 3a 5b 5b 30 2c 31 2c 32 35 {"info": [[0,1,25 00000845 2c 31 36 37 37 37 32 31 35 2c 31 34 35 37 39 35 ,1677721 5,145795 ... 000008E5 5d 2c 22 63 6d 64 22 3a 22 44 41 4e 4d 55 5f 4d ],"cmd": "DANMU_M 000008F5 53 47 22 7d SG"}
解码响应的数据体:
function 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); switch ($resp['cmd']) { case 'DANMU_MSG': // 弹幕消息 // info[1] 弹幕内容 // info[2][1] 发送者昵称 echo $resp['info'][2][1] . " : " . $resp['info'][1] . PHP_EOL; break; case 'SEND_GIFT': // 直播间送礼物信息 $data = $resp['data']; // uname 发送者的昵称 // giftName 赠送的礼物名称 // unum 一次赠送的数量 // price 礼物的价值 echo $data['uname'] . ' 赠送' . $data['num'] . '份' . $data['giftName'] . PHP_EOL; break; case 'WELCOME': // 直播间欢迎信息 break; default: // 未知的消息类型 } } socket_close($socket); }
若是客户端出现忽然断网等异常状况,服务端依旧会继续推送数据,维护这种半打开的 TCP 链接将会浪费服务器的资源。客户端能够每隔一小段时间给服务端发送心跳包来保活,若是服务端必定超时时间内没收到某个客户端的心跳包,就主动断开链接。
B 站的弹幕服务器也有相似的机制,随便打开一个未开播的直播间,抓包将看到每隔 30s 左右会给服务端发送一个心跳包,协议头第四部分的值从 7 修改成 2 便可。若是不发送心跳包,弹幕服务器将在 1~2min 内主动断开链接。
// 发送心跳包 function sendHeartBeatPkg($socket) { // 包类型从数据包的 7 修改成心跳包的 2 $str = pack('NnnNN', 16, 16, 1, 2, 1); socket_write($socket, $str, strlen($str)); }
录播:直接使用 FFmpeg 保存推流地址的视频便可
剪辑:根据 每分钟 的弹幕数量变化状况,若是出现峰值,取峰值 先后的一分钟 做为精彩部分。
峰值的判断标准:
以上加粗的断定粒度、断定标准均可根据本身喜欢的主播修改,具体参考 edit.php 的实现。还能够为精彩时刻加上弹幕断定,好比分析是否有大量的 66六、23三、基本操做、学不来之类的词集中出现等等。
参考 结巴分词 的算法,可用于生成直播的词图、分析粉丝的习惯用语等等。我参考的教程:
开发遇到了两个难点
再看看人家斗鱼,有开放使用的 《斗鱼弹幕服务器第三方接入协议》,协议也不会修改,兴趣使然写了 B 站的爬虫,这种根据弹幕峰值剪辑视频的想法应用在斗鱼上,估计会更有价值 :)