images\cherry_red.png1 Python
      images\cherry_blue.png1.1 爬虫
         images\cherry_orange.png1.1.1 多线程
         images\cherry_orange.png1.1.2 B站
         images\cherry_orange.png1.1.3 zmq71多线程爬取
         images\cherry_orange.png1.1.4 jable.tv多线程爬取
            images\cherry_cyan.png1.1.4.1 jable.tv细节
         images\cherry_orange.png1.1.5 python执行js代码
         images\cherry_orange.png1.1.6 windows代理配置
      images\cherry_blue.png1.2 Linux编译升级3.9版本
      images\cherry_blue.png1.3 数据分析
         images\cherry_orange.png1.3.1 预测考研成绩
   images\cherry_red.png2 Python django
      images\cherry_blue.png2.1 目录层面说明
         images\cherry_orange.png2.1.1 urls.py
         images\cherry_orange.png2.1.2 settings.py
         images\cherry_orange.png2.1.3 M 模型数据库
         images\cherry_orange.png2.1.4 T templates/...html
         images\cherry_orange.png2.1.5 V(逻辑处理) views.py
      images\cherry_blue.png2.2 django模板
         images\cherry_orange.png2.2.1 模板标签
            images\cherry_cyan.png2.2.1.1 过滤器
            images\cherry_cyan.png2.2.1.2 标签
               images\cherry_orange_dark.png2.2.1.2.1 if/else
               images\cherry_orange_dark.png2.2.1.2.2 for
               images\cherry_orange_dark.png2.2.1.2.3 ifequal/ifnotequal
               images\cherry_orange_dark.png2.2.1.2.4 csrf_token
            images\cherry_cyan.png2.2.1.3 模板继承
         images\cherry_orange.png2.2.2 自定义标签和过滤器
      images\cherry_blue.png2.3 django模型ORM
         images\cherry_orange.png2.3.1 App应用
            images\cherry_cyan.png2.3.1.1 models.py
         images\cherry_orange.png2.3.2 SQL
            images\cherry_cyan.png2.3.2.1 新增
            images\cherry_cyan.png2.3.2.2 删除
            images\cherry_cyan.png2.3.2.3 更新
            images\cherry_cyan.png2.3.2.4 查询
         images\cherry_orange.png2.3.3 单表示例
         images\cherry_orange.png2.3.4 多表示例
         images\cherry_orange.png2.3.5 聚合查询
         images\cherry_orange.png2.3.6 分组查询
      images\cherry_blue.png2.4 django表单
         images\cherry_orange.png2.4.1 GET
         images\cherry_orange.png2.4.2 POST
         images\cherry_orange.png2.4.3 Request
      images\cherry_blue.png2.5 django视图
      images\cherry_blue.png2.6 django路由
      images\cherry_blue.png2.7 django Admin管理
      images\cherry_blue.png2.8 django组件
         images\cherry_orange.png2.8.1 Form页面组件
         images\cherry_orange.png2.8.2 Auth用户认证
         images\cherry_orange.png2.8.3 Cookie/Session
         images\cherry_orange.png2.8.4 中间件
         images\cherry_orange.png2.8.5 视图FBV/CBV
      images\cherry_blue.png2.9 django+nginx+uwsgi
      images\cherry_blue.png2.10 Python小知识
         images\cherry_orange.png2.10.1 def __int__(self):
         images\cherry_orange.png2.10.2 def __str__(self):
         images\cherry_orange.png2.10.3 @staticmethod
         images\cherry_orange.png2.10.4 @wraps
         images\cherry_orange.png2.10.5 pycharm
curl -L -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36' -H 'Referer: https://jable.tv/' -H 'Accept-L: zh-CN,zh;q=0.9' -H 'Sec-Fetch-User: ?1' -H 'Sec-Ch-Ua: "Not?A_Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"' -H 'Sec-Ch-Ua-Mobile: ?0' -H 'Sec-Ch-Ua-Platform: "Windows"' -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' -x http:127.0.0.1:10810https://jable.tv/videos/ssis-639/

windows版本,注意双引号转移!
curl -L -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" -H "Referer: https://jable.tv/" -H "Accept-L: zh-CN,zh;q=0.9" -H "Sec-Fetch-User: ?1" -H "Sec-Ch-Ua: \"Not?A_Brand\";v=\"99\", \"Chromium\";v=\"90\", \"Google Chrome\";v=\"90\"" -H "Sec-Ch-Ua-Mobile: ?0" -H "Sec-Ch-Ua-Platform: \"Windows\"" -H "Cache-Control: no-cache" -H "Pragma: no-cache" -x http://127.0.0.1:10810 https://jable.tv/videos/ssis-639/

import os
import re
from Crypto.Cipher import AES
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import shutil
import time

# 该网站需要代理相关的操作,需进行相关配置!
proxy_url = "http://127.0.0.1:10810"
# 视频下载链接
url = "https://jable.tv/videos/sdmm-093/"
file_tile = url.split('/')[4]
req_url = ""
iv = ""
# 线程池数量
thread_nums = 20

# 创建的临时目录
temp_dir = Path('./temp_dir')
temp_sol_dir = Path('./temp_sol')
vedio_dir = Path('./vedio')


def jable_init(proxy_url, url):
    global req_url
    global iv
    cmd = 'curl -L -o ./hlsUrl.txt -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" -H "Referer: https://jable.tv/" -H "Accept-L: zh-CN,zh;q=0.9" -H "Sec-Fetch-User: ?1" -H "Sec-Ch-Ua: \"Not?A_Brand\";v=\"99\", \"Chromium\";v=\"90\", \"Google  Chrome\";v=\"90\"" -H "Sec-Ch-Ua-Mobile: ?0" -H "Sec-Ch-Ua-Platform: \"Windows\"" -H "Cache-Control: no-cache" -H "Pragma: no-cache" -x ' + proxy_url + " " + url + ' >hlsUrl.txt'
    print(cmd)
    os.system(cmd)
    with open('./hlsUrl.txt', 'rb') as f:
        text = f.read()
    hlsUrl = re.findall(r"var hlsUrl = '(.*?)';", text.decode('utf-8'))
    rls = hlsUrl[0].split('/')
    # 修复一下,URL链接并不确定,需修复
    for i in range(len(rls) - 1):
        req_url = req_url + rls[i] + "/"

    cmd = r'curl -L  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" -H "Referer: https://jable.tv/" -H "Accept-L: zh-CN,zh;q=0.9" -H "Sec-Fetch-User: ?1" -H "Sec-Ch-Ua: \"Not?A_Brand\";v=\"99\", \"Chromium\";v=\"90\", \"Google  Chrome\";v=\"90\"" -H "Sec-Ch-Ua-Mobile: ?0" -H "Sec-Ch-Ua-Platform: \"Windows\"" -H "Cache-Control: no-cache" -H "Pragma: no-cache" -x ' + proxy_url + "  " + "\"" + \
          hlsUrl[0] + "\"" + ' > ./ts.txt'
    print(cmd)
    os.system(cmd)
    with open('./ts.txt', 'rb') as f:
        text = f.read()
    m3u8_ts = re.findall(r"\n(\d+\.ts)\n", text.decode('utf-8'))
    m3u8_key = re.findall(r'URI="([^"]+)"', text.decode('utf-8'))
    m3u8_iv = re.findall(r'IV=0x([0-9a-fA-F]+)', text.decode('utf-8'))
    iv = bytes.fromhex(m3u8_iv[0])
    # 获取m3u8 加密密钥
    cmd = 'curl -L -o ./temp_dir/m3u8.key -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" -H "Referer: https://jable.tv/" -H "Accept-L: zh-CN,zh;q=0.9" -H "Sec-Fetch-User: ?1" -H "Sec-Ch-Ua: \"Not?A_Brand\";v=\"99\", \"Chromium\";v=\"90\", \"Google  Chrome\";v=\"90\"" -H "Sec-Ch-Ua-Mobile: ?0" -H "Sec-Ch-Ua-Platform: \"Windows\"" -H "Cache-Control: no-cache" -H "Pragma: no-cache" -x  ' + proxy_url + " " + "\"" + req_url + \
          m3u8_key[0] + "\"" + ' > ./temp_dir/m3u8.key'
    os.system(cmd)
    return m3u8_ts


def get_content(u):
    cotent = "\"" + req_url + u + "\""
    cmd = 'curl --connect-timeout 30 --retry 5 -L -o ./temp_dir/' + u + ' -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" -H "Referer: https://jable.tv/" -H "Accept-L: zh-CN,zh;q=0.9" -H "Sec-Fetch-User: ?1" -H "Sec-Ch-Ua: \"Not?A_Brand\";v=\"99\", \"Chromium\";v=\"90\", \"Google  Chrome\";v=\"90\"" -H "Sec-Ch-Ua-Mobile: ?0" -H "Sec-Ch-Ua-Platform: \"Windows\"" -H "Cache-Control: no-cache" -H "Pragma: no-cache" -x ' + proxy_url + " " + cotent + ' > ./temp_dir/' + u
    os.system(cmd)


def m3u8_fix(m3u8_ts):
    key = open('./temp_dir/m3u8.key', 'rb')
    cipher = AES.new(key.read(), AES.MODE_CBC, iv)
    for ts in m3u8_ts:
        try:
            t = open('./temp_dir/' + ts, 'rb')
            tf = open('./temp_sol/' + ts, 'wb')
            tf.write(cipher.decrypt(t.read()))
            t.close()
            tf.close()
        except:
            print("ts子文件未成功下载%s"%ts)
    key.close()


def file_merging(m3u8_ts):
    with open('./vedio/' + file_tile + ".mp4", 'wb') as f:
        for ts in m3u8_ts:
            # 异常捕捉,存在部分子视频片段未成功下载
            try:
                # 注意文件名称是否符号规范,r以二进制的形式打开二进制文件
                with open('./temp_sol/' + ts, 'rb') as tf:
                    f.write(tf.read())
            except:
                print("ts子文件未成功下载%s"%ts)
    tf.close()
    f.close()
    shutil.rmtree('./temp_dir')
    shutil.rmtree('./temp_sol')
    print("合并文件完成")


if __name__ == '__main__':
    print('jable.tv多线程下载工具')
    start_time = time.time()
    temp_dir.mkdir(exist_ok=True)
    temp_sol_dir.mkdir(exist_ok=True)
    vedio_dir.mkdir(exist_ok=True)

    m3u8_ts = jable_init(proxy_url, url)
    print(m3u8_ts)
    # 开启线程池
    executor = ThreadPoolExecutor(max_workers=int(thread_nums))
    result = executor.map(get_content, m3u8_ts)
    # 关闭线程池
    executor.shutdown(wait=True)
    # m3u8 解密
    m3u8_fix(m3u8_ts)
    # 合并视频片段
    file_merging(m3u8_ts)
    end_time = time.time()
    print(" 已加载完毕" + " 花费时间: " + str(end_time - start_time) + ' s \n')