原文: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 站的爬蟲,這種根據彈幕峯值剪輯視頻的想法應用在鬥魚上,估計會更有價值 :)