有什么问题欢迎大家加QQ群:565712652进行讨论!

用Python爬取最右APP数据(段子和视频弹幕)

Python爬虫 Jason zhou 4944℃ 8评论

今天我要给大家介绍的是用Python爬取最右APP手机端的数据,主要是最右APP上的段子信息和视频弹幕。由于是APP数据抓取,因此我们需要先使用抓包工具进行抓包分析,之后在进行接口的解密以及数据的爬取,接下来让我详细分析整个用Python爬取最右APP数据的全部过程。

一,用Charles进行抓包分析

Charles是一个HTTP代理服务器,HTTP监视器,反转代理服务器,当手机通过连接Charles的代理访问互联网时,Charles可以监控手机端发送和接收的所有数据。它允许一个开发者查看所有连接互联网的HTTP通信,这些包括request, response和HTTP headers(包含cookies与caching信息)。Charles的安装过程请参考:抓包工具Charles的安装与配置

接下来开始抓包分析,将手机连接到Charles代理上,打开最右APP,待最右APP加载完成就能看到Charles界面的抓包数据了。找到url为http://api.izuiyou.com抓到的数据包,点开找到index文件夹,如果没有的话,下拉此时的最右APP的界面,待加载完成就会有index文件夹了。

志颖博客--最右APP爬虫--图片1

志颖博客–最右APP爬虫–图片1

这个文件夹下的数据就是我们要爬取的数据,是最右APP推荐页的段子数据。先来看一下手机端的页面:

志颖博客--最右APP爬虫--图片2

志颖博客–最右APP爬虫–图片2

再看一下Charles的抓包数据:

志颖博客--最右APP爬虫--图片3

志颖博客–最右APP爬虫–图片3

然后我们来看一下index文件夹下的名为recommend?sign=22a5bdae13749c61f9038a7c0bed4304的json文件的原始请求头(点击raw):

POST /index/recommend?sign=22a5bdae13749c61f9038a7c0bed4304 HTTP/1.1
Request-Type: text/json
ZYP: mid=116456192
User-Agent: okhttp/3.11.0 Zuiyou/4.7.3
Content-Type: application/json; charset=utf-8
Content-Length: 634
Host: api.izuiyou.com
Accept-Encoding: gzip
Connection: keep-alive

{"filter":"all","auto":1,"tab":"推荐","direction":"homebutton","c_types":[1,3,2,8,7,9,11],"sdk_ver":{"tt":"1.9.6.3","tx":"4.19.574","tt_aid":"5001336","tx_aid":"1106701465"},"ad_wakeup":1,"h_ua":"Mozilla\/5.0 (Linux; Android 7.1.2; MI 5X Build\/N2G47H; wv) AppleWebKit\/537.36 (KHTML, like Gecko) Version\/4.0 Chrome\/67.0.3396.87 Mobile Safari\/537.36","h_av":"4.7.3","h_dt":0,"h_os":25,"h_app":"zuiyou","h_model":"MI 5X","h_did":"866655030396869_02:00:00","h_nt":1,"h_m":116456192,"h_ch":"xiaomi","h_ts":1544158739365,"token":"T6K1NCRqAec6tUN7wn3-JSGqoTZpEirJqSGFXweZIyic8hcdPl3JUMLiPyCYrr_NTzVqH","android_id":"57b9b8465c2e440b"}

这是一个完整的http请求,请求方式为post,上面空行下面是post请求带的数据。我们看一下它的URL,有一个sign参数,它的值是没有规律的,显然是经过了加密,这里是它是使用了消息摘要算法进行了加密,生成的sign用于验证身份。

最后我们来看一下视频弹幕的抓包分析。找到一个带有视频的段子,点开观看,同时观察Charles界面的变化,再查看新的数据包。发现http://dmapi.izuiyou.com的danmaku文件夹下有视频弹幕,名字为list?sign=(32位的sign值)。

志颖博客--最右APP爬虫--图片4

志颖博客–最右APP爬虫–图片4

文件是json格式的,下面我们来看一下它的原始请求头:

POST /danmaku/list?sign=3ce44eef559dca08a921785132e997b3 HTTP/1.1
Request-Type: text/json
ZYP: mid=116456192
User-Agent: okhttp/3.11.0 Zuiyou/4.7.3
Content-Type: application/json; charset=utf-8
Content-Length: 316
Host: dmapi.izuiyou.com
Accept-Encoding: gzip
Connection: keep-alive

{"pid":78916670,"vid":441842959,"t":0,"h_av":"4.7.3","h_dt":0,"h_os":25,"h_app":"zuiyou","h_model":"MI 5X","h_did":"866655030396869_02:00:00","h_nt":1,"h_m":116456192,"h_ch":"xiaomi","h_ts":1544175021056,"token":"T5K9NCRqAec6tUN7wn3-JSGqoTbKmnuBTpt_ZPNCKD36EL3KzBebe6mQ2LnTg43_OShFq","android_id":"57b9b8465c2e440b"}

仍然是个post的请求,且使用了消息摘要算法进行加密,加密数据仍然是post所携带的数据。

二,加密分析

在上一步的抓包分析中我们得到了最后APP手机端推荐页的URL和计算sign值的消息数据,以及视频弹幕的URL和其对应的计算sign值的消息数据。这一步我们主要是分析加密过程和模拟加密过程计算sign值。sign的加密过程为:将post所携带的(消息)数据的字符串的后面加上字符串”ZDY0MTBlODcx”,然后把得到的字符串的前十个字符和后十个字符相互调换,最后将调换的字符串按消息摘要算法进行加密即得到sign值。

接下来分析手机端推荐页的post请求所携带的(消息)数据的变化规律,先随便看一个:

{
	"filter": "all",
	"auto": 0,    // 这个是变值,如果是自动加载值就为1,手指滑动加载值就为0
	"tab": "推荐",
	"direction": "down",   // 这个值是变值,homebutton为第一次点开最右APP让它自动加载时的值,如果下拉则为down,上拉为up。
	"c_types": [1, 3, 2, 8, 7, 9, 11],
	"sdk_ver": {
		"tt": "1.9.6.3",
		"tx": "4.19.574",
		"tt_aid": "5001336",
		"tx_aid": "1106701465"
	},
	"ad_wakeup": 2,
	"h_ua": "Mozilla\/5.0 (Linux; Android 7.1.2; MI 5X Build\/N2G47H; wv) AppleWebKit\/537.36 (KHTML, like Gecko) Version\/4.0 Chrome\/67.0.3396.87 Mobile Safari\/537.36",
	"h_av": "4.7.3",
	"h_dt": 0,
	"h_os": 25,
	"h_app": "zuiyou",
	"h_model": "MI 5X",
	"h_did": "866655030396869_02:00:00",
	"h_nt": 1,
	"h_m": 116456192,
	"h_ch": "xiaomi",
	"h_ts": 1544175005700,    // Unix时间戳
	"token": "T5K9NCRqAec6tUN7wn3-JSGqoTbKmnuBTpt_ZPNCKD36EL3KzBebe6mQ2LnTg43_OShFq",
	"android_id": "57b9b8465c2e440b"
}

同一个手机其他值都是相同,只有上面标注的变量是变化的。只有当post所携带的(消息)数据的sign值和URL中的sign值一致时(身份验证通过),服务器端才会正确返回我们需要的数据。

同样的我们来分析一下视频弹幕的post所携带的(消息)数据的变化规律:

{
	"pid": 78916670,   // 推荐页数据中的id
	"vid": 441842959,    // 视频id
	"t": 0,            // 视频播放时刻
	"h_av": "4.7.3",
	"h_dt": 0,
	"h_os": 25,
	"h_app": "zuiyou",
	"h_model": "MI 5X",
	"h_did": "866655030396869_02:00:00",
	"h_nt": 1,
	"h_m": 116456192,
	"h_ch": "xiaomi",
	"h_ts": 1544175021056,    // Unix时间戳
	"token": "T5K9NCRqAec6tUN7wn3-JSGqoTbKmnuBTpt_ZPNCKD36EL3KzBebe6mQ2LnTg43_OShFq",
	"android_id": "57b9b8465c2e440b"
}

同一个手机的其他值仍然是相同的,只有被标注的才是变化的量。视频弹幕是随播放不断被加载的,但是”t”: 0 始终是第一个被加载的,仔细观察你会发现第一个视频弹幕返回值里面有下一次视频弹幕请求的t值,还有一个more参数,如果more的值是1表明弹幕没有加载完成,如果more的值为0表明该视频弹幕加载完成,无需再请求弹幕了。因此通过迭代你就能获取到一个视频的全部弹幕了。

# 获取全部弹幕
def danmu(pid, vid):

    def parse_chunked_data(t):
        data = {"pid":pid,"vid":vid,"t":t,"h_av":"4.7.3","h_dt":0,"h_os":25,"h_app":"zuiyou","h_model":"MI 5X","h_did":"866655030396869_02:00:00","h_nt":1,"h_m":116456192,"h_ch":"xiaomi","h_ts":unixtime(),"token":"TeKfNCRqAec6tUN7wn3-JSGqoTTXBsx0YTPoKXva9q6z94jiDk2da1MuSuhRdh-G8Bp3-","android_id":"57b9b8465c2e440b"}
        sign = messagesdigest(str(data))
        # 弹幕地址
        url = 'http://dmapi.izuiyou.com/danmaku/list?sign=' + str(sign)
        headers = {"User-Agent": "okhttp/3.11.0 Zuiyou/4.7.1"}
        try:
            r = requests.post(url, headers=headers, data=json.dumps(data))
            response = r.json()
            if response["ret"] == 1:
                chunked_danmu = response["data"]["list"]
                parser_danmu(chunked_danmu)
                # print('{}\n\n'.format(chunked_danmu))
                print("\n")
                # 判断弹幕是否加载完成的标志,1为未完成,0为完成
                more = response["data"]["more"]
                # 下次弹幕加载的开始时间
                t = response["data"]["t"]
                return more, t
            else:
                print("sign签名出错哦!出错pid={},vid={}\n请稍后重试!".format(pid, vid))

        except RequestException as e:
            print(e)

    t = 0
    more, t, = parse_chunked_data(t)
    while more == 1:
        more, t = parse_chunked_data(t)
    

参数h_ts是Unix时间戳的值,Python中可用time.time()来求当前Unix时间戳值。

# unix时间戳
def unixtime():
    """

    :return: unix时间戳
    """
    time_string = '{:.3f}'.format(time.time()).replace('.', '')
    return eval(time_string)

h_ts是13位的,因此我们需要对其进行格式化。其实这个h_ts值每次保持不变也行,只要保证请求时的消息和sign值是一一对应就行,但最好还是取当前时刻的Unix时间戳。

 

最后我们来看一下sign值的计算过程,将消息字符串加上字符串”ZDY0MTBlODcx”,把得到的字符串的前十个字符和后十个字符相互颠倒,然后再进行消息摘要算法加密得到sign值。我们可以定义一个函数来计算sign的值:

# 消息摘要算法加密
def messagesdigest(msg):

    # 消息的混淆,在消息后面添加字符串“ZDY0MTBlODcx”
    msgs = msg + 'ZDY0MTBlODcx'

    msg_ls = list(msgs)
    # 混淆后消息的前十个字符
    str_head = msg_ls[0:10]
    # 混淆后消息的后十个字符
    str_foot = msg_ls[len(msg_ls) - 10:len(msg_ls)]
    # 将前十个字符和后十个字符进行交换
    del msg_ls[0:10]
    del msg_ls[len(msg_ls)-10:len(msg_ls)]
    msg_ls = str_foot + msg_ls + str_head
    mixedmsg = ''
    for i in range(len(msg_ls)):
        mixedmsg += str(msg_ls[i])
    # 用于显示首尾是否正常颠倒
    # print(mixedmsg)
    # mixedmsg = 'Y0MTBlODcx"all","auto":1,"tab":"推荐","direction":"homebutton","c_types":[1,3,2,8,7,9,11],"sdk_ver":{"tt":"1.9.6.3","tx":"4.19.574","tt_aid":"5004095","tx_aid":"1107850635"},"ad_wakeup":1,"h_ua":"Mozilla\/5.0 (Linux; Android 7.1.2; MI 5X Build\/N2G47H; wv) AppleWebKit\/537.36 (KHTML, like Gecko) Version\/4.0 Chrome\/67.0.3396.87 Mobile Safari\/537.36","h_av":"4.7.3","h_dt":0,"h_os":25,"h_app":"zuiyou","h_model":"MI 5X","h_did":"866655030396869_02:00:00","h_nt":1,"h_m":116456192,"h_ch":"xiaomi","h_ts":1543834422778,"token":"TfKbNCRqAec6tUN7wn3-JSGqoTcO1QytGiEBG2E1jQvCYBqj-TcCLYxVzUKtxgpDii503","android_id":"57b9b8465c2e440b"}ZD{"filter":'

    # 消息摘要算法加密,得到sign
    # 先将混淆后的信息按utf-8编码成byte
    data = mixedmsg.encode('utf-8')

    digest = hashlib.md5()
    digest.update(data)
    sign = digest.hexdigest()

    # 返回消息摘要算法加密得到的sign值
    return sign

消息摘要算法的实现我是使用Python中的内置库hashlib的hashlib.md5(),我们检验sign值是否计算正确可以使用抓包得到的数据进行加密然后看得到的sign是否与URL中的sign值一致。

 

三,总结

最右APP的加密使用了消息摘要算法加密,因此找出消息数据的变化规律,在计算出sign值,我们便能顺利地爬取到数据。

获取源代码。大家在爬取过程中有任何问题欢迎留言!

参考文章:最右APP协议加密算法分析笔记

转载请注明:志颖博客 » 用Python爬取最右APP数据(段子和视频弹幕)

喜欢 (18)or分享 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(8)个小伙伴在吐槽
  1. 现在post的数据好像加密了,有办法解密吗
    司马老师2019-06-17 20:43 回复
    • 并没有加密,只是把响应的文件内容格式给改了,仍然可以通过用Python转换成json格式的数据。代码已更新!
      Jason zhou2019-06-30 06:31 回复
  2. 你好 感谢你的代码 让我少花了3天时间 我提出一个建议 你可以这样修改 你的代码: # unix时间戳 def unixtime(): """ :return: unix时间戳 """ # time_string = '{:.3f}'.format(time.time()).replace('.', '') # print(time_string) # return eval(time_string) times = int(time.time() * 1000) # 乘法开销比正则少 且直接就是 int 类型。 return times
    LaoSi2019-10-21 14:45 回复
  3. 请问你是怎么样分析加密过程的?我想学习一下。
    无情2020-02-17 22:06 回复
  4. 现在打开charles看到的是乱码 post的数据看不到 还有这个混淆加密是怎么找出规律的啊 大佬
    七月流火2020-03-06 16:00 回复