内容简介:从零开始仿写一个B站客户端之-抓包B站接口
从零开始仿写一个B站客户端之-抓包B站接口
从零开始仿写一个B站客户端之-使用ijkplayer打造一个通用的播放器
既然要仿写一个客户端,那么数据从哪儿来呢?刚开始打算用springboot自己写后端,然后去学吧,第一个hello world项目就运行不起了(;′⌒`),这不是明示我劝退吗。所以果断抓包B站接口,不需要花费时间写后端代码,还还不缺数据来源。
抓包 工具 使用的是 fiddler ,对手机上的B站客户端进行抓包。需要注意的是必须确保安装 fiddler 的电脑和手机在同一个局域网环境下 。
电脑上的配置
fiddler
安装好 fiddler 之后,在Tools->Options->Connections中这样配:
Fiddler listens on port是手机连接fiddler时的代理端口号,.Allow remote computers to connect是允许远程(手机端)发送请求。
配置Https:
点击ok之后,在手机上访问电脑ip+8888,电脑ip查看方式是在cmd中输入ipconfig:
手机上的配置
我这里的ip是192.168.0.134,手机端用自带的浏览器访问: http://192.168.0.134:8888下载证书并安装。点击最下面那个蓝色链接安装证书:
emmm...小米手机不能直接安装,需要从更多设置 ->系统安全->加密与凭据->从sd卡中安装证书
安装证书完成之后,需要修改wifi的网络,手动设置代理,代理服务器主机名为电脑的IP地址,代理端口为在fiddler里设置的端口号,保存后,fiddler将能够收到手机上的请求信息:
准备就绪,可以在手机上打开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 复制代码
前面的参数几乎都是固定的,除了最后的 roomid 和 ts 以及 sign ,如果有一个参数不正确,服务器就会返回下面的错误:
{
"code": -3,
"message": "API校验密匙错误",
"ttl": 1
}
复制代码
API校验密匙就是最后一个参数 sign ,它是通过前面的参数 排序 之后,加上 SecretKey 做md5生成的,其中 SecretKey 存放在了so库中,具体操作参考了@Misery_Dx的:
获取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客户端
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 腾讯 AI-Java 客户端 Taip 重大更新,所有接口均已接入
- 支付宝客户端架构解析:iOS 客户端启动性能优化初探
- 自己动手做数据库客户端: BashSQL开源数据库客户端
- 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
- 客户端HTTP缓存
- 简析移动客户端安全
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Android 源码设计模式解析与实战
何红辉、关爱民 / 人民邮电出版社 / 2015-11 / 79.00元
本书专门介绍Android源代码的设计模式,共26章,主要讲解面向对象的六大原则、主流的设计模式以及MVC和MVP模式。主要内容为:优化代码的首步、开闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特原则、单例模式、Builder模式、原型模式、工厂方法模式、抽象工厂模式、策略模式、状态模式、责任链模式、解释器模式、命令模式、观察者模式、备忘录模式、迭代器模式、模板方法模式、访问者模式、中介......一起来看看 《Android 源码设计模式解析与实战》 这本书的介绍吧!