python3爬虫(4)各种网站视频下载

发布时间:2019-08-07 14:07:05编辑:auto阅读(4346)

    理论上来讲只要是网上(浏览器)能看到图片,音频,视频,都能够下载下来,然而实际操作的时候也是有一定难度和技术的,这篇文章主要讲述各个网站视频资源如何下载。

     

    1. B站视频

    页面链接:

    https://www.bilibili.com/bangumi/play/ep118490?from=search&seid=7943855106424547918

    首先我们用万能下载器“you-get”测试一下,下载成功,60多兆的视频文件,打开可以观看。我们在浏览器输入该网址,F12打开网络监测,回车进入该网页,点击播放视频,观看一分钟左右,为什么要观看一分钟,主要是看视频是一个链接传输,还是不停的更换视频链接,还有就是1分钟会有挺大的视频缓冲数据,明显比其他网络资源大,方便咱们分析。暂停视频,停止抓包,看到抓包栏信息如下:

    我们重点注意,Size,Time,Watefall栏,因为视频链接要返回数据,大小和花费时间明显比其他的资源大的多,滚动下看看所有信息,找到一个怀疑目标

    看到返回数据29.4兆,这应该就是视频资源,先别急着分析这个链接,我们再看看有无其他怀疑目标,滚动一边发现仅此一个。选中该链接,看看详细信息

    我们看到该链接是个get请求和一个关键字.flv,这个应该就是视频连接地址。

    全连接如下:

    https://upos-hz-mirrorkodo.acgvideo.com/upgcxcode/49/50/29645049/29645049-1-32.flv?e=ig8euxZM2rNcNbKHhwdVhoMMnWdVhwdEto8g5X10ugNcXBlqNxHxNEVE5XREto8KqJZHUa6m5J0SqE85tZvEuENvNC8xNEVE9EKE9IMvXBvE2ENvNCImNEVEK9GVqJIwqa80WXIekXRE9IMvXBvEuENvNCImNEVEua6m2jIxux0CkF6s2JZv5x0DQJZY2F8SkXKE9IB5QK==&deadline=1547005191&dynamic=1&gen=playurl&oi=22475807&os=kodo&platform=pc&rate=490000&trid=52c1879aeb584205af339c5624957e09&uipk=5&uipv=5&um_deadline=1547005191&um_sign=6049af7768edf6ebf7819f897bbda605&upsig=0b7a5fa168a1d70655e645783d7184d3

    这个链接结构式视频链接+参数的形式,号后面都是参数,

    视频链接如下:

    https://upos-hz-mirrorkodo.acgvideo.com/upgcxcode/49/50/29645049/29645049-1-32.flv

    把这两个地址分别输入浏览器地址栏试试,发现都没什么反应,再用浏览器自带下载工具试试(当然也可以用其他下载工具试,如迅雷),

    发现全连接那个下载失败,视频连接那个下载成功,下载到了60多兆的视频文件,可以播放,这样这个下载地址就算找到了,我们再试试上次用python写的简单7行代码试试,

    代码链接:

    python3爬虫(2)下载有固定链接的视频

    发现瞬间结束,调试一下发现下载失败了,错误码:459。如下图:

    这个不应该啊,估计是http请求头出了问题,我们抓下浏览器是什么头,发现浏览器自带抓包工具无法抓下载的包头,只能够抓浏览网页的头,用抓包工具Fiddler抓吧。

    我们把这个包头写到请求里面,发现下载成功了。完整代码如下:

    import requests
    
    hd = {
        'Connection':'keep-alive',
        'Host':'upos-hz-mirrorkodo.acgvideo.com:443',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0'
    }
    
    print("开始下载")
    url = 'https://upos-hz-mirrorkodo.acgvideo.com/upgcxcode/49/50/29645049/29645049-1-32.flv'
    r = requests.get(url, headers=hd, stream=True)
    
    with open('test.mp4', "wb") as mp4:
        for chunk in r.iter_content(chunk_size=1024 * 1024):
            if chunk:
                mp4.write(chunk)
    
    print("下载结束")
    
    

    2.优酷

    页面链接:

    http://v.youku.com/v_show/id_XMTQ2NzQyMjY1Ng.html

    还是老规矩,

    首先我们用万能下载器“you-get”测试一下,下载成功,13多兆的视频文件,打开可以观看。我们在浏览器输入该网址,F12打开网络监测,回车进入该网页,点击播放视频,观看一分钟左右。暂停视频,停止抓包。观察抓包栏目,这次和上面的例子不一样,这次找了半天也没找到1个超过1兆的数据包,推测是分开传输的,经过进一步的寻找发现m3u8链接,如下图:

    经过前面的学习我们知道这是m3u8+ts传输视频流的,具体技术请看:

    python3爬虫(3)下载流媒体m3u8

    我们看下这个具体细节,手工下载一下m3u8文件,可以下载,和网页端比较一下是一样的,手工下载ts列表里面的ts文件,也是可以下载的,播放一下看看,都没啥问题。这些步骤可以用下面的代码实现,前提是知道m3u8下载地址(包括地址里面的参数)

    import os
    import requests
    
    """
    下载M3U8文件里的所有片段
    """
    
    def download(url):
        download_path = os.getcwd() + "\download"
        if not os.path.exists(download_path):
            os.mkdir(download_path)
        all_content = requests.get(url).text # 获取M3U8的文件内容
        file_line = all_content.split("\r\n") # 读取文件里的每一行
        # 通过判断文件头来确定是否是M3U8文件
        if file_line[0] != "#EXTM3U":
            raise BaseException(u"非M3U8的链接")
        else:
            unknow = True   # 用来判断是否找到了下载的地址
            for index, line in enumerate(file_line):
                if "EXTINF" in line:
                    unknow = False
                        # 拼出ts片段的URL
                    pd_url = file_line[index + 1]
                    res = requests.get(pd_url)
                    # c_fule_name = str(index)+ '.ts'
                    c_fule_name = "%(index)02d" % {'index': index} + '.ts'
                    with open(download_path + "\\" + c_fule_name, 'ab') as f:
                        f.write(res.content)
                        f.flush()
            if unknow:
                raise BaseException("未找到对应的下载链接")
            else:
                print("下载完成")
    
    #合并的时候名字要有规律,从前往后排
    def merge_file(path):
        os.chdir(path)
        os.system("copy /b * new.mp4")
    
    if __name__ == '__main__':
        download("http://pl-ali.youku.com/playlist/m3u8?vid=XMTQ2NzQyMjY1Ng&type=hd2&ups_client_netip=0156f41f&utid=cKsgFHBPZVECAXUjhXp%2Bu8Ip&ccode=0502&psid=244b3690aa7b9cd1c11c2f6c8ae6582b&duration=90&expire=18000&drm_type=1&drm_device=7&ups_ts=1547012701&onOff=0&encr=0&ups_key=9a7e324bb33543281964c43caa15dc80")
        merge_file(os.getcwd() + "\download")

    我们这个时候就考虑能否仅根据网页地址全自动下载呢,毕竟you-get可以做到全自动,我们来看看这个m3u8地址:

    http://pl-ali.youku.com/playlist/m3u8?vid=XMTQ2NzQyMjY1Ng&type=mp4&ups_client_netip=0156f41f&utid=cKsgFHBPZVECAXUjhXp%2Bu8Ip&ccode=0502&psid=db59c8bd03f9e26f6b21b17bccf8f1c9&duration=90&expire=18000&drm_type=1&drm_device=7&ups_ts=1547003676&onOff=0&encr=0&ups_key=49fa2661f64619e0e57d22611df8e5b7

    地址是固定的,参数才是关键,参数如下:

    vid:XMTQ2NzQyMjY1Ng

    type:mp4

    ups_client_netip:0156f41f

    utid:cKsgFHBPZVECAXUjhXp+u8Ip

    ccode:0502

    psid:db59c8bd03f9e26f6b21b17bccf8f1c9

    duration:90

    expire:18000

    drm_type:1

    drm_device:7

    ups_ts:1547003676

    onOff:0

    encr:0

    ups_key:49fa2661f64619e0e57d22611df8e5b7

    这些参数我们猜测,应该是获取这个m3u8之前向服务器获取的,应该是以json方式返回来的,在抓包栏目里面我们找找,紧挨着这个m3u8向上找,找到了1个,

    几个ups属性找到了,没有ups_key,这个估计可能是js算出来的码,不是从服务器获取到的。Vid属性可以从网址里面提取出来,type是清晰度,mp4:标清,hd:高清,hd2:超清,(我们切换清晰度,观察m3u8地址变化总结出来的),utid是个固定值,我们换个视频还是这个码,ccode, drm_type, drm_device, onOff, encr,也一样固定值;duration进过总结就是视频时长(秒),按道理来讲应该是服务器放回来的,简单找了一下没找到。先记录到这里吧。

    ================以下为2019/1/10 13:20 更新=======================

    接着上次聊。

    这个m3u8的参数:type:mp4,ups_key:49fa2661f64619e0e57d22611df8e5b7,都是不容易找到。

    上次我们找到ups那个json里面当时也看了看其他节点,收获还真不小,如下图:

    注意这个stream分支,打开他的分支0,

    我们看到了m3u8地址,这就太好了,再往下看看,

    有个cdn_url,从这个地址我们可以看出这就是一个视频链接。试着下载一下看看,就是我们在网页里面看到的视频。刚不是看到stream有4个子项吗,分别打开看看,每个子项里面都有m3u8_url和cdn_url,试了一下每个都可以下载视频,一种height属性:288,288,378,622。看下界面有,标清,高清,超清,这些应该是互相对应的。亦即只要获得了这个json其实就获取视频源头。我们看下这个请求的地址:

    http://acs.youku.com/h5/mtop.youku.play.ups.appinfo.get/1.1/?jsv=2.5.0&appKey=24679788&t=1547091011531&sign=be34df75c65e2e871e720039252ee056&api=mtop.youku.play.ups.appinfo.get&v=1.1&timeout=20000&YKPid=20160317PLF000211&YKLoginRequest=true&AntiFlood=true&AntiCreep=true&type=jsonp&dataType=jsonp&callback=mtopjsonp1&data=%7B%22steal_params%22%3A%22%7B%5C%22ccode%5C%22%3A%5C%220502%5C%22%2C%5C%22client_ip%5C%22%3A%5C%22192.168.1.1%5C%22%2C%5C%22utid%5C%22%3A%5C%22cKsgFHBPZVECAXUjhXp%2Bu8Ip%5C%22%2C%5C%22client_ts%5C%22%3A1547091011%2C%5C%22version%5C%22%3A%5C%220.6.8%5C%22%2C%5C%22ckey%5C%22%3A%5C%22DIl58SLFxFNndSV1GFNnMQVYkx1PP5tKe1siZu%2F86PR1u%2FWh1Ptd%2BWOZsHHWxysSfAOhNJpdVWsdVJNsfJ8Sxd8WKVvNfAS8aS8fAOzYARzPyPc3JvtnPHjTdKfESTdnuTW6ZPvk2pNDh4uFzotgdMEFkzQ5wZVXl2Pf1%2FY6hLK0OnCNxBj3%2Bnb0v72gZ6b0td%2BWOZsHHWxysSo%2F0y9D2K42SaB8Y%2F%2BaD2K42SaB8Y%2F%2BahU%2BWOZsHcrxysooUeND%5C%22%7D%22%2C%22biz_params%22%3A%22%7B%5C%22vid%5C%22%3A%5C%22XMTQ2NzQyMjY1Ng%3D%3D%5C%22%7D%22%2C%22ad_params%22%3A%22%7B%5C%22vs%5C%22%3A%5C%221.0%5C%22%2C%5C%22pver%5C%22%3A%5C%220.6.8%5C%22%2C%5C%22sver%5C%22%3A%5C%221.0%5C%22%2C%5C%22site%5C%22%3A1%2C%5C%22aw%5C%22%3A%5C%22w%5C%22%2C%5C%22fu%5C%22%3A0%2C%5C%22d%5C%22%3A%5C%220%5C%22%2C%5C%22bt%5C%22%3A%5C%22pc%5C%22%2C%5C%22os%5C%22%3A%5C%22win%5C%22%2C%5C%22osv%5C%22%3A%5C%227%5C%22%2C%5C%22dq%5C%22%3A%5C%22auto%5C%22%2C%5C%22atm%5C%22%3A%5C%22%5C%22%2C%5C%22partnerid%5C%22%3A%5C%22null%5C%22%2C%5C%22wintype%5C%22%3A%5C%22interior%5C%22%2C%5C%22isvert%5C%22%3A0%2C%5C%22vip%5C%22%3A0%2C%5C%22emb%5C%22%3A%5C%22AjM2Njg1NTY2NAJ2LnlvdWt1LmNvbQIvdl9zaG93L2lkX1hNVFEyTnpReU1qWTFOZy5odG1s%5C%22%2C%5C%22p%5C%22%3A1%2C%5C%22rst%5C%22%3A%5C%22mp4%5C%22%2C%5C%22needbf%5C%22%3A2%7D%22%7D

    看看他的参数列表:

    jsv:2.5.0

    appKey:24679788

    t:1547091011531

    sign:be34df75c65e2e871e720039252ee056

    api:mtop.youku.play.ups.appinfo.get

    v:1.1

    timeout:20000

    YKPid:20160317PLF000211

    YKLoginRequest:true

    AntiFlood:true

    AntiCreep:true

    type:jsonp

    dataType:jsonp

    callback:mtopjsonp1

    data:{"steal_params":"{\"ccode\":\"0502\",\"client_ip\":\"192.168.1.1\",\"utid\":\"cKsgFHBPZVECAXUjhXp+u8Ip\",\"client_ts\":1547091011,\"version\":\"0.6.8\",\"ckey\":\"DIl58SLFxFNndSV1GFNnMQVYkx1PP5tKe1siZu/86PR1u/Wh1Ptd+WOZsHHWxysSfAOhNJpdVWsdVJNsfJ8Sxd8WKVvNfAS8aS8fAOzYARzPyPc3JvtnPHjTdKfESTdnuTW6ZPvk2pNDh4uFzotgdMEFkzQ5wZVXl2Pf1/Y6hLK0OnCNxBj3+nb0v72gZ6b0td+WOZsHHWxysSo/0y9D2K42SaB8Y/+aD2K42SaB8Y/+ahU+WOZsHcrxysooUeND\"}","biz_params":"{\"vid\":\"XMTQ2NzQyMjY1Ng==\"}","ad_params":"{\"vs\":\"1.0\",\"pver\":\"0.6.8\",\"sver\":\"1.0\",\"site\":1,\"aw\":\"w\",\"fu\":0,\"d\":\"0\",\"bt\":\"pc\",\"os\":\"win\",\"osv\":\"7\",\"dq\":\"auto\",\"atm\":\"\",\"partnerid\":\"null\",\"wintype\":\"interior\",\"isvert\":0,\"vip\":0,\"emb\":\"AjM2Njg1NTY2NAJ2LnlvdWt1LmNvbQIvdl9zaG93L2lkX1hNVFEyTnpReU1qWTFOZy5odG1s\",\"p\":1,\"rst\":\"mp4\",\"needbf\":2}"}

     

    这条路走下去主要有3个难点:

    难点一:

    sign:cda7f7031b84db2b741d31ac4a8bec89

    难点二:

    ckey:115#133lN51O1TaT1YgQMCfR1Csou61hIeAacuvuZj .............

    难点三:

    emb:AjM2Njg1NTY2NAJ2LnlvdWt1LmNvbQIvdl9zaG93L2lkX1hNVFEyTnpReU1qWTFOZy5odG1s

    这3个值不知道哪里去弄,当然按道理来讲肯定有点方去弄,只是难度有点大。

    刚刚看到希望,瞬间蔫了,因为我们看了一下其他链接,返回json的不是很多,json里面有这些字段值是没有,这就有点棘手了。

    回头想想,不是you-get能够获取到这个视频吗,我们看看他是怎么获得到的。打开抓包工具Fiddler,这个是名气最大的,简单看下,7个请求,其中有6个是python进程的请求,1个是浏览器sogouexplore.exe请求。

    看看这个6个python请求。第一个是log.mmstat.com,第二个是https请求,后边4次好像是重复的动作,一模一样,其实是两次请求。点开https请求,发现没有什么有价值的信息,好像是Fiddler解析https需要配置什么,上次配置之后又导致浏览器不能正常访问网页。换个抓包工具吧,我下载了一个HttpAnalyzer。效果还不错,先看下抓包结果,

    选中那个https请求,看下是能够完全解析的。返回数据支持json,data,hex,preview四种展示方式

    我们看下,这个返回结果其实和上面咱们那个比较长的请求结果是一样的。You-get获取到这个json之后解析了里面的cdn_url选择最清晰的下载了下来。看下他传输的时候有哪些参数,看了下这个参数少了很多,鉴于这个请求之前没有和优酷服务器通讯,我们猜测这些值都是定值,有仔细看了下client_ts字段值很想时间戳,确定了一下就是时间戳,

    我们简短的写代码,模拟一下这个请求,果然成功了,返回了想要的json。这个请求这么好,浏览器里面是不是也有这个请求而我们没注意到呢?我们搜索一下ups.youku.com。看看浏览器有没发出类似的请求,

    发现0个请求,也就是没有发出向这个网站的请求,那you-get又是怎么知道的呢,估计是以前版本,请求是向ups.youku.com发出的,现在更新了。既然更新了为什么老的还能用?因为整个优酷太大,前台页面有很多,更新前台页面工作量太大,所以现在服务器是新老本兼容的,只不过以后的页面都会以更新之后的出现。

    第一个链接http://log.mmstat.com/eg.js是干什么的,一开始没注意,因为根据现有内容已经可以获得我们想要的内容,后来测试的路中出现问题:客户端无权播放,

    You-get也会出现类似问题,只不过概率很低,我们分一下原因,发现you-get发送请求中utid的值是变动,这个值是哪里来的?本地有个列表循环着来,不太合适吧,我们看下第一个链接http://log.mmstat.com/eg.js 正好发现了这个值。我们改下自己代码,每次请求之先获取一下这个utid,发现现在就很流畅了。

    注意:

    通过这次实战我们也了解到了,不会存在一种给个页面就能下载页面里面的视频的通杀方案,you-get之所以能下载大部分主流网站是因为他为每个网站都做了适配,亦即每个网站下载视频原理他都已经研究了。

    源码(想要下载你的视频,将param里面的vid值改成你的地址栏的值就行了)

    import requests
    import time
    import json
    
    def downfile(filename, url):
        r = requests.get( url, stream=True)
        with open(filename, "wb") as mp4:
            for chunk in r.iter_content(chunk_size=1024 * 1024):
                if chunk:
                    mp4.write(chunk)
    
    # 获取证书
    r = requests.get('http://log.mmstat.com/eg.js')
    start = len('window.goldlog=(window.goldlog||{});goldlog.Etag=\"')
    end = len('window.goldlog=(window.goldlog||{});goldlog.Etag=\"66C9FJlZrDYCAQFQU4AhkzO0')
    sert = r.text[start:end]
    
    param = {
        'vid':'XMTQ2NzQyMjY1Ng',
        'ccode':'0590',
        'client_ip':'192.168.1.1',
        'utid':'2py9FNCXjUcCAQFQU4APrwPf',
        'client_ts':'1547028409',
        'ckey':'DIl58SLFxFNndSV1GFNnMQVYkx1PP5tKe1siZu/86PR1u/Wh1Ptd+WOZsHHWxysSfAOhNJpdVWsdVJNsfJ8Sxd8WKVvNfAS8aS8fAOzYARzPyPc3JvtnPHjTdKfESTdnuTW6ZPvk2pNDh4uFzotgdMEFkzQ5wZVXl2Pf1/Y6hLK0OnCNxBj3+nb0v72gZ6b0td+WOZsHHWxysSo/0y9D2K42SaB8Y/+aD2K42SaB8Y/+ahU+WOZsHcrxysooUeND'
    }
    
    headers = {
        'Accept-Encoding': 'identity',
        'Host': 'ups.youku.com',
        'Referer': 'http://v.youku.com',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36',
        'Connection': 'close'
    }
    
    now = time.time()
    nowint = int(now)
    nowstr = str(nowint)
    param['client_ts'] = nowstr
    param['utid'] = sert
    
    url = 'http://v.youku.com/ups/get.json?'
    r = requests.get(url, headers=headers, params=param)
    
    r_j = json.loads(r.text)
    streams = r_j.get('data').get('stream')
    
    
    for stream in streams:
        m3u8_url = stream.get('m3u8_url')
        cdn_url = stream.get('segs')[0].get('cdn_url')
    
        print('m3u8_url:' + m3u8_url)
        print('cdn_url:' + cdn_url)
        filename = cdn_url[len('http://ykugc.cp31.ott.cibntv.net/65720C705E33C7182E34B311F/'):len('http://ykugc.cp31.ott.cibntv.net/65720C705E33C7182E34B311F/030020010056B748030687093F3C3CF7C1644A-3E5C-1E70-1F07-0C1AD8DC39BB.mp4')]
        downfile(filename,cdn_url)
    
    print('end')
    
    
    

     

     

     

     

     

    最后,现在CSDN写文章支持word拷贝带图片,赞一个,记得一年前写文章要单独上传图片,麻烦死,现在好了。。

关键字

上一篇: python3学习笔记

下一篇: apache cgi python