從零開始仿寫一個B站客戶端之抓包介面

靚仔凌霄發表於2019-05-10

章節

從零開始仿寫一個B站客戶端之-編譯ijkplayer

從零開始仿寫一個B站客戶端之-抓包B站介面

從零開始仿寫一個B站客戶端之-使用ijkplayer打造一個通用的播放器

從零開始仿寫一個B站客戶端之-整體架構設計和網路請求封裝

既然要仿寫一個客戶端,那麼資料從哪兒來呢?剛開始打算用springboot自己寫後端,然後去學吧,第一個hello world專案就執行不起了(;′⌒`),這不是明示我勸退嗎。所以果斷抓包B站介面,不需要花費時間寫後端程式碼,還還不缺資料來源。

抓包工具使用的是fiddler,對手機上的B站客戶端進行抓包。需要注意的是必須確保安裝fiddler的電腦和手機在同一個區域網環境下 。

電腦上的配置

  1. 配置fiddler代理埠號
  2. 配置https,也可以不配,目前抓包出來的B站介面都是http的,以防萬一還是配上吧。

安裝好fiddler之後,在Tools->Options->Connections中這樣配:

從零開始仿寫一個B站客戶端之抓包介面

從零開始仿寫一個B站客戶端之抓包介面

Fiddler listens on port是手機連線fiddler時的代理埠號,.Allow remote computers to connect是允許遠端(手機端)傳送請求。

配置Https:

從零開始仿寫一個B站客戶端之抓包介面

點選ok之後,在手機上訪問電腦ip+8888,電腦ip檢視方式是在cmd中輸入ipconfig:

從零開始仿寫一個B站客戶端之抓包介面

手機上的配置

我這裡的ip是192.168.0.134,手機端用自帶的瀏覽器訪問: http://192.168.0.134:8888下載證書並安裝。點選最下面那個藍色連結安裝證書:

從零開始仿寫一個B站客戶端之抓包介面

emmm...小米手機不能直接安裝,需要從更多設定 ->系統安全->加密與憑據->從sd卡中安裝證書

安裝證書完成之後,需要修改wifi的網路,手動設定代理,代理伺服器主機名為電腦的IP地址,代理埠為在fiddler裡設定的埠號,儲存後,fiddler將能夠收到手機上的請求資訊:

從零開始仿寫一個B站客戶端之抓包介面

準備就緒,可以在手機上開啟B站客戶端開始抓包了:

從零開始仿寫一個B站客戶端之抓包介面

介面分析

以直播up主的粉絲榜介面為例,抓到的介面是這個樣子的:

http://api.live.bilibili.com/rankdb/v2/RoomRank/mobileMedalRank?actionKey=appkey&appkey=1d8b6e7d45233436&build=5400000&channel=bilibiil140&device=android&mobi_app=android&page=1&platform=android&roomid=2910685&ruid=33588706&ts=1556159005&sign=fdc0eb4340508ad9a62d1a27146a4183
複製程式碼

這太長了,可以剔除一些無用資訊,變為下面這樣:

http://api.live.bilibili.com/rankdb/v2/RoomRank/mobileMedalRank?page=1&roomid=2910685&ruid=33588706
複製程式碼

其中page表示第一頁,roomid表示房間號,ruid表示up主的uid。

有些介面是可以剔除的,但是有些介面是必須要一家人整整齊齊的,比如獲取直播up主的uid資訊就需要全部引數:

http://api.live.bilibili.com/xlive/app-room/v1/index/getInfoByRoom?actionKey=appkey&appkey=1d8b6e7d45233436&build=5400000&channel=bilibiil140&device=android&mobi_app=android&platform=android&room_id=2910685&ts=1556157467&sign=811b018c9e54efad87e4ec16a76cd111
複製程式碼

前面的引數幾乎都是固定的,除了最後的roomidts以及sign,如果有一個引數不正確,伺服器就會返回下面的錯誤:

{
	"code": -3,
	"message": "API校驗密匙錯誤",
	"ttl": 1
}
複製程式碼

API校驗密匙就是最後一個引數sign,它是通過前面的引數排序之後,加上SecretKey 做md5生成的,其中SecretKey存放在了so庫中,具體操作參考了@Misery_Dx的:

仿B站Android客戶端系列(啟動篇)

獲取sign的程式碼:

fun getSign(map: Map<String, Any>): String {
        //拼接引數(按順序) + SecretKey
        val orignSign = getUrlParamsByMap(map) + SECRET_KEY
        //進行MD5加密
        var sign = ""
        try {
            sign = MD5Util.getMD5(orignSign).trim()
            Log.i(TAG, "加密後的sign: $sign")
        } catch (e: NoSuchAlgorithmException) {
            Log.e(TAG, "sign encryption failed: ${e.printStackTrace()}")
        }
        return sign
    }

/**
     * 將map轉換成url引數
     * @param map
     * @return
     */
    fun getUrlParamsByMap(map: Map<String, Any>): String {
        var params =  StringBuffer()
        val it = map.iterator()
        while (it.hasNext()) {
            val str = it.next()
            params.append(str.key)
            params.append("=")
            params.append(str.value)
            if (it.hasNext()) {
                params.append("&")
            }
        }
        return params.toString()
    }
複製程式碼

能獲取到sign,大部分的問題就解決了。

直播彈幕的獲取參考@lovelyyoshino直播彈幕 WebSocket 協議,嗯。。。沒搞定,不知道是我傳送的封包資料有問題還是B站直播彈幕協議變了,如果有大佬能搞定,希望能不吝賜教,感謝感謝~。

這是傳送資料封包的方法:

/**
     * @param cmd 命令
     * @param data 資料包
     */
    private fun sendCmd(cmd: Int, data: ByteArray, webSocket: WebSocket){
        var buffer = ByteBuffer.allocate(16 + data.size)
        buffer.order(ByteOrder.BIG_ENDIAN)  //位元組序為大端模式
        buffer.putInt(16 + data.size)
        buffer.putShort(16)  //頭部長度
        buffer.putShort(1)  //協議版本,目前是1
        buffer.putInt(cmd)  //操作碼(封包型別)
        buffer.putInt(1)  //sequence,可以取常數1
        buffer.put(data)
        webSocket.send(ByteString.of(buffer))
    }
複製程式碼

這是使用okhttp3自帶的websocket實現的加入房間:

private var webSocket: okhttp3.WebSocket? =null
    fun joinRoom(roomId:Int){
        var client = OkHttpClient.Builder().build()
        var request = Request
            .Builder()
            .url(GlobalProperties.LIVE_DANMAKU_URL)
            .build()
        webSocket = client.newWebSocket(request,object : WebSocketListener(){
            override fun onOpen(webSocket: okhttp3.WebSocket, response: Response) {
                super.onOpen(webSocket, response)
                var inRoomMessage = JSONObject()
                inRoomMessage.put("clientver","1.6.3")
                inRoomMessage.put("platform","web")
                inRoomMessage.put("protover",2)
                inRoomMessage.put("roomid",roomId)  //必填
                inRoomMessage.put("type",2)
                var bytes = inRoomMessage.toString().toByteArray(Charsets.UTF_8)
                sendCmd(7, bytes, webSocket)
                Log.d(TAG,"websocket連線成功,傳送進房訊息$inRoomMessage")
            }

            override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
                super.onMessage(webSocket, bytes)
                Log.d(TAG,"websocket接收訊息$bytes")
            }
            override fun onClosed(webSocket: okhttp3.WebSocket, code: Int, reason: String) {
                super.onClosed(webSocket, code, reason)
                Log.d(TAG,"websocket斷開連線")
                exitRoom()
            }

            override fun onFailure(webSocket: okhttp3.WebSocket, t: Throwable, response: Response?) {
                super.onFailure(webSocket, t, response)
                Log.d(TAG,"websocket連線失敗: $response , throw: $t")
                exitRoom()
            }
        })
    }
複製程式碼

第一次連線成功之後,傳送進房訊息,然後連線就立即斷開了,應該是我傳送的資料不對,才導致服務端主動斷開連線的。

目前暫時是使用直播歷史評論抓取,而不是實時的:

http://api.live.bilibili.com/xlive/app-room/v1/dM/gethistory?room_id=2910685
複製程式碼

直播彈幕的獲取暫時就放後面再說了~

目前我抓取的介面都放在GlobalProperties這個類裡面,不想自己抓包的同學可以去這裡看:

專案地址:仿BiliBili客戶端

相關文章