你是否遇到过这样的情况:在微信小程序中发现了一个非常有价值的教学视频或精彩的娱乐内容,想要保存下来反复观看或离线使用,却发现小程序没有提供下载按钮?这种情况对于学生、自媒体从业者或需要收集素材的用户来说尤为常见。本文将带你一步步解决这个痛点,无需深厚的编程基础,只需按照我们的详细指导操作,就能轻松实现微信小程序视频的下载与合并。
与常见的录屏方式相比,直接获取视频源文件有着明显的优势:画质无损、文件体积更小、不受屏幕分辨率限制。更重要的是,一旦掌握了这项技能,你可以批量处理多个视频,大幅提升工作效率。我们将使用Fiddler这一专业网络调试工具捕获视频流,然后通过Python脚本自动化下载和合并过程。
Fiddler是一款免费的Web调试代理工具,能够记录计算机和互联网之间的所有HTTP/HTTPS通信。首先,我们需要从官方网站下载并安装最新版本的Fiddler Classic。安装过程与常规软件无异,只需一路点击"下一步"即可完成。
安装完成后,打开Fiddler进行初始配置:
启用HTTPS解密:这是关键步骤,因为微信小程序的通信基本都是加密的
配置连接设置:
过滤微信流量:
注意:首次安装证书后,建议重启Fiddler和微信客户端以确保所有设置生效。如果在后续步骤中遇到证书警告,可能需要手动将Fiddler根证书添加到受信任的根证书颁发机构。
为了让微信小程序的流量经过Fiddler,我们需要配置系统代理:
验证配置是否成功:
bash复制curl -v http://www.example.com --proxy http://127.0.0.1:8888
如果看到Fiddler捕获到了这次请求,说明代理设置正确。现在可以打开微信PC版,准备捕获小程序视频流量了。
在微信中打开包含目标视频的小程序,开始播放视频。此时Fiddler会捕获到大量请求,我们需要从中筛选出视频流:
典型的视频流请求特征:
| 特征项 | 说明 | 示例 |
|---|---|---|
| URL结构 | 包含m3u8或ts扩展名 | .../playlist.m3u8 |
| 响应类型 | 通常是video/MP2T | Content-Type: video/MP2T |
| 请求方法 | 通常是GET | GET /video/segment1.ts |
M3U8是一种基于文本的播放列表格式,用于指定媒体片段的位置。它实际上是M3U播放列表格式的UTF-8编码版本。当你在小程序中播放视频时,播放器首先获取M3U8文件,然后根据其中的索引下载各个TS片段进行播放。
一个典型的M3U8文件内容如下:
code复制#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.000000,
segment00001.ts
#EXTINF:10.000000,
segment00002.ts
#EXTINF:10.000000,
segment00003.ts
#EXT-X-ENDLIST
这段文本表示视频被分割为3个10秒长的TS片段。我们的目标就是获取所有这些TS片段的URL,然后批量下载。
在Fiddler中,找到视频请求后,我们需要记录以下关键信息用于后续Python脚本:
将这些信息整理为Python脚本可用的格式:
python复制headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...',
'Host': 'video.example.com',
'Referer': 'https://servicewechat.com/...'
}
base_url = 'https://video.example.com/path/segment{}.ts'
考虑到网络波动和服务器限制,我们的下载脚本需要具备以下功能:
以下是实现这些功能的Python代码框架:
python复制import requests
import os
from tqdm import tqdm # 进度条库
def download_ts_segments(base_url, headers, save_dir, max_retry=3):
if not os.path.exists(save_dir):
os.makedirs(save_dir)
segment_num = 1
retry_count = 0
max_segments = 1000 # 安全限制,防止无限循环
with tqdm(desc="下载进度") as pbar:
while segment_num <= max_segments and retry_count < max_retry:
# 格式化序号为5位数,如00001
seq = f"{segment_num:05d}"
url = base_url.format(seq)
save_path = os.path.join(save_dir, f"{seq}.ts")
try:
response = requests.get(url, headers=headers, stream=True, timeout=10)
if response.status_code == 200:
with open(save_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
pbar.update(1)
segment_num += 1
retry_count = 0 # 重置重试计数器
else:
retry_count += 1
if retry_count >= max_retry:
break
except Exception as e:
print(f"下载片段{seq}时出错: {str(e)}")
retry_count += 1
print(f"下载完成,共下载{segment_num-1}个片段")
在实际操作中,你可能会遇到以下问题及解决方案:
403 Forbidden错误:
下载速度慢:
TS片段顺序错乱:
多线程下载的改进版本:
python复制from concurrent.futures import ThreadPoolExecutor
def download_single_ts(args):
seq, base_url, headers, save_dir = args
url = base_url.format(f"{seq:05d}")
save_path = os.path.join(save_dir, f"{seq:05d}.ts")
for _ in range(3): # 重试3次
try:
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
with open(save_path, 'wb') as f:
f.write(response.content)
return True
except:
continue
return False
def download_concurrently(base_url, headers, save_dir, max_workers=5):
with ThreadPoolExecutor(max_workers=max_workers) as executor:
results = list(tqdm(
executor.map(download_single_ts,
[(i, base_url, headers, save_dir) for i in range(1, 1000)]),
total=1000
))
return sum(results) # 返回成功下载的数量
下载完成后,我们需要将所有TS片段合并为一个完整的视频文件。最简单的方法是使用二进制合并:
python复制def merge_ts_files(ts_dir, output_file):
ts_files = sorted([f for f in os.listdir(ts_dir) if f.endswith('.ts')])
with open(output_file, 'wb') as merged:
for ts_file in tqdm(ts_files, desc="合并进度"):
with open(os.path.join(ts_dir, ts_file), 'rb') as f:
merged.write(f.read())
print(f"合并完成,输出文件: {output_file}")
这种方法简单快速,但有时可能会遇到音视频不同步的问题。更可靠的方法是使用FFmpeg工具:
python复制import subprocess
def merge_with_ffmpeg(ts_dir, output_file):
# 生成文件列表
with open("file_list.txt", 'w') as f:
for ts in sorted(os.listdir(ts_dir)):
if ts.endswith('.ts'):
f.write(f"file '{os.path.join(ts_dir, ts)}'\n")
# 调用FFmpeg合并
cmd = [
'ffmpeg',
'-f', 'concat',
'-safe', '0',
'-i', 'file_list.txt',
'-c', 'copy',
output_file
]
subprocess.run(cmd, check=True)
合并后的视频可能需要进一步处理:
转换为MP4格式:
python复制def convert_to_mp4(input_file, output_file):
cmd = [
'ffmpeg',
'-i', input_file,
'-c:v', 'libx264',
'-preset', 'fast',
'-crf', '22',
'-c:a', 'aac',
'-b:a', '128k',
output_file
]
subprocess.run(cmd, check=True)
压缩视频体积:
提取音频:
python复制def extract_audio(input_file, output_audio):
cmd = [
'ffmpeg',
'-i', input_file,
'-vn',
'-acodec', 'copy',
output_audio
]
subprocess.run(cmd, check=True)
将上述所有步骤整合为一个完整的解决方案:
python复制class VideoDownloader:
def __init__(self, base_url, headers):
self.base_url = base_url
self.headers = headers
def download(self, save_dir='ts_files', max_segments=1000):
# 实现下载逻辑
pass
def merge(self, output_file='output.mp4', cleanup=True):
# 实现合并逻辑
pass
def process(self, output_file='final.mp4'):
self.download()
self.merge(output_file)
if cleanup:
import shutil
shutil.rmtree('ts_files')
有些小程序的M3U8文件是动态生成的,每次请求的TS片段URL都不同。针对这种情况:
实时解析M3U8内容:
python复制def parse_m3u8(m3u8_url, headers):
response = requests.get(m3u8_url, headers=headers)
if response.status_code == 200:
lines = response.text.split('\n')
ts_urls = [line.strip() for line in lines if line.endswith('.ts')]
return ts_urls
return []
处理加密的TS流:
对于定期更新的小程序视频,可以设置自动化监控:
python复制import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class M3U8Watcher(FileSystemEventHandler):
def on_modified(self, event):
if event.src_path.endswith('.m3u8'):
# 触发下载流程
pass
def start_monitoring(path='.'):
event_handler = M3U8Watcher()
observer = Observer()
observer.schedule(event_handler, path, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
缓存管理:
网络优化:
资源清理:
python复制def optimize_download():
session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
pool_connections=10,
pool_maxsize=10,
max_retries=3
)
session.mount('http://', adapter)
session.mount('https://', adapter)
return session
为了让非技术用户也能方便使用,我们可以用PySimpleGUI创建一个简单的图形界面:
python复制import PySimpleGUI as sg
def create_gui():
layout = [
[sg.Text("M3U8 URL或基础URL模式:"), sg.Input(key='-URL-')],
[sg.Text("保存路径:"), sg.Input(key='-PATH-'), sg.FolderBrowse()],
[sg.Button("开始下载"), sg.Button("退出")],
[sg.Output(size=(80, 20))]
]
window = sg.Window("微信小程序视频下载器", layout)
while True:
event, values = window.read()
if event in (None, '退出'):
break
if event == '开始下载':
base_url = values['-URL-']
save_dir = values['-PATH-'] or 'ts_files'
downloader = VideoDownloader(base_url, headers)
downloader.process()
window.close()
这个GUI可以进一步扩展,添加进度显示、历史记录等功能,让整个工具更加用户友好。
虽然本文主要介绍PC端的解决方案,但有时我们也需要从移动端获取视频。这里简要介绍两种方法:
通过电脑共享网络捕获:
Android设备本地捕获:
python复制def analyze_har(file_path):
import json
with open(file_path, 'r', encoding='utf-8') as f:
har_data = json.load(f)
video_entries = []
for entry in har_data['log']['entries']:
if entry['request']['url'].endswith(('.m3u8', '.ts')):
video_entries.append(entry)
return video_entries
让我们通过一个真实案例来演示整个流程。假设我们要下载一个微信小程序中的烹饪教学视频:
准备工作:
捕获过程:
.../playlist.m3u8的请求分析请求:
Python脚本调整:
合并与验证:
通过这个案例,我们发现实际URL模式为:
https://video.example.com/path/segment_00001.ts到segment_00120.ts
相应的Python代码调整为:
python复制base_url = 'https://video.example.com/path/segment_{}.ts'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...',
'Referer': 'https://servicewechat.com/...'
}
downloader = VideoDownloader(base_url, headers)
downloader.process(output_file='cooking_tutorial.mp4')
在实际操作中,你可能会遇到以下典型问题:
Fiddler捕获不到小程序流量:
TS片段下载不完整:
合并后的视频无法播放:
遇到403/404错误:
视频有加密:
针对加密视频的解密示例:
python复制from Crypto.Cipher import AES
def decrypt_ts(encrypted_data, key, iv):
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
return cipher.decrypt(encrypted_data)
# 使用方式
with open('encrypted.ts', 'rb') as f:
encrypted = f.read()
decrypted = decrypt_ts(encrypted, key, iv)
with open('decrypted.ts', 'wb') as f:
f.write(decrypted)
为了让这个解决方案更加完善,可以考虑以下优化方向:
浏览器扩展开发:
云服务集成:
智能识别系统:
分布式下载:
跨平台支持:
一个简单的云函数部署示例(以AWS Lambda为例):
python复制import boto3
from tempfile import mkdtemp
import shutil
def lambda_handler(event, context):
base_url = event['base_url']
headers = event.get('headers', {})
bucket = event['bucket']
output_key = event.get('output_key', 'output.mp4')
temp_dir = mkdtemp()
try:
downloader = VideoDownloader(base_url, headers)
downloader.download(save_dir=temp_dir)
output_path = os.path.join(temp_dir, output_key)
downloader.merge(output_file=output_path, cleanup=False)
s3 = boto3.client('s3')
s3.upload_file(output_path, bucket, output_key)
return {
'statusCode': 200,
'body': f"Video uploaded to s3://{bucket}/{output_key}"
}
finally:
shutil.rmtree(temp_dir)