流程一览
1.环境搭建:
- 使用Anaconda(基本环境为Python3.6),Pycharm
- 文章发行时间为:2019.7.23 请注意网站的html节点变动和编程语言的版本更新
- 两个地址了解什么是m3u8,以及知道它的用途和格式:
-* M2U8简介
-* m3u8文件信息总结
(感谢他人的付出和原创)
2.直接上代码再讲解:
import requests
from lxml import etree
import m3u8
from Crypto.Cipher import AES
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHT"
"ML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"
}
base_url = 'http://www.tcmmooc.com'
cookies = None
def handle_login():
"""
登陆获取用户cookies
"""
res_1 = requests.get("http://www.tcmmooc.com/login", headers=headers)
cookie_1 = res_1.cookies.get_dict()
html = etree.HTML(res_1.text)
token = html.xpath('//*[@id="login-form"]/input[2]/@value')
res_2 = requests.post("http://www.tcmmooc.com/login_check", headers=headers,
data={
"_username": "************",
"_password": "*************",
"_remember_me": "on",
"_target_path": "http://www.tcmmooc.com/",
"_csrf_token": token
},
cookies=cookie_1)
cookie_2 = res_2.cookies.get_dict()
cookie_dict = {}
cookie_dict.update(cookie_1)
cookie_dict.update(cookie_2)
global cookies
cookies = cookie_dict
def get_html(url):
"""
获取url的请求结果并返回封装的html
:param url: 请求的url
:return: html
"""
res = requests.get(url, headers=headers, cookies=cookies)
html = etree.HTML(res.text)
return html
def handle_start_m3u8_url(url):
"""
处理m3u8视频url的请求的url
:param url: m3u8视频的url来源ajax的url
:return: 返回m3u8视频的最后一个链接url
"""
m3u8_content = requests.get(url, headers=headers, cookies=cookies).text
lines_list = m3u8_content.strip().split('\r\n')
if len(lines_list) < 3:
lines_list = m3u8_content.strip().split('\n')
if '#EXTM3U' not in m3u8_content:
raise BaseException('非M3U8连接')
return lines_list[-1]
def handle_m3u8_data(m3u8_url):
"""
下载m3u8视频
:param m3u8_url: 最后m3u8视频的url
"""
m3u8_obj = m3u8.load(m3u8_url) # 使用m3u8库,导入url,返回m3u8结果的对象
a = 0
key = requests.get(m3u8_obj.keys[0].uri, headers=headers, cookies=cookies).content # 获取aes加密的url结果
for i in m3u8_obj.keys: # 循环密匙
for seg in m3u8_obj.segments.by_key(i): # 循环密匙对应的m3u8视频
res = requests.get(seg.uri, headers=headers, cookies=cookies) # 获取m3u8视频片段的url返回的加密视频结果
iv = bytes.fromhex(seg.key.iv[2:]) # 提取aes加密的iv值,每个视频片段的iv值不同
content_video_part = AES.new(key, AES.MODE_CBC, iv).decrypt(res.content) # 对视频结果进行解密
with open("all\\text.MP4", 'ab') as f: # 追加保存解密结果
f.write(content_video_part)
print(a)
a += 1
def get_video_url():
"""
获取m3u8视频连接的url,然后处理并下载视频
"""
html = get_html('http://www.tcmmooc.com/course/6342')
video_url = base_url + html.xpath('//ul[@id="course-item-list"]/li[1]/a/@data-url')[0]
html = get_html(video_url)
data_player_url = base_url + html.xpath('//div[@id="lesson-preview-player"]/@data-player-url')[0]
html = get_html(data_player_url)
data_url = html.xpath('//div[@id="lesson-video-content"]/@data-url')[0]
fina_m3u8_url = handle_start_m3u8_url(data_url)
handle_m3u8_data(fina_m3u8_url)
if __name__ == "__main__":
handle_login() # 登陆
get_video_url() # 下载
3.讲解
(附加github上开源的python m3u8 格式解析库,建议谷歌浏览器打开,自带翻译)
3.1 代码中的handle_m3u8_data()为主要内容,请看m3u8请求后的结果:
图片方框里的第一行可以看出这是aes-128加密,属于对称加密(附一个讲解的链接:Python与常见加密方式,后面的uri是加密的key值需要请求这个uri得到返回的结果:
后面的iv为密钥向量,可以看出每个视频片段的iv不同,key值相同,所以key的请求放到前面只请求一次。
第三行的url链接就是视频片段了,直接请求后解密就好了
3.2 至于handle_start_m3u8_url()函数解析的也是一个m3u8的返回结果:
最后一行的url才是上面代码解析的视频,图里其他的url是这个视频的其他url链接通道,简而言之,他们的通道路径不同,但是内容一样。(你自己平时看视频不也有好几个通道嘛,这个不行就换一个,至于里面的乱码,大概就是高清之类的意思)
3.3 其他的代码就不怎么需要讲解了
大致流程就是:请求key的uri获取key值—》请求视频返回加密的视频—》用key和iv对每个视频依次解密。
我要说几个注意的地方:
(1)每个视频片段的key值相同所以只需请求一次就好,不同的话会导致解密失败。
(2)还有下载下来的视频有一部分会存在一个问题:观看视频时拖动进度条画面会存在延迟现象,大概三四秒的样子。
(3)可能存在有些视频的加密方式不同,请自行百度,思路应该是一样的。
代码有些地方还不太完善,请自行解决,本文主要是讲解的思路和可能存在的问题,如有不妥之处,请详细指出,本人会虚心接受,大家共同讨论进步。
附加:
1.奇特的请求头(更新时间 (2020-02-22更新) )
最近因为一些原因就又去爬了一下视频(因为感觉爬取视频的主要用途在于下载限时免费的视频,很想看,但是太多了或者暂时没时间,就想先爬取下来保存看。)
主要是更新一个问题,在实际的爬取中可能会遇见这种请求头的格式:
header = {
':authority': 'hls.videocc.net',
':method': 'GET',
':path': '*********************************',
':scheme': 'https'
}
正常方式直接写入是会报错的,怎么办嘞?
在请求之前设置以下这个就可以了:
from hyper.contrib import HTTP20Adapter
session.mount('https://', HTTP20Adapter()) # import requests session = requests.session()
参考博文:Python 请求头header在http/http2下的问题
最后的爬取并未成功,因为它返回的key是加密的,需要对JS很熟练才行,寻找到前端对于key的处理,再用python复现出来。感觉JS不行,是很难爬取大型网站的。学习JS ing!