基于Python的爬虫项目(一)--- 下载m3u8视频(aes加密)

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请求后的结果:

m3u8返回结果

  图片方框里的第一行可以看出这是aes-128加密,属于对称加密(附一个讲解的链接:Python与常见加密方式,后面的uri是加密的key值需要请求这个uri得到返回的结果:

加密的keys值

  后面的iv为密钥向量,可以看出每个视频片段的iv不同,key值相同,所以key的请求放到前面只请求一次。
  第三行的url链接就是视频片段了,直接请求后解密就好了
3.2 至于handle_start_m3u8_url()函数解析的也是一个m3u8的返回结果:

m3u8返回结果

  最后一行的url才是上面代码解析的视频,图里其他的url是这个视频的其他url链接通道,简而言之,他们的通道路径不同,但是内容一样。(你自己平时看视频不也有好几个通道嘛,这个不行就换一个,至于里面的乱码,大概就是高清之类的意思)
3.3 其他的代码就不怎么需要讲解了
   大致流程就是:请求key的uri获取key值—》请求视频返回加密的视频—》用key和iv对每个视频依次解密。
  我要说几个注意的地方:
  每个视频片段的key值相同所以只需请求一次就好,不同的话会导致解密失败。
  还有下载下来的视频有一部分会存在一个问题:观看视频时拖动进度条画面会存在延迟现象,大概三四秒的样子。
  可能存在有些视频的加密方式不同,请自行百度,思路应该是一样的。
  代码有些地方还不太完善,请自行解决,本文主要是讲解的思路和可能存在的问题,如有不妥之处,请详细指出,本人会虚心接受,大家共同讨论进步。

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!


感谢大家支持!人生苦短,我用python!
原创文章 4 获赞 6 访问量 1395

猜你喜欢

转载自blog.csdn.net/weixin_42572656/article/details/96981292