专栏首页未闻Code为了抓取弹幕,你需要知道的一些二进制数据常识

为了抓取弹幕,你需要知道的一些二进制数据常识

摄影:产品经理

春暖花开

文本不会讲具体某个网站的弹幕抓取方法。而是描述抓取到二进制的弹幕信息以后,如何进行处理。

不少直播网站会使用 websockets 来传输弹幕,当我们使用某种方式抓取到弹幕以后,你看到的弹幕可能是这样的:

b'\x00\x00\x00\x1a\x00\x10\x00\x01\x00\x00\x00\x08\x00\x00\x00\x01{"code":0}'
b'\x00\x00\x00\x14\x00\x10\x00\x01\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x19\xfd'
b'\x00\x00\x01\xed\x00\x10\x00\x02\x00\x00\x00\x05\x00\x00\x00\x00x\xda|R]k\xdc0\x10<(}\xe9S\x7f\xc3\x16\xfc\xa4\x9ce\xd9:\xeb\x1cLI\xda\x04B!\x94\x92\x06Z\x04B\x96t\xb1\x1a\x7f\x08Y\xf65\x84\xfc\xf7\xa2\xbb\x12(m#X\xa1\xdd\x19\xcd.\xcb\xacV\xaf\xde\xac\xde\xae\xe2y\x1d\xafGP\xbd\x86\n\xce>\xdc\\\xdd^\xdd|\x13\xe7g\xd7\xd7\x17_\xc4\xd7\xcf\x1f\xcfn.\xc4-\x01\x04Z\x06\t\xd5#X\rU^2\x04\xc1\x86\xce@\x05|.\x1b\xa2X\xc9gZ`\r\x08\xd4\xb8\x18\x0f\x15\x00\x82F\xaa\xfb;?\xceCToCpS\xc5S\x9eZ\xbcn\xf5\xd45k5\xf6<mv\x13O\xa5\nv\xb1\xe1\xe1\xc4u2\xf0t\n2X\xc5S\x82\xb3-\xde\xe2\x82\xa7\r5$\xc3f\xb7a\x86R\x85\x0b\xb2+p\xc9J\xac\ral[4<\xcd\x8a\xe5\xfb\\\xb6[\xf7i\xed\x86;@\xf0c\xee\x9d\x98}\xf7G\xef\xce.f\xdd\xd8\xce\xc68\x0e\xe0x\xda\x86\xbe;b\'\xd2\xb9\x13/\x87{5{o\x86\xc0S;h\xf3s\x1d\x19\xef\xed$"G\xb4\xb2\xdb\x89\xbdi\x16k\xf6u\x96\xb4\x0f\x8d\xb7\xfaX\x9dm\x9d!\x8a\x18u\xa8\xc4\x0e]^^\x90m\x8e0\xca1\xcap\x8cS\x82\x08\xca\xc9!\xfd\x1b\xc7\xa7\xc5\xcb\xf0\x06Q\xb4\xa1\x0em\xfe-N\x11E\xf4\xffp\xfe\xf2h\xe5\xcb\xe2\x89\x1cT;z1[]\x13\xb6a\x84\x16\x89\x9d\xc4`\xf6"nL\xa8q\x08\xd2\x0e\xc6\xd7Y"\xbd\x91b!\xc2\xea:\xc7\xe5s\xead\\\xea\xa1\x9a\x1c\xfe\x84\x07g\xea^N\xc1x\xe1\x8d\xec\x82\xed\x8d8\xb0\xdbq\xf6\xc9\xf3\xab\xce\xe0\xb7\xe7\x84\x1a\xbb1\x1a\xec\x1d;\xa7,+\xa3\xe5\xbaq2\xb2\x89~\xcc\xa2\xeb\x86\xc1\xf8\x832T\x05\x82\xbd\xb1wm\x80*c\x08\xa4\xd6\xe2\x88C\x85\x9f\x9e~\x05\x00\x00\xff\xff)b\xe55'

遇到这些二进制数据,如何把它解析为人眼能够看得懂的内容呢?

对于第一个bytes 型的数据,你可能会这样操作:

>>> data = b'\x00\x00\x00\x1a\x00\x10\x00\x01\x00\x00\x00\x08\x00\x00\x00\x01{"code":0}'
>>> data[16:]
b'{"code":0}'
>>> data[16:].decode()
'{"code":0}'

那第二条数据又应该怎么解析?第三条数据呢?第一条这个16是怎么来的呢?

为了解释这个问题,我们需要知道 Python 的struct 模块。这个模块可以使用Python的 bytes 型数据来表示 C 语言的结构体。

一般通过 websocket 传过来的弹幕,前面会有若干个字节作为头部数据,记录数据包的长度、头部长度、数据包的类型等等信息。

今天我们要作为例子的这个弹幕网站,它的弹幕头部格式如下:

I

H

H

I

I

包体长度

头部长度

包体数据类型

操作符

序列 id

这个头部对应的结构体为:

struct.Struct('>I2H2I')

其中,>表示这是一个大端序,二进制高位在左边。I表示无符号整型数字,占4个字节,H 表示无符号短整型,占用2个字节。2HHH的简化写法,类似的还有3I表示III以此类推。

什么叫做大端序和小端序呢?这是由 CPU 架构决定的一种二进制数据储存方式。我们现在的 X86电脑,是小端序。

有一个数字7,它的二进制数据为111只占用3位。但是当我们使用整型的时候,一般会使用4字节,也就是32位二进制位。于是数字7会写为:00000000 00000000 00000000 00000111。其中最左边的8位是高位。最右边的8位是低位。这就是大端序。

但数字7在我们的电脑上,真正储存为00000111 00000000 00000000 00000000。最左边是低位,最右边是高位。这就是小端序。

我们可以用struct模块来测试一下:

>>> struct.pack('>I', 7)
b'\x00\x00\x00\x07'
>>> struct.pack('<I', 7)
b'\x07\x00\x00\x00'

这里返回的结果是十六进制,\x07表示十六进制的7.

看到这里,可能有同学会问,数字7本来用3位就能表示,为什么要搞个4字节32位?这不是浪费吗?

这是因为,当我们提前定义好数据的类型以后,就可以提前知道数据的长度。例如两个整型数100和67,他们各占用4字节,于是总长度是8字节。现在我把这两个数字都改了,改成3999和6785,两个数字都变大了。但是由于没有超过4字节能表示的最大范围,所以这两个数字占用的空间仍然是8字节。

回到我们开头说的弹幕网站。通过技术手段,我知道了它的头部有5个部分,分别用1个4直接无符号整数、两个无符号短整数、2个无符号整数表示。

所以我们可以提前构造头部的结构体,并知道头部的长度:

>>> head = struct.Struct('>I2H2I')
>>> head.size
16

那么,对于第一种情况,我们先截取头部,并把它还原为数字:

>>> data = b'\x00\x00\x00\x1a\x00\x10\x00\x01\x00\x00\x00\x08\x00\x00\x00\x01{"code":0}'
>>> header = head.unpack_from(data[:head.size])
>>> header
(26, 16, 1, 8, 1)

返回的元组中,第一个元素26表示包体长度为26个字节(包含头部),第三个元素1表示数据类型为未压缩的版本。所以我们直接获取数据的第16-26字节即可:

>>> data = b'\x00\x00\x00\x1a\x00\x10\x00\x01\x00\x00\x00\x08\x00\x00\x00\x01{"code":0}'
>>> data[16: 26]
b'{"code":0}'

同理。我们来看一下第二段数据:

>>> data = b'\x00\x00\x00\x14\x00\x10\x00\x01\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x19\xfd'
>>> header = head.unpack_from(data)
>>> header
(20, 16, 1, 3, 1)
>>> value_bytes = data[16: 20]
>>> value_bytes
b'\x00\x00\x19\xfd'
>>> int_value = int.from_bytes(value_bytes, 'big')
>>> int_value
6653

对头部解包以后,可以知道整个包体长度是20,那么获取第16到20字节的数据。这个数据是被转为 bytes 型数据的整数,所以需要把它重新转回int 型。由于数据是大端储存,所以代码需要写为int.from_bytes(value_bytes, 'big').

这里为什么我知道需要把这个数据转成整数呢?这是因为头部里面第4位数字3表示这条消息是当前视频的热度,就是一个数字。

第三段就留做作业给大家来解决了。给一个提示:解析出头部以后,你会发现头部的第3位对应数字2,表示后面的数据是经过压缩的。你可以使用 Python 的zlib。decompress(data[16: 数据包长度])对它进行解压缩。解压缩以后,你会惊讶地发现本文是用哪个网站的弹幕数据来进行举例。

本文分享自微信公众号 - 未闻Code(itskingname),作者:kingname

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 一日一技:炸掉你的内存—— itertools.tee 的缺陷

    在上一篇文章中,我们讲到了,使用itertools.tee可以让一个生成器被多次完整遍历:

    青南
  • 一日一技:单机单节点 MongoDB 为什么删除数据后不释放空间?

    MongoDB 3.6以后,默认使用的储存引擎是 WiredTiger。这个引擎有一个特点,就是删除数据不释放空间。例如现在你的一个集合里面有10000000条...

    青南
  • 一日一技:在Python中创建临时文件用于记录临时数据

    当我们在做数据分析的时候,可能会由于数据量过大导致内存不足。如果我们没有条件使用更高配置的电脑,也没有办法优化数据,那么我们可以先把计算的中间值存放在一个文本文...

    青南
  • Redis 基于主从复制的 RCE 利用方式

    在2019年7月7日结束的WCTF2019 Final上,LC/BC的成员Pavel Toporkov在分享会上介绍了一种关于redis新版本的RCE利用方式[...

    Seebug漏洞平台
  • golang-101-hacks(13)——二维切片

    注:本文是对golang-101-hacks中文翻译。 Go支持多维切片,再此只对二维切片切片做介绍。日常生活中通常会使用到二维切片,而多维似乎并不多见。如果...

    羊羽shine
  • 翻译:使用红外传感器与Arduino进行简单动作与手势检测

    译注:昨天看 Adruino 的 Twitter 推了这篇项目,第一眼就觉得非常有趣,翻译给大家看看。文中的红外传感器比较高级,和淘宝上5块钱的那种只能输出0和...

    张高兴
  • MikroTik-SMB 测试之 Mutiny-Fuzzer

    Mutiny是由思科研究人员开发的一款基于变异的网络fuzz框架,其主要原理是通过从数据包(如pcap文件)中解析协议请求并生成一个.fuzzer文件,然后基于...

    信安之路
  • CVE-2020-0796,又是一场补丁攻坚战

    每年真正比较有影响力的漏洞编号,其实并不多,而这个CVE-2020-0796,就是我们在疫情之下全面返岗伊始,最值得去重视的一个。

    Bypass
  • C语言数组结合位运算实战-位移与查表

    在嵌入式项目开发中,LED灯的操作是一定要会的,也是基础中的基础,比如用51单片机写个跑马灯,这不简单嘛,定义一个数组把那8个跑马灯存起来,然后搞个for循环...

    morixinguan
  • 漏洞告之:SMBv3协议远程代码执行漏洞(附自查脚本)

    北京时间3月10日23时微软发布安全通告称Microsoft Server Message Block 3.1.1(SMBv3)协议在处理某些请求的方式中存在代...

    Aran

扫码关注云+社区

领取腾讯云代金券