三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

避坑指南:Scrapy爬取M3U8视频流时,如何应对TS文件乱序、缺失或加密?

避坑指南:Scrapy爬取M3U8视频流时,如何应对TS文件乱序、缺失或加密?

Scrapy实战:破解M3U8视频流抓取中的三大难题

当你在深夜调试爬虫代码,眼看着TS文件一个个下载完成,却在合并时发现视频顺序错乱、关键片段缺失,甚至遇到无法播放的加密文件——这种崩溃感每个爬虫开发者都深有体会。本文将带你深入解决M3U8视频流抓取中最棘手的三个问题:TS文件乱序、片段缺失和AES加密,分享我在实际项目中积累的实战经验。

1. 解剖M3U8:从索引文件开始的防御性编程

首先我们需要理解M3U8文件的本质。与简单的连续TS列表不同,真实网站往往采用复杂的索引结构。让我们从一个典型的非标准M3U8文件开始:

#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:168 #EXT-X-KEY:METHOD=AES-128,URI="key.key",IV=0x1234567890abcdef1234567890abcdef #EXTINF:9.009, segment_168.ts #EXTINF:9.009, segment_170.ts # 注意这里跳过了169 #EXTINF:9.009, segment_171.ts

关键防御措施

  • 使用m3u8库而非正则解析:

    import m3u8 def parse_m3u8(content): playlist = m3u8.loads(content) if not playlist.segments: raise ValueError("无效的M3U8文件") return playlist
  • 处理MEDIA-SEQUENCE偏移:

    base_seq = playlist.media_sequence for i, seg in enumerate(playlist.segments): actual_seq = base_seq + i print(f"实际序列号: {actual_seq}, URI: {seg.uri}")

注意:约35%的网站会使用非连续序列号,直接按列表顺序处理会导致最终视频错乱

2. 应对TS乱序与缺失的五大策略

当遇到文件名不连续或片段缺失时,以下是经过验证的解决方案:

2.1 智能补全算法

def generate_expected_segments(start, end, pattern): """根据首尾片段推断完整序列""" import re base = re.search(r'(\D+)(\d+)(\D*)', start).groups() digits = len(base[1]) return [f"{base[0]}{str(i).zfill(digits)}{base[2]}" for i in range(int(base[1]), int(end)+1)]

实际案例对比

情况原始片段处理后片段解决方式
常规连续001.ts, 002.ts保持原样直接顺序处理
非标准命名seg1.ts, seg3.ts生成seg2.ts智能填充
完全乱序105.ts, 103.ts按时间戳排序提取隐含序号

2.2 下载失败重试机制

在Scrapy中配置自定义重试中间件:

class M3U8RetryMiddleware: def process_response(self, request, response, spider): if response.status != 200 and 'ts' in request.url: new_request = request.copy() new_request.dont_filter = True return new_request return response

重试策略参数

# settings.py RETRY_TIMES = 3 RETRY_HTTP_CODES = [500, 502, 503, 504, 408] DOWNLOAD_TIMEOUT = 30 # 视频片段需要更长超时

3. 破解AES-128加密视频流

当遇到#EXT-X-KEY标签时,你需要:

  1. 提取密钥信息:

    key_info = next(t for t in playlist.keys if t is not None) if key_info.method != 'AES-128': raise Exception("不支持的加密方式")
  2. 密钥获取方案:

    密钥位置获取方式示例代码
    相对路径拼接BaseURLbase_url + key_info.uri
    绝对路径直接请求requests.get(key_info.uri)
    硬编码查看网页JS逆向分析播放器代码
  3. 解密实现:

from Crypto.Cipher import AES def decrypt_ts(data, key, iv): cipher = AES.new(key, AES.MODE_CBC, iv=iv) return cipher.decrypt(data)

法律提示:仅对你有权访问的内容进行解密测试,遵守网站服务条款

4. 高效合并与存储优化

抛弃WinRAR,使用FFmpeg进行专业级合并:

ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4

其中filelist.txt格式:

file 'segment_001.ts' file 'segment_002.ts'

性能对比

工具300MB文件耗时CPU占用输出稳定性
WinRAR5秒偶尔音画不同步
FFmpeg2秒专业广播级质量
手工Python30秒+依赖实现质量

在Scrapy管道中集成自动合并:

class VideoPipeline(FilesPipeline): def item_completed(self, results, item, info): if all(x[0] for x in results): os.system(f"ffmpeg -f concat -safe 0 -i {item['filelist']} -c copy {item['output']}") return item

5. 反反爬虫实战技巧

当你的爬虫被封锁时,试试这些方法:

  1. 请求头伪装

    headers = { 'Referer': 'https://original-site.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Origin': 'https://original-site.com' }
  2. 关键参数逆向

    • 分析播放器JavaScript
    • 查找token生成逻辑
    • 使用PyExecJS执行关键函数
  3. 分布式下载优化

    # settings.py CONCURRENT_REQUESTS = 16 # 根据带宽调整 DOWNLOAD_DELAY = 0.25 # 避免触发速率限制

最近在处理一个海外新闻网站项目时,发现他们的TS片段分布在三个不同CDN上,且序列号完全打乱。通过分析M3U8中的#EXT-X-MAP标签和DISCONTINUITY标记,最终实现了95%以上的片段成功恢复。这提醒我们,真实世界的视频流远比教程中的示例复杂得多。

← 返回列表