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

python3爬虫(4)各种网站视频下载方法
原创H-KING 最后发布于2019-01-09 11:06:23 阅读数 13608 收藏
展开
理论上来讲只要是网上(浏览器)能看到图片,音频,视频,都能够下载下来,然而实际操作的时候也是有一定难度和技术的,这篇文章主要讲述各个网站视频资源如何下载。

 

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拷贝带图片,赞一个,记得一年前写文章要单独上传图片,麻烦死,现在好了。。
————————————————
版权声明:本文为CSDN博主「H-KING」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/liujiayu2/article/details/86137139

posted @ 2020-02-02 10:31  知一以天  阅读(3187)  评论(0编辑  收藏  举报