前言

  我现在没时间玩游戏,偶尔无聊会在斗鱼上看看诸如PDD和周淑怡这种带喜剧效果的主播玩,可是每次都要登录才能看超清或者蓝光画质,这让已经懒习惯的我很是头疼,输入来输入去就罢了,各种广告、礼物特效飞来飞去简直不要太烦,于是乎想着抓一抓直播源用别的播放器来看算了,但抓包过后我发现事情远没有想象中那么简单……

  去网上搜了一下,发现斗鱼和别的直播平台与众不同,它的直播源貌似比较“宝贝”,各种加密、各种保护!那怎么办呢?动手搞呗!

巨人

  自己动手搞是不可能的,这辈子是不可能的,在我坚定决心自己分析的时候,总有那么一些伟大的人在向我喊着:“少年,我看你根骨奇佳,这份代码就交给你了。”

  没错,我在搜索相关资料的时候居然发现了一份斗鱼直播源获取的源码,那还想什么?拿来呗。

  站在巨人的肩膀上看风景,那真可谓是一览众山小呀!话虽如此,但偷懒这种恶习还是不能让它根深蒂固的,我还是决定分析完他的代码再找个时间自己抓包研究研究。(源码作者是wbt5,代码开源在github上。)

分析

  从其源码上看,获取斗鱼的直播源倒也没有太复杂,总结起来就是以form-data的形式向https://m.douyu.com/api/room/ratestream发送POST请求,如果成功,就会返回带有短效直播源地址的Json,将短效直播源地址修改一下就能够获得一个长效的直播源地址。

  而form-data的结构中,唯一比较复杂的一点在于sign值的获取。整个结构如下表:

form-data属性备注
v2501+tradetimetradetime为当天日期,例如:20191031
did10000000000000000000000000001501固定值
tttimestamp10位时间戳,例如:1572507694
signsign32位加密字符串
ver219032101固定值
ridroomid实际直播房间ID
rate-1固定值

  具体代码如下,具体分析看我添加的注释,方便新手快速看懂。

tradetime和timestamp获取

def get_tt():
    tt1 = str(int(time.time())) #10位时间戳
    tt2 = str(int((time.time() * 1000))) #13位时间戳
    today = time.strftime('%Y%m%d', time.localtime()) #当天日期
    return tt1, tt2, today

roomid和sign获取

def get_homejs(rid):
    room_url = 'https://m.douyu.com/' + rid 
    response = requests.get(url=room_url)  
    pattern_real_rid = r'"rid":(\d{1,7})'
    real_rid = re.findall(pattern_real_rid, response.text, re.I)[0] #获取roomid(有一些主播的房间ID是viprid,也就是表面上的rid,例如周淑怡的22222,实际上则是290935)
    if real_rid != rid:
        room_url = 'https://m.douyu.com/' + real_rid
        response = requests.get(url=room_url)
    homejs = ''
    pattern = r'(function ub9.*)[\s\S](var.*)'
    result = re.findall(pattern, response.text, re.I)
    str1 = re.sub(r'eval.*;}', 'strc;}', result[0][0])
    homejs = str1 + result[0][1] #获得第一段JS代码
    return homejs, real_rid


def get_sign(rid, post_v, tt, ub9):
    docjs = execjs.compile(ub9) #执行第一段JS代码
    res2 = docjs.call('ub98484234') #调用JS代码里的函数“ub98484234”
    str3 = re.sub(r'\(function[\s\S]*toString\(\)', '\'', res2)
    md5rb = hashlib.md5((rid + '10000000000000000000000000001501' + tt + '2501' +
                         post_v).encode('utf-8')).hexdigest()
    str4 = 'function get_sign(){var rb=\'' + md5rb + str3
    str5 = re.sub(r'return rt;}[\s\S]*','return re;};', str4) 
    str6 = re.sub(r'"v=.*&sign="\+', '', str5) #获得第二段JS代码
    docjs1 = execjs.compile(str6)  #执行第二段JS代码
    sign = docjs1.call(
        'get_sign', rid, '10000000000000000000000000001501', tt) #调用JS里的函数“get_sign”以获取sign值
    return sign

获取和修改直播源地址

def get_sign_url(post_v, rid, tt, ub9):
    sign = get_sign(rid, post_v, tt, ub9)
    request_url = 'https://m.douyu.com/api/room/ratestream'
    post_data = {
        'v': '2501' + post_v,
        'did': '10000000000000000000000000001501',
        'tt': tt,
        'sign': sign,
        'ver': '219032101',
        'rid': rid,
        'rate': '-1'
    }
    header = {
        'Content-Type': 'application/x-www-form-urlencoded',
        'User-Agent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Mobile Safari/537.36'
    }
    response = requests.post(url=request_url, headers=header, data=post_data).json()

  通过get_sign_url函数获得Json后可以从中提取到短效直播源地址。

  举个例子,下方是提取的短效直播源地址,提取里边的290935rl8pFB4Chu

http://hls1a.douyucdn.cn/live/290935rl8pFB4Chu_2000/playlist.m3u8?wsSecret=8e85c3c47dcfd12c0f3f626b7e9dc06e\u0026wsTime=1572509708\u0026token=h5-douyu-0-290935-a9b3efee00dd21938f948106b9d7c0df\u0026did=10000000000000000000000000001501\u0026origin=all\u0026vhost=play2

  拼接成http://tx2play1.douyucdn.cn/live/290935rl8pFB4Chu.flv?uuid=即可。

后话

  分析完这几段代码其实我还有些疑问,比如提交请求的API是怎么找到的?还有到那两段关键的JS代码是怎么找到的?这些都需要我抽空抓包去自己研究,学习本就应该刨根问底、追本溯源。

如果觉得我的文章对您有用,请我喝一杯咖啡吧,您的鼓励就是我的动力,感谢~
  留言