Python实战:用列表推导式+Requests搞定M3U8视频下载,自动过滤广告.ts文件
2026/6/6 8:31:39 网站建设 项目流程

Python实战:构建M3U8视频下载器与广告过滤系统

每次在视频网站追剧时,那些突如其来的广告总是让人烦躁。作为开发者,我们完全可以用Python打造一个专属的下载工具,不仅能自动跳过广告片段,还能将视频保存到本地随时观看。本文将带你从零开始,用不到100行代码实现这个实用工具。

1. 环境准备与基础原理

在开始编码前,我们需要了解几个关键概念。M3U8是一种基于HTTP Live Streaming (HLS)协议的播放列表格式,它将视频分割成多个小的.ts文件,便于网络传输和自适应码率切换。我们的任务就是解析这个播放列表,下载所有真正的视频片段,同时过滤掉广告内容。

首先确保你的Python环境已安装必要的库:

pip install requests pycryptodome

注意:pycryptodome是替代Crypto的现代加密库,功能相同但维护更好

M3U8文件通常包含类似这样的内容:

#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXTINF:9.009, http://example.com/segment1.ts #EXTINF:9.009, http://ad.server.com/ad1.ts #EXTINF:9.009, http://example.com/segment2.ts

2. 智能广告过滤系统实现

广告识别是核心挑战。常见广告特征包括:

  • 特定域名(如ad.、ads.、doubleclick.net等)
  • URL中包含特定路径(如/ad/、/commercial/)
  • 文件命名模式(如ad_开头)

2.1 基础过滤方案

最简单的过滤方式是排除包含广告关键词的URL:

def filter_ads(lines): ad_keywords = ['ad.', 'ads.', 'doubleclick.net'] return [ line.strip() for line in lines if line.strip().endswith('.ts') and not any(keyword in line for keyword in ad_keywords) ]

2.2 高级动态过滤方案

更健壮的做法是将广告规则外部化,便于更新维护:

def load_filter_rules(rule_file='ad_rules.txt'): with open(rule_file) as f: return [line.strip() for line in f if line.strip()] def advanced_filter(lines, rules): return [ line.strip() for line in lines if line.strip().endswith('.ts') and not any(rule in line for rule in rules) ]

规则文件示例:

ad. ads. /ad/ /commercial/ tracking.

3. 稳健的下载引擎实现

下载大量小文件需要考虑网络异常、性能优化等问题。下面是增强版的下载器:

import requests from pathlib import Path import time def download_segments(urls, output_dir='output', max_retries=3): Path(output_dir).mkdir(exist_ok=True) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' } for idx, url in enumerate(urls, 1): filename = f"{idx:010d}.ts" # 10位数字填充 filepath = Path(output_dir) / filename for attempt in range(max_retries): try: resp = requests.get(url, headers=headers, timeout=20) resp.raise_for_status() with open(filepath, 'wb') as f: f.write(resp.content) print(f"下载成功: {filename}") break except Exception as e: print(f"尝试 {attempt+1}/{max_retries} 失败: {e}") if attempt == max_retries - 1: print(f"无法下载 {url}, 跳过...") time.sleep(2 ** attempt) # 指数退避

关键优化点:

  • 自动创建输出目录
  • 文件名自动补零确保正确排序
  • 指数退避重试机制
  • 详细的进度反馈

4. 视频合并与解密处理

4.1 基本合并方法

下载完成后,可以使用系统命令合并文件:

import subprocess def merge_ts_files(input_dir='output', output_file='output.mp4'): if not Path(input_dir).exists(): raise FileNotFoundError(f"目录 {input_dir} 不存在") # Windows系统使用copy命令 if os.name == 'nt': cmd = f'copy /b "{input_dir}\\*.ts" "{output_file}"' # Linux/Mac使用cat命令 else: cmd = f'cat {input_dir}/*.ts > {output_file}' subprocess.run(cmd, shell=True, check=True)

4.2 处理加密视频

遇到加密视频时,我们需要解密后再合并:

from Crypto.Cipher import AES def decrypt_ts_file(encrypted_data, key, iv=None): cipher = AES.new(key, AES.MODE_CBC, iv=iv) if iv else AES.new(key, AES.MODE_CBC) return cipher.decrypt(encrypted_data) def download_and_decrypt(url, key, output_path, iv=None): response = requests.get(url, timeout=10) if response.status_code == 200: decrypted_data = decrypt_ts_file(response.content, key, iv) with open(output_path, 'wb') as f: f.write(decrypted_data) return True return False

5. 完整工作流实现

将所有组件整合成一个完整的解决方案:

def process_m3u8(m3u8_url, output_video='output.mp4', ad_rules_file=None): print("正在下载M3U8文件...") m3u8_content = requests.get(m3u8_url).text.splitlines() print("过滤广告片段...") if ad_rules_file: rules = load_filter_rules(ad_rules_file) segments = advanced_filter(m3u8_content, rules) else: segments = filter_ads(m3u8_content) print(f"发现 {len(segments)} 个有效视频片段") download_segments(segments) print("合并视频文件...") merge_ts_files(output_file=output_video) print(f"视频已保存为 {output_video}")

使用示例:

if __name__ == '__main__': process_m3u8( 'http://example.com/playlist.m3u8', 'my_video.mp4', 'ad_rules.txt' )

6. 高级功能扩展

6.1 并发下载优化

使用线程池加速下载:

from concurrent.futures import ThreadPoolExecutor def concurrent_download(urls, output_dir='output', max_workers=5): with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = [] for idx, url in enumerate(urls, 1): filename = f"{idx:010d}.ts" futures.append(executor.submit( download_single_segment, url, output_dir, filename )) for future in futures: future.result() # 等待所有任务完成

6.2 自动识别加密视频

通过解析M3U8中的EXT-X-KEY标签处理加密:

def parse_encryption_info(m3u8_content): key_uri = None iv = None for line in m3u8_content: if line.startswith('#EXT-X-KEY'): parts = line.split(',') for part in parts: if 'URI=' in part: key_uri = part.split('=')[1].strip('"') elif 'IV=' in part: iv = part.split('=')[1].strip('"') return key_uri, iv

6.3 进度显示与断点续传

添加进度条和断点检查功能:

from tqdm import tqdm def download_with_progress(urls, output_dir='output'): existing_files = set(f.name for f in Path(output_dir).glob('*.ts')) with tqdm(total=len(urls), desc='下载进度') as pbar: for idx, url in enumerate(urls, 1): filename = f"{idx:010d}.ts" if filename in existing_files: pbar.update(1) continue download_single_segment(url, output_dir, filename) pbar.update(1)

7. 错误处理与日志记录

健壮的生产级代码需要完善的错误处理:

import logging logging.basicConfig( filename='video_downloader.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) def safe_download(url, output_path, max_retries=3): for attempt in range(max_retries): try: response = requests.get(url, timeout=15) response.raise_for_status() with open(output_path, 'wb') as f: f.write(response.content) logging.info(f"成功下载: {url}") return True except Exception as e: logging.warning(f"尝试 {attempt+1} 失败: {url} - {str(e)}") if attempt == max_retries - 1: logging.error(f"最终下载失败: {url}") return False time.sleep(2 ** attempt)

在实际项目中,这套系统帮我节省了大量追剧时间。最令人满意的是广告过滤功能,通过维护一个广告规则文件,可以持续更新应对各种新的广告URL模式。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询