咨询热线0898-88889999
网站首页 关于傲世皇朝 傲世皇朝注册 傲世皇朝动态 傲世皇朝登录 傲世皇朝平台 傲世皇朝入口 傲世皇朝代理 联系我们
咨询热线
0898-88889999
地址:海南省海口市
邮箱:admin@youweb.com

傲世皇朝平台

当前位置: 首页 > 傲世皇朝平台

抓取抖音直播的评论数据的python代码 抓取直播间评论

发布时间:2024-04-22 14:01:55 点击量:


背景

因为想拿到一些知乎弹幕的数据 以及做一个直播播报机器人,所以最近在研究知乎直播的弹幕

分析

抓取比较简单,不多说了...都是正常的操作

但是 拿到的数据却很奇怪

为了演示方便,我们以 rest 接口示范,本质上和 websocket 接口是一样的。

我们以直播间 11529 为例子

拿取弹幕的接口是: https://www.zhihu.com/api/v4/drama/theaters/11529/recent-messages

抓取抖音直播的评论数据的python代码 抓取直播间评论_matlab弹幕抓取

直播间弹幕返回数据截图

可以看到弹幕数据应该在 messages 里面,但是数据好像经过了某种加密

js 大搜查

首先全局搜索 recent-messages,找到需要的 js 文件(这边也就是查找哪个 js 请求了拿弹幕的网址)

把 js 文件下载到本地格式化后,搜索 recent-messages

抓取抖音直播的评论数据的python代码 抓取直播间评论_搜索_02

搜索LOAD_RECENT_MESSAGES

找到了如何解析 message 的第一步 base64 解密

js 中 atob 函数解释[1]

抓取抖音直播的评论数据的python代码 抓取直播间评论_数据_03

并且 转换后的结果传给了函数 p

继续搜索p 往上搜索(记得搜索模式选择全词匹配与区分大小写) 要不然搜索结果太多了...

运气好 上面第一个就是

抓取抖音直播的评论数据的python代码 抓取直播间评论_数据_04

为了验证可以替换知乎 js 到你本地的 js

加两行console.log就行了

代码如下...

function p(e){

console.log("before:", e);

var t = d.EventMessage.decode(e),

n = t.eventCode,

r = t.event;

console.log("after:", t);

复制代码

可以发现这就是我们想要的

那么现在只要搞清楚 EventMessage.decode 这个方法干了啥就可以了...

然后搜到了具体的代码

抓取抖音直播的评论数据的python代码 抓取直播间评论_数据_05

就一步一步 debug,发现好像是某种编码规范?

难道是知乎自己定义的吗...

在这边搞了一周...还没有搞明白

大概说下 我迷惑的点在哪

抓取抖音直播的评论数据的python代码 抓取直播间评论_bash_06

如上这个 Uint8Array

先是对第一位 >>> 操作,判断这个 字节表示的是 什么含义

然后后面的 xxx 个字节表示具体的值,但是 xxx 个字节到底是多少个,是怎么区分的 我没有弄明白

特别是如下 这三个明明都是 int64,他们的字节长度却不一样

timestampMs 是 6 个字节

theaterId 是 2 个字节

dramaId 是 9 个字节

我拿个小本本一边 debug 一边记...(本来字段少的话,看多了是可以直接找到规律 这样解决的,但是其中一个字段event是个字典,有 40 个 key...

我看到代码的时候 就炸了...

抓取抖音直播的评论数据的python代码 抓取直播间评论_数据_07

所以我就想 算了 不了解它到底怎么实现的把,我直接吧这段 js 抠出来...然后搭个 nodejs 的服务得了

扣 js

扣的时候 还计较简单,除了这一句的s

e instanceof s || (e = s.create(e));

复制代码

抓取抖音直播的评论数据的python代码 抓取直播间评论_matlab弹幕抓取_08

这个 s 到这我就找不到它到底从哪来的了

所以我就只能 google.(搜了好多次)

抓取抖音直播的评论数据的python代码 抓取直播间评论_搜索_09

真是惊喜!发现竟然是protobuf[2]

所以这个所谓的加密 是一种通用的协议...

至此,问题就简单了

Protocol Buffers

官方的定义如下:

Protocol buffers 是一种与语言无关、与平台无关的可扩展机制,用于序列化结构化数据。

更多介绍可以去看protocol-buffers 官网[3]

下面的内容来自 Burpsuite 中 protobuf 数据流的解析[4]

Varint 编码

Protobuf 的二进制使用 Varint 编码。Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。

Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,比如 300,会用两个字节来表示:1010 1100 0000 0010。

下图演示了 protobuf 如何解析两个 bytes。注意到最终计算前将两个 byte 的位置相互交换过一次,这是因为 protobuf 字节序采用 little-endian 的方式。

抓取抖音直播的评论数据的python代码 抓取直播间评论_数据_10

所以我们上面那个疑惑解决了...

就是怎么确定某个字段到底应该几个字节(或者说现在能划分数据了)

数值类型

Protobuf 经序列化后以二进制数据流形式存储,这个数据流是一系列 key-Value 对。Key 用来标识具体的 Field,在解包的时候,Protobuf 根据 Key 就可以知道相应的 Value 应该对应于消息中的哪一个 Field。

Key 的定义如下:

(field_number << 3) | wire_type

Key 由两部分组成。第一部分是 field_number,比如消息 tutorial .Person 中 field name 的 field_number 为 1。第二部分为 wire_type。表示 Value 的传输类型。Wire Type 可能的类型如下表所示:

Type

Meaning

Used For

Varint

int32, int64, uint32, uint64, sint32, sint64, bool, enum

1

64-bit

fixed64, sfixed64, double

2

Length-delimi

string, bytes, embedded messages, packed repeated fields

3

Start group

Groups (deprecated)

4

End group

Groups (deprecated)

5

32-bit

fixed32, sfixed32, float

以数据流:08 96 01 为例分析计算 key-value 的值:

#!bash

08=0000 1000b

=> 000 1000b(去掉最高位)

=> field_num=0001b(中间4位), type=000(后3位)

=> field_num=1, type=0(即Varint)

96 01=1001 0110 0000 0001b

=> 001 0110 0000 0001b(去掉最高位)

=> 1 001 0110b(因为是little-endian)

=> 128+16+4+2=150

复制代码

最后得到的结构化数据为:

1:150

其中 1 表示为 field_num,150 为 value。

手动反序列化

抓取抖音直播的评论数据的python代码 抓取直播间评论_搜索_11

以上面例子中序列化后的二进制数据流进行反序列化分析:

#!bash

0A=0000 1010b=> field_num=1, type=2;

2E=0010 1110b=> value=46;

0A=0000 1010b=> field_num=1, type=2;

07=0000 0111b=> value=7;

复制代码

读取 7 个字符“Vincent”;

#!bash

10=0001 0000=> field_num=2, type=0;

09=0000 1001=> value=9;

1A=0001 1010=> field_num=3, type=2;

10=0001 0000=> value=16;

复制代码

读取 10 个字符“Vincent@test.com”;

#!bash

22=0010 0010=> field_num=4, type=2;

0F=0000 1111=> value=15;

0A=0000 1010=> field_num=1, type=2;

0B=0000 1011=> value=11;

复制代码

读取 11 个字符“15011111111”;

#!bash

10=0001 0000=> field_num=2, type=0;

02=0000 0010=> value=2;

复制代码

最后得到的结构化数据为:

#!bash

1 {

1: "Vincent"

2: 9

3: "Vincent@test.com"

4 {

1: "15011111111"

2: 2

}

}

复制代码

使用 protoc 反序列化

实现操作经常碰到较复杂、较长的流数据,手动分析确实麻烦,好在 protoc 加“decode_raw”参数可以解流数据,我实现了一个 python 脚本供使用:

import subprocess, sys

import json

import base64

def decode(data):

process = subprocess.Popen(

["protoc", "--decode_raw"],

stdin=subprocess.PIPE,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE,

)

output = error = None

try:

output, error = process.communicate(data)

except OSError:

pass

finally:

if process.poll() != 0:

process.wait()

return output

with open(sys.argv[1], "rb") as f:

data = f.read()

print('',decode(data))

复制代码

回到知乎直播

那么就先测试解析一条吧

import subprocess, sys

import json

import base64

def decode(data):

process = subprocess.Popen(

["protoc", "--decode_raw"],

stdin=subprocess.PIPE,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE,

)

output = error = None

try:

output, error = process.communicate(data)

except OSError:

pass

finally:

if process.poll() != 0:

process.wait()

return output

a1 = "CAESpgMKowMKhgMKIDQwYjQ3Y2NiZmM0NDc1YjAxOGE1YTQxN2UxY2Y5ODk3EhLlsI/pgI/mmI7niLHlvrfljY4aDGR1LXlhby0xMy04NiJBaHR0cHM6Ly9waWM0LnpoaW1nLmNvbS92Mi1kMDYxNjFiMWQzOWNkNjRlYmRhNDBmOWMwNjVhNmNhNV94cy5qcGcqswEIiVoSCemFkueqneeqnRgBIAAoJTABOpoBCAEQABpAaHR0cHM6Ly9waWMxLnpoaW1nLmNvbS92Mi0xZDEyNTg1YzdhOTY2MTNkM2JlZjQxMTcyY2Q4ZWYxNV9yLnBuZyJAaHR0cHM6Ly9waWM0LnpoaW1nLmNvbS92Mi1iMDM1ZWRkNTA3NjgwNzU3MmJkNGU3YTg5MjRjZTEzYl9yLnBuZyoHIzcyQkJGRioHIzAwODRGRjJHCAkQjGAaQGh0dHBzOi8vcGljNC56aGltZy5jb20vdjItODI1NTRlYzgzYmViMzJlOWVjNDQxNGY0YzYyMmFjMmNfci5wbmcQARoM5oiR5Lmf6KeJ5b6XIICA8cTaz6SEERiplO2Pki4giVoogKDrtNOUlYQRMhUxLTEyMjczOTE5NjY4NTU4Mzk3NDQ4AQ=="

message = base64.b64decode(a1)

print(decode(message))

复制代码

结果如下

Out[7]: b'1: 1 2 { 1 { 1 { 1: "40b47ccbfc4475b018a5a417e1cf9897" 2: "\\345\\260\\217\\351\\200\\217\\346\\230\\216\\347\\210\\261\\345

\\276\\267\\345\\215\\216" 3: "du-yao-13-86" 4: "https://pic4.zhimg.com/v2-d06161b1d39cd64ebda40f9c065a6ca5_xs.jpg" 5 { 1: 1152

9 2: "\\351\\205\\222\\347\\252\\235\\347\\252\\235" 3: 1 4: 0 5: 37 6: 1 7 { 1: 1

2: 0 3: "https://pic1.zhimg.com/v2-1d12585c7a96613d3bef41172cd8ef15_r.png" 4: "https://pic4.zhimg.com/v2-b035edd5076807572bd4e7a8924c

e13b_r.png" 5: "#72BBFF" 5: "#0084FF" } } 6 { 1: 9 2: 12300 3: "https://pic4.zhimg.com/v2-82554ec83beb32e9ec4414f4c622ac2c_r.png" } } 2: 1 3: "\\346\\210\\221\\344\\271\\237\\350\\247\\211\\345\\276\\227" 4: 1227391966855839744 } } 3: 1585413048873 4: 11529 5: 1227323967020912640 6: "1-1227391966855839744" 7: 1 '

复制代码

可以看到解析成功了,那么后面的工作就比较简单了...

只要按照知乎的 js 对应出某个位置的具体字段名字就好了...

最后看一个成功的截图

抓取抖音直播的评论数据的python代码 抓取直播间评论_matlab弹幕抓取_12

参考资料

[1]

js中atob函数解释: https://developer.mozilla.org/zh-CN/docs/Web/API/WindowBase64/atob

[2]

protobuf: https://github.com/protobufjs/protobuf.js

[3]

protocol-buffers官网: https://developers.google.com/protocol-buffers

[4]

Burpsuite中protobuf数据流的解析: https://wooyun.js.org/drops/Burpsuite%E4%B8%ADprotobuf%E6%95%B0%E6%8D%AE%E6%B5%81%E7%9A%84%E8%A7%A3%E6%9E%90.html

本文使用 mdnice 排版

平台注册入口