吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]
查看: 3577|回复: 45
上一主题 下一主题
收起左侧

[Python 原创] 某鹅通视频地址分析及批量下载运行代码

  [复制链接]
跳转到指定楼层
楼主
threettiger 发表于 2026-3-21 07:57 回帖奖励
本帖最后由 threettiger 于 2026-3-26 08:02 编辑

在参考大佬 某鹅通最新方法  的基础上写的代码,代码分两部分,一部分是抓取课程链接,另一部分是批量下载视频文件。
获取视频下载链接地址代码,具体使用时请替换下你自己的网页版课程链接、Cookie、课程id,以及相关请求参数。
课程使用的某鹅通网页版,请求网页也需要替换你们课程的网页版地址。

我是为了下载小鹅通自己的学习视频,才搞的,虽然发了全部源码,发现很多朋友,还是不会用。教大家个简单的方法,下载个traecn,然后把代码复制进去,让AI帮你去调试,trae免费版也很强,2026了,不需要手搓代码了,你甚至可以把本帖子的链接直接丢给AI,让它去帮你阅读,然后帮你去写代码,最后成功跑通。

这里面小白,可能还是需要点网页调试的能力,这些都是基础的能力,现在AI大行其道,很多小朋友可能都不熟悉,我简单讲一下:
1.打开你的课程电脑版网址,记得要登录(至少你有看视频的权限),到课程页面,然后按F12,调出开发者工具,找到网络,

2.输入比如我们获取视频详细信息的接口是:https://appvvutermf4498.h5.xiaoecloud.com/xe.course.business.video.detail_info.get/2.0.0
你在筛选器里面输入筛选下内容detail_info,筛选一下,接口很多,开始的时候需要自己去分析的。

看到接口后,就可以看到请求头信息,负载信息,响应信息了,你可以右键复制这些丢给AI,让AI帮你去替换网页版课程链接、Cookie、课程id,以及相关请求参数。


这个不是小白的无脑使用工具,本来就是我自己下载视频用的,借鉴的也是论坛大佬的破解方法和思路, 鼓励大家动手试试。网络爬虫,API调试,web逆向,这些都是最最基础的。我看评论区还是有些人不会,所以补充啰嗦了下。大佬跳过。

[Python] 纯文本查看 复制代码
import requests, json, re, os, base64, ssl, urllib3, concurrent.futures

urllib3.disable_warnings()

# ssl._create_default_https_context = ssl._create_unverified_context


headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36',
    'Accept-Encoding': 'gzip, deflate, br',
    'Cookie': 'xenbyfpfUnhLsdkZbX=0; ko_token=ffcfaf82d99e32af1695286910e7510c; newuserdays=90; olduserdays=180; regtime=1689246133; shop_version_type=4; sensorsdata2015jssdkcross=%7B%22%24device_id%22%3A%2219d08adcd9f8de-04410a3b780ae48-5704343e-400760-19d08adcda03e2%22%7D; sajssdk_2015_new_user_appvvutermf4498_h5_xiaoecloud_com=1; sa_jssdk_2015_appvvutermf4498_h5_xiaoecloud_com=%7B%22distinct_id%22%3A%22u_64afd9b51ff0c_jEaSdgUTpb%22%2C%22first_id%22%3A%2219d08adcd9f8de-04410a3b780ae48-5704343e-400760-19d08adcda03e2%22%2C%22props%22%3A%7B%7D%7D; logintime=1773967151',
    'Referer': 'https://appvvutermf4498.h5.xiaoecloud.com/',
    'Origin': 'https://appvvutermf4498.h5.xiaoecloud.com',
    'Host': 'appvvutermf4498.h5.xiaoecloud.com',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Accept': 'application/json, text/plain, */*',
    'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"',
    'sec-ch-ua-mobile': '?1',
    'sec-ch-ua-platform': '"iOS"',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-origin',
    'req-uuid': '20260320083913000905730',
    'retry': '1',
}

# 解密小鹅通url播放地址
s = "W$siZGVmaW5pdGlvbl9uYW@lIjoiXHU5YWQ%XHU#ZTA@IiwiZGVmaW5pdGlvbl9wIjoiNzIwUCIsInVybCI6Imh0dHBzOlwvXC9jLXZvZC@ody@rLnhpYW9la#5vdy5jb#@cL#Fzc#V0XC9jM#Q5YzBhZDM#ZWQ5OGZiZTQzMGMwYTQ%MGYwZjdjNlwvYzkxNzBjN#Q%NjkyNjBhMzI0OGYzZWQ0ZGUwNTQxZWUubTN@OD9zaWduPTBmOTdjOTFhMzM5NDIzZWMwMDM%NTc0N#E@MmJmYjYyJnQ9NjVhNTdlZDgmdXM9ZkxLdGN%VEFtUyIsImlzX$N@cHBvcnQiOmZhbHNlLCJleHQiOnsiaG9zdCI6Imh0dHBzOlwvXC9jLXZvZC@ody@rLnhpYW9la#5vdy5jb#0iLCJwYXRoIjoiYXNzZXRcL#MzZDljMGFkMzZlZDk%ZmJlNDMwYzBhNDgwZjBmN#M#IiwicGFyYW0iOiJzaWduPTBmOTdjOTFhMzM5NDIzZWMwMDM%NTc0N#E@MmJmYjYyJnQ9NjVhNTdlZDgmdXM9ZkxLdGN%VEFtUyJ9fV0=__ba"


# def decode_url(s):
#     s = s.replace("@", "1").replace("#", "2").replace("$", "3").replace("%", "4").replace("__ba", "")
#     json_str = base64.b64decode(s.encode("utf-8")).decode("utf-8")[1:-1]
#     jjson = json.loads(json_str)
#     video_url = jjson.get("url")
#     return video_url


def decode_url(s):
    s = s.replace("@", "1").replace("#", "2").replace("$", "3").replace("%", "4").replace("__ba", "")
    try:
        # 先解码base64
        decoded_bytes = base64.b64decode(s.encode("utf-8"))
        json_str = decoded_bytes.decode("utf-8")
        
        # 移除可能存在的前后引号
        if json_str.startswith('"') and json_str.endswith('"'):
            json_str = json_str[1:-1]
        
        # 解析JSON
        jjson = json.loads(json_str)
        
        if isinstance(jjson, list):
            # 如果是数组,返回第一个视频URL(通常是高清)
            if jjson and isinstance(jjson[0], dict):
                video_url = jjson[0].get("url")
                return video_url
            else:
                print("视频URL数组格式不正确")
        elif isinstance(jjson, dict):
            # 如果是单个字典
            video_url = jjson.get("url")
            return video_url
        else:
            print("无法确定数据结构")
    except Exception as e:
        print(f"解码URL时出错:{e}")
        return None


def get_video_url(resource_id):
    global headers
    # 使用浏览器中的正确API端点
    url = "https://appvvutermf4498.h5.xiaoecloud.com/xe.course.business.video.detail_info.get/2.0.0"
    data = {
        'bizData[resource_id]': resource_id,
        'bizData[product_id]': "p_5ef1f421a523f_xHcyCVLp",
        'bizData[opr_sys]': "MacIntel"  # 使用浏览器中的操作系统
    }
    
    # 创建专用的请求头,确保包含所有必要信息
    video_headers = headers.copy()
    video_headers['Referer'] = f"https://appvvutermf4498.h5.xiaoecloud.com/p/course/video/{resource_id}?product_id=p_5ef1f421a523f_xHcyCVLp"
    
    try:
        # 添加超时设置
        response = requests.post(url, headers=video_headers, data=data, verify=False, timeout=30)
        
        if not response.text.strip():
            print(f"警告: 获取视频URL时收到空响应,resource_id: {resource_id}")
            return None
        
        r = json.loads(response.text)
        # 只打印状态码和必要信息,减少内存使用
        print(f"视频详情响应状态: {r.get('code')}")
        if r.get('code') == 0 and 'data' in r:
            video_urls = decode_url(r['data']['video_urls'])
            return video_urls
        else:
            print(f"视频详情请求失败: {r.get('msg')}")
            return None
    except requests.exceptions.Timeout:
        print(f"请求超时,resource_id: {resource_id}")
        return None
    except requests.exceptions.RequestException as e:
        print(f"网络请求错误: {e}, resource_id: {resource_id}")
        return None
    except (json.JSONDecodeError, KeyError) as e:
        print(f"解析视频URL时出错: {e}, resource_id: {resource_id}")
        return None
    except Exception as e:
        print(f"获取视频URL时发生未知错误: {e}, resource_id: {resource_id}")
        return None


# 测试解码功能
url = decode_url(s)
print(f"解码测试结果: {url}")

# 获取课程列表功能(分页获取全部视频)
url = "https://appvvutermf4498.h5.xiaoecloud.com/xe.course.business.column.items.get/2.0.0"
list_headers = headers.copy()
list_headers['Referer'] = 'https://appvvutermf4498.h5.xiaoecloud.com/p/course/column/p_5ef1f421a523f_xHcyCVLp'

# 先获取课程总数量和总页数
page_size = 20  # 每页数量
page_index = 1

total_count = 0
all_video_courses = []

# 第一次请求获取总数量
first_data = {
    'bizData[column_id]': 'p_5ef1f421a523f_xHcyCVLp',
    'bizData[page_index]': str(page_index),
    'bizData[page_size]': str(page_size),
    'bizData[sort]': 'desc'
}

first_response = requests.post(url, headers=list_headers, data=first_data, verify=False)

if first_response.status_code == 200 and first_response.text.strip():
    try:
        first_r = json.loads(first_response.text)
        if first_r.get('code') == 0 and 'data' in first_r:
            data_content = first_r['data']
            if isinstance(data_content, dict):
                # 获取总数量和总页数
                if 'total' in data_content:
                    total_count = data_content['total']
                    print(f"课程总数量: {total_count}")
                elif 'total_num' in data_content:
                    total_count = data_content['total_num']
                    print(f"课程总数量: {total_count}")
                else:
                    print("未找到总数量字段,将使用列表长度作为总数量")
                    if 'list' in data_content:
                        total_count = len(data_content['list'])
                        print(f"假设总数量: {total_count}")
                
                if total_count > 0:
                    # 计算总页数
                    import math
                    total_pages = math.ceil(total_count / page_size)
                    print(f"总页数: {total_pages}")
                    
                    # 获取所有页的视频课程信息
                    for page in range(1, total_pages + 1):
                        print(f"\n正在获取第 {page}/{total_pages} 页...")
                        
                        page_data = {
                            'bizData[column_id]': 'p_5ef1f421a523f_xHcyCVLp',
                            'bizData[page_index]': str(page),
                            'bizData[page_size]': str(page_size),
                            'bizData[sort]': 'desc'
                        }
                        
                        page_response = requests.post(url, headers=list_headers, data=page_data, verify=False, timeout=30)
                        
                        if page_response.status_code == 200 and page_response.text.strip():
                            try:
                                page_r = json.loads(page_response.text)
                                if page_r.get('code') == 0 and 'data' in page_r and 'list' in page_r['data']:
                                    current_list = page_r['data']['list']
                                    print(f"第 {page} 页获取到 {len(current_list)} 个课程")
                                    
                                    for i in current_list:
                                        if i.get('resource_type') == 3:
                                            # 收集视频课程信息,稍后使用多线程获取URL
                                            all_video_courses.append({
                                                'title': i.get('resource_title', '未知'),
                                                'resource_id': i['resource_id']
                                            })
                                else:
                                    print(f"第 {page} 页请求失败: {page_r.get('msg')}")
                            except json.JSONDecodeError as e:
                                print(f"第 {page} 页JSON解析错误: {e}")
                        else:
                            print(f"第 {page} 页请求失败,状态码: {page_response.status_code}")
                    
                    # 使用多线程获取视频URL
                    if all_video_courses:
                        print(f"\n开始使用多线程获取 {len(all_video_courses)} 个视频URL...")
                        all_video_info = []
                        
                        # 定义一个线程安全的回调函数
                        def process_video(course):
                            title = course['title']
                            resource_id = course['resource_id']
                            print(f"正在处理视频: {title}")
                            video_url = get_video_url(resource_id)
                            if video_url:
                                return {
                                    'chapterName': title,
                                    'filePath': video_url
                                }
                            return None
                        
                        # 创建线程池,最大线程数设置为10
                        with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
                            # 提交所有任务
                            future_to_course = {
                                executor.submit(process_video, course): course 
                                for course in all_video_courses
                            }
                            
                            # 收集结果
                            for future in concurrent.futures.as_completed(future_to_course):
                                result = future.result()
                                if result:
                                    all_video_info.append(result)
                        
                        # 保存所有视频信息
                        if all_video_info:
                            file_name = 'D:/code/python/test2/xiaoetong/小鹅通高项课程2026.json'
                            with open(file_name, 'w', encoding='utf-8') as f:
                                json.dump(all_video_info, f, ensure_ascii=False, indent=4)
                            print(f"\n已保存{len(all_video_info)}个视频信息到{file_name}")
                        else:
                            print("\n没有获取到任何视频URL")
    except Exception as e:
        print(f"获取课程列表时出错: {e}")
        import traceback
        traceback.print_exc()
else:
    print(f"请求失败,状态码:{first_response.status_code}")
    print(f"响应内容: {first_response.text}")



批量下载代码:

[Python] 纯文本查看 复制代码
import requests
import imageio_ffmpeg
import os
import json
import time
import re
import shutil
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urljoin, urlparse
from Crypto.Cipher import AES  # 需要安装pycryptodome
import binascii

# 设置日志级别
DEBUG = True

# 创建video目录和temp目录(如果不存在)
output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "video")
temp_dir = os.path.join(output_dir, "temp")
os.makedirs(output_dir, exist_ok=True)
os.makedirs(temp_dir, exist_ok=True)
print(f"Output directory: {output_dir}")
print(f"Temp directory: {temp_dir}")

# 处理文件名,确保合法
def sanitize_filename(filename):
    # 替换或移除非法字符
    return re.sub(r'[<>"/\\|?*]', '', filename)

# 创建全局会话,用于复用连接
session = requests.Session()
session.headers.update({
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
})

# 下载单个视频片段
def download_segment(segment_url, segment_file, encryption_key=None):
    try:
        if DEBUG:
            print(f"Downloading segment: {segment_url}")
        response = session.get(segment_url, timeout=60)  # 使用会话,增加超时
        response.raise_for_status()  # 检查HTTP错误
        
        content = response.content
        
        # 如果有加密密钥,解密TS文件
        if encryption_key:
            try:
                # AES-128解密,IV通常是文件编号(从文件名提取)
                iv_match = re.search(r'segment_\d+_(\d+)\.ts', segment_file)
                if iv_match:
                    segment_number = int(iv_match.group(1))
                    # 使用段号作为IV(16字节)
                    iv = segment_number.to_bytes(16, byteorder='big')
                    
                    # 创建AES解密器
                    cipher = AES.new(encryption_key, AES.MODE_CBC, iv=iv)
                    
                    # 解密内容(需要是16字节的倍数)
                    if len(content) % 16 == 0:
                        content = cipher.decrypt(content)
                        # 去除PKCS7填充
                        padding_len = content[-1]
                        content = content[:-padding_len]
                    else:
                        print(f"Warning: Segment {segment_file} length not multiple of 16, skipping decryption")
                else:
                    print(f"Warning: Could not extract segment number from {segment_file}, skipping decryption")
            except Exception as e:
                print(f"Error decrypting segment {segment_file}: {e}")
                # 保留原始内容,不中断下载
        
        with open(segment_file, 'wb') as f:
            f.write(content)
        if DEBUG:
            print(f"Segment saved: {segment_file}")
        return segment_file
    except Exception as e:
        print(f"Error downloading segment {segment_url}: {e}")
        raise

# 下载单个视频
def download_video_from_url(video_url, output_file, video_index):
    try:
        print(f"\nStarting download for: {output_file}")
        
        # 解析URL,获取基础URL和m3u8路径
        parsed_url = urlparse(video_url)
        base_url = f"{parsed_url.scheme}://{parsed_url.netloc}{os.path.dirname(parsed_url.path)}/"
        m3u8_path = os.path.basename(parsed_url.path) + "?" + parsed_url.query if parsed_url.query else os.path.basename(parsed_url.path)

        # 下载m3u8文件(使用会话提高速度)
        print(f"Downloading m3u8 file from {video_url}")
        response = session.get(video_url, timeout=30)
        response.raise_for_status()  # 检查HTTP错误
        m3u8_content = response.text
        
        # 不保存m3u8文件到本地,直接处理内容
        
        # 检查m3u8内容是否包含加密信息
        encryption_key = None
        if "#EXT-X-KEY" in m3u8_content:
            print("Encrypted HLS stream detected (AES-128)")
            
            # 提取加密信息
            import re
            key_matches = re.findall(r'#EXT-X-KEY:([^\n]+)', m3u8_content)
            if key_matches:
                key_info = key_matches[0]
                print(f"Encryption details: {key_info}")
                
                # 提取密钥URL
                key_url_match = re.search(r'URI="([^"]+)"', key_info)
                if key_url_match:
                    key_url = key_url_match.group(1)
                    print(f"Downloading encryption key from: {key_url}")
                    
                    # 下载密钥
                    try:
                        key_response = session.get(key_url, timeout=30)
                        key_response.raise_for_status()
                        encryption_key = key_response.content
                        print(f"Successfully downloaded encryption key ({len(encryption_key)} bytes)")
                    except Exception as e:
                        print(f"Error downloading encryption key: {e}")
                        encryption_key = None
        
        if DEBUG:
            print(f"M3U8 content length: {len(m3u8_content)} bytes")
            print(f"M3U8 content preview: {m3u8_content[:200]}...")

        # 提取所有视频片段URL
        segments = [line.strip() for line in m3u8_content.split('\n') if line and not line.startswith("#")]
        print(f"Found {len(segments)} segments")
        
        if not segments:
            print(f"No segments found in m3u8 file")
            return
        
        segment_files = []

        # 生成固定的时间戳,用于所有segment文件名
        timestamp = int(time.time() * 1000)
        
        # 为当前视频创建独立的临时目录
        video_temp_dir = os.path.join(temp_dir, f"video_{video_index}")
        os.makedirs(video_temp_dir, exist_ok=True)
        print(f"Using temporary directory: {video_temp_dir}")
        
        # 创建一个字典来存储片段URL和对应的文件路径
        segment_info = {}
        
        # 多线程下载视频片段(增加到16个线程以提高下载速度)
        with ThreadPoolExecutor(max_workers=16) as executor:
            futures = []
            for i, segment_url in enumerate(segments):
                absolute_url = urljoin(base_url, segment_url)
                segment_file = os.path.join(video_temp_dir, f"segment_{timestamp}_{i}.ts")  # 保存到视频专用临时目录
                segment_info[absolute_url] = segment_file
                futures.append(executor.submit(download_segment, absolute_url, segment_file, encryption_key))

            # 等待所有片段下载完成
            downloaded_count = 0
            failed_count = 0
            for future in as_completed(futures):
                try:
                    result = future.result()
                    downloaded_count += 1
                    segment_files.append(result)  # 只添加成功下载的文件
                    print(f"Downloaded segment {downloaded_count}/{len(segments)}")
                except Exception as e:
                    failed_count += 1
                    print(f"Error in segment download: {e}")

        print(f"Download complete: {downloaded_count} segments downloaded successfully, {failed_count} failed")

        # 检查是否有足够的片段下载成功
        if not segment_files:
            print("No segments were downloaded successfully")
            return
            
        # 确保片段按顺序排列(按数字排序)
        segment_files.sort(key=lambda x: int(x.split('_')[-1].split('.')[0]))  # 提取数字部分进行排序

        # 合并视频片段
        print(f"Merging {len(segment_files)} segments into {output_file}")
        
        # 使用Python直接合并TS文件为TS格式
        merge_success = False
        try:
            # 过滤掉不存在的文件
            existing_segment_files = []
            for seg_file in segment_files:
                if os.path.exists(seg_file):
                    existing_segment_files.append(seg_file)
                else:
                    print(f"Warning: Segment file not found: {seg_file}")
            
            if not existing_segment_files:
                print("Error: No existing segment files found for merging")
                return
                
            print(f"Found {len(existing_segment_files)} existing segment files for merging")
            
            # 调试:显示前几个文件
            print("First 5 segment files:")
            for i, seg_file in enumerate(existing_segment_files[:5]):
                print(f"  {i+1}: {seg_file} (Size: {os.path.getsize(seg_file)} bytes)")
            
            # 创建临时TS格式文件
            ts_output_file = output_file.replace('.mp4', '_temp.ts')
            print(f"\nMerging all TS segments directly into {ts_output_file}...")
            
            # 合并文件
            with open(ts_output_file, 'wb') as outfile:
                total_size = 0
                for i, seg_file in enumerate(existing_segment_files):
                    with open(seg_file, 'rb') as infile:
                        content = infile.read()
                        outfile.write(content)
                        total_size += len(content)
                    if (i + 1) % 100 == 0 or i + 1 == len(existing_segment_files):
                        print(f"  Merged {i + 1}/{len(existing_segment_files)} segments ({total_size/1024/1024:.1f} MB)")
            
            # 检查输出文件
            if os.path.exists(ts_output_file):
                file_size = os.path.getsize(ts_output_file)
                print(f"Successfully merged TS segments into temporary file {ts_output_file} (Size: {file_size} bytes)")
                
                # 使用FFmpeg将TS转换为MP4
                print(f"Converting TS to MP4: {output_file}...")
                try:
                    # 构建FFmpeg命令
                    import shutil
                    ffmpeg_path = shutil.which('ffmpeg')
                    if not ffmpeg_path:
                        print("Error: FFmpeg not found in PATH")
                        print("You can manually convert TS to MP4 using:")
                        print(f"  ffmpeg -i '{ts_output_file}' -c copy '{output_file}'")
                        merge_success = False  # FFmpeg不存在,无法转换,合并失败
                        return
                    
                    ffmpeg_cmd = [
                        ffmpeg_path,
                        '-i', ts_output_file,
                        '-c', 'copy',  # 直接复制流,不重新编码
                        '-y',  # 覆盖输出文件
                        output_file
                    ]
                    
                    # 使用UTF-8编码处理输出,避免UnicodeDecodeError
                    result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore')
                    
                    if result.returncode == 0:
                        # 检查MP4文件是否创建成功且大小合理
                        if os.path.exists(output_file) and os.path.getsize(output_file) > 0:
                            mp4_size = os.path.getsize(output_file)
                            print(f"Successfully converted to MP4: {output_file} (Size: {mp4_size} bytes)")
                            merge_success = True
                            
                            # 删除临时TS文件
                            if os.path.exists(ts_output_file):
                                os.remove(ts_output_file)
                        else:
                            print(f"Error: MP4 output file {output_file} was not created or is empty")
                            merge_success = False
                    else:
                        print(f"FFmpeg conversion failed with return code: {result.returncode}")
                        print(f"FFmpeg stderr: {result.stderr}")
                        print("You can manually convert TS to MP4 using:")
                        print(f"  ffmpeg -i '{ts_output_file}' -c copy '{output_file}'")
                        merge_success = False  # 转换失败,合并失败
                except Exception as e:
                    print(f"Error converting TS to MP4: {e}")
                    print("You can manually convert TS to MP4 using:")
                    print(f"  ffmpeg -i '{ts_output_file}' -c copy '{output_file}'")
                    merge_success = False  # 转换异常,合并失败
            else:
                print(f"Error: Temporary TS file {ts_output_file} was not created")
                merge_success = False
            
        except Exception as e:
            print(f"Error merging segments: {e}")
            import traceback
            traceback.print_exc()
            
            # 不要清理临时文件,以便调试
            print("Keeping temporary files for debugging due to merge failure")
            return

        # 清理临时文件(只有合并成功后才清理)
        if merge_success:
            try:
                # 使用批量删除提高效率,不显示详细信息
                if os.path.exists(video_temp_dir):
                    start_time = time.time()
                    shutil.rmtree(video_temp_dir)
                    cleanup_time = time.time() - start_time
                    if cleanup_time > 5:  # 只有清理时间超过5秒才显示信息
                        print(f"Cleanup took {cleanup_time:.2f} seconds")
            except Exception as e:
                print(f"Error cleaning up temporary files: {e}")
            print(f"Successfully downloaded and merged: {output_file}")
        else:
            print(f"Merge failed, not cleaning temporary files for debugging")
        
    except Exception as e:
        print(f"Error downloading video {video_url}: {e}")
        # 清理临时文件
        try:
            if os.path.exists(video_temp_dir):
                shutil.rmtree(video_temp_dir)
        except:
            pass
        # 重新抛出异常,让调用者知道下载失败
        raise

# 从JSON文件下载所有视频
def download_all_videos(json_file):
    try:
        with open(json_file, 'r', encoding='utf-8') as f:
            videos = json.load(f)

        total_videos = len(videos)
        print(f"Found {total_videos} videos in total")
        print(f"Output directory: {output_dir}")
        
        # 准备所有视频下载任务
        video_tasks = []
        for i, video in enumerate(videos):
            video_name = sanitize_filename(video["chapterName"])
            output_file = os.path.join(output_dir, f"{i+1:03d}_{video_name}.mp4")
            video_url = video["filePath"]
            
            # 检查文件是否已存在
            if os.path.exists(output_file):
                print(f"Video {i+1}/{total_videos} already exists, skipping: {output_file}")
                continue
            
            video_tasks.append((video_url, output_file, i+1))
        
        remaining_videos = len(video_tasks)
        print(f"Will download {remaining_videos} videos (skipping {total_videos - remaining_videos} already downloaded)")
        
        if remaining_videos == 0:
            print("\nAll videos are already downloaded!")
            return
        
        # 使用多线程同时下载5个视频任务
        max_workers = 11  # 同时下载的视频数量
        print(f"\nStarting download with {max_workers} concurrent video tasks...")
        
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            # 创建任务
            futures = {}
            for i, (video_url, output_file, video_index) in enumerate(video_tasks):
                print(f"Scheduling video {video_index}/{total_videos}: {output_file}")
                future = executor.submit(download_video_from_url, video_url, output_file, video_index)
                futures[future] = (video_index, output_file)
            
            # 处理结果
            completed = 0
            failed = 0
            for future in as_completed(futures):
                video_index, output_file = futures[future]
                try:
                    future.result()
                    completed += 1
                    print(f"&#10003; Video {video_index} completed: {output_file}")
                except Exception as e:
                    failed += 1
                    print(f"&#10007; Video {video_index} failed: {e}")
                
                print(f"Progress: {completed + failed}/{remaining_videos} (Completed: {completed}, Failed: {failed})")

        print("\nAll videos processed!")
        print(f"Summary: {completed} videos downloaded successfully, {failed} failed")
        
    except Exception as e:
        print(f"Error in download_all_videos: {e}")

if __name__ == "__main__":
    json_file = "d:\\code\\python\\test2\\xiaoetong\\小鹅通高项课程2026.json"
    download_all_videos(json_file)



免费评分

参与人数 8吾爱币 +13 热心值 +8 收起 理由
sqlddos + 1 + 1 我很赞同!
pjbl + 1 + 1 用心讨论,共获提升!
bnm11 + 1 + 1 我很赞同!
iKunNice + 1 + 1 非常带派,这个真是好东西!楼主好人
JinxBoy + 1 谢谢@Thanks!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
qq63 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
tzxinqing + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
calvin33 发表于 2026-3-21 18:48
某鹅通视频,有些付费课程不允许网页观看,只能在手机进行观看,这种是不是要投屏到电脑解决
沙发
xixicoco 发表于 2026-3-21 13:43
3#
zwh8698 发表于 2026-3-21 14:06
4#
wupeiwupei 发表于 2026-3-21 14:17
好,好,好,大好人啊~!给楼主点赞!
5#
dork 发表于 2026-3-21 14:26
这个含金量没受过它的折磨的人不知道有多高
6#
nonameled 发表于 2026-3-21 14:51
感谢大佬源码分享
7#
JikeCoolTech 发表于 2026-3-21 15:19
怎么用?没看懂
头像被屏蔽
8#
long679 发表于 2026-3-21 16:02
提示: 作者被禁止或删除 内容自动屏蔽
9#
sanfengzhang 发表于 2026-3-21 17:46
认真拜读了一番。
10#
sanfengzhang 发表于 2026-3-21 17:59
那个课程是收费的。没付费不能试了。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-21 20:04

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表