B 站直播間數據爬蟲

原文: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    # 直播數據可視化文件腳本

準備 API

以 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 站官方 C# 版客戶端代碼,分析協議組成,感謝博主 lyyyuna
  • 分詞算法:參考的是結巴分詞的前綴詞典與動態規劃算法,算法能力待提高 :(

再看看人家鬥魚,有開放使用的 《鬥魚彈幕服務器第三方接入協議》,協議也不會修改,興趣使然寫了 B 站的爬蟲,這種根據彈幕峯值剪輯視頻的想法應用在鬥魚上,估計會更有價值 :)

相關文章
相關標籤/搜索