翠花让我下30集学习视频,我直接用Python写了个下载器(附源码)
起因
翠花在短视频平台刷到一个视频,转头跟我说:
“这个视频很有学习意义,你把它下载下来发给我。”
然后她发来第二条消息:
“顺便把这个系列的都下一下,大概三十多个。”
我当时的表情是这样的:🙂
心里想的是:翠花,您说的"顺便"这两个字,让我觉得我们对"顺便"的理解可能不在同一个维度。
但没有办法,翠花的话还得听
我本来想直接找个现成的工具凑合一下,结果发现:
- 在线下载网站,广告比视频还多
- 有些工具收费,有些工具限免
- 碰到加密严格的网站直接歇菜
于是我决定自己写一个。
工具长什么样
用tkinter撸了一个GUI,三个标签页:
标签1:智能嗅探下载
粘贴视频网页链接,点"嗅探",自动解析出所有可用格式,选画质,点下载。
支持YouTube、B站、抖音、西瓜视频等几百个网站,底层是yt-dlp在干活。
标签2:M3U8直链下载
有些网站yt-dlp解析不了,但你能找到M3U8地址,直接粘过来,用FFmpeg下载合并。
标签3:批量下载
一行一个链接,支持几十个视频排队下载,晚上睡觉前开着,第二天收工。
核心代码解析
1. 嗅探视频信息
用的是yt-dlp --dump-json命令,把视频的元数据全打出来,然后解析JSON:
cmd = [sys.executable, "-m", "yt_dlp", "--no-update", "--dump-json", url]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding='utf-8')
stdout, stderr = process.communicate()
info = json.loads(stdout)
拿到info之后,里面有title、duration、formats这些字段,formats是个数组,每种画质对应一个条目。
2. 解析视频格式列表
这是整个工具里最啰嗦的一段,因为yt-dlp返回的格式列表比较原始,需要自己过滤和排版:
for f in formats:
format_id = f.get('format_id')
ext = f.get('ext', 'unknown')
if f.get('vcodec') != 'none' and f.get('acodec') != 'none':
# 有视频有音频
height = f.get('height')
desc = f"{height}p {ext}" if height else f"{resolution} {ext}"
elif f.get('acodec') != 'none':
# 仅音频
abr = f.get('abr')
desc = f"音频 {abr}kbps" if abr else "仅音频"
然后把format_id和显示文字存成一个列表,绑到tkinter的Combobox上,用户选哪个,下载的时候就传对应的format_id给yt-dlp。
3. M3U8下载
M3U8本质上是个索引文件,里面列了一堆.ts切片地址。FFmpeg可以直接读M3U8地址,一边拉切片一边合并,一条命令搞定:
cmd = [FFMPEG_EXE, "-i", m3u8_url, "-c", "copy", output_path]
subprocess.Popen(cmd, ...)
缺点是FFmpeg下载M3U8的时候不会告诉你进度,只能看到stderr里时不时冒出来time=xxx,所以进度条只能开不确定模式,转圈圈表示"还在跑"。
4. 批量下载
批量下载的核心是一个for循环,遍历用户粘进文本框的每一行链接:
for idx, url in enumerate(urls, 1):
if self.batch_stop_flag:
break # 用户点了停止
if url.lower().endswith('.m3u8'):
# 用FFmpeg下载
...
else:
# 用yt-dlp下载
...
每个任务跑完才跑下一个,没有用异步,因为yt-dlp本身已经挺吃CPU了,同时跑多个反而容易卡死。
5. Cookies问题
有些网站需要登录才能下载,yt-dlp支持从浏览器读取Cookies:
cmd.extend(["--cookies-from-browser", "chrome"])
但有个坑:Chrome正在运行的时候,Cookie数据库被锁着,读不了。所以工具里加了个提示:勾选Cookies选项之前,先把Chrome关了。
遇到yt-dlp解析不了的怎么办
这是实战中最常遇到的情况,尤其是一些加密比较严的平台。
我的处理方式是加了一个"M3U8探测"按钮,原理是请求视频网页的HTML源码,用正则把里面出现的.m3u8链接全抠出来:
patterns = [
r'https?://[^\s"\']+\.m3u8[^\s"\']*',
r'https?://[^\s"\']+\.m3u8\?[^\s"\']*',
]
for pat in patterns:
matches = re.findall(pat, html)
能抠到就有戏,把M3U8地址粘到标签2,用FFmpeg下载。
抠不到的话,最后的办法是CocoCut浏览器插件——它在网络层蹲着,只要视频在浏览器里播得出,就能把流抓下来。
完整源代码
注:代码部分已省略(太长),完整版WX搜索虾搞AI实验室回复「视频下载」领取。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
视频嗅探下载器 (Video Sniffer & Downloader) - 虾搞AI实验室
标签1:智能嗅探下载(基于 yt-dlp)
标签2:M3U8 直链下载(基于 ffmpeg)
标签3:批量下载(支持多 URL 列表)
完全离线运行,支持 Cookies
"""
import os
import sys
import subprocess
import threading
import queue
import json
import re
import tempfile
import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox
from urllib.parse import urlparse
# 可选拖拽支持
try:
from tkinterdnd2 import DND_FILES, TkinterDnD
DND_AVAILABLE = True
except ImportError:
DND_AVAILABLE = False
from tkinter import Tk as TkinterDnD
# 图像处理
from PIL import Image, ImageTk
import base64
import io
# ==================== Base64 资源占位符(已省略长码)====================
BASE64_ICON = "" # 原为图标 base64,已省略
BASE64_QR = "" # 原为二维码 base64,已省略
# ==================== 全局配置 ====================
def get_base_path():
if getattr(sys, 'frozen', False):
return sys._MEIPASS
else:
return os.path.dirname(os.path.abspath(__file__))
BASE_DIR = get_base_path()
FFMPEG_DIR = os.path.join(BASE_DIR, "ffmpeg", "bin")
FFMPEG_EXE = os.path.join(FFMPEG_DIR, "ffmpeg.exe")
FFPLAY_EXE = os.path.join(FFMPEG_DIR, "ffplay.exe")
FFMPEG_AVAILABLE = os.path.isfile(FFMPEG_EXE)
FFPLAY_AVAILABLE = os.path.isfile(FFPLAY_EXE)
# ==================== 辅助函数 ====================
def format_duration(seconds):
if not seconds:
return "未知"
import datetime
return str(datetime.timedelta(seconds=int(seconds)))
def format_file_size(size_bytes):
if not size_bytes:
return "未知"
for unit in ['B', 'KB', 'MB', 'GB']:
if size_bytes < 1024.0:
return f"{size_bytes:.1f} {unit}"
size_bytes /= 1024.0
return f"{size_bytes:.1f} TB"
# ==================== 主应用程序类 ====================
class VideoDownloaderApp:
def __init__(self, root):
self.root = root
self.root.title("视频嗅探下载器-虾搞AI实验室")
self.root.geometry("1050x650")
self.root.minsize(950, 600)
self.set_window_icon()
# 线程控制
self.current_process = None
self.stop_requested = False
self.task_queue = queue.Queue()
# 嗅探标签页的数据
self.current_video_info = None
self.current_formats = []
self.current_url = ""
self.use_cookies = False
self.open_folder_after = False
# 检查 ffmpeg/ffplay
if not FFMPEG_AVAILABLE:
self.log("警告:未找到 ffmpeg,合并音视频/M3U8下载功能可能失败", "WARNING")
if not FFPLAY_AVAILABLE:
self.log("提示:未找到 ffplay,在线播放功能不可用", "INFO")
self.create_widgets()
self.poll_log_queue()
if DND_AVAILABLE:
self.setup_drag_drop()
(完整代码太长了,上面只放了前半部分,包含配置、辅助函数和UI初始化。后半部分包含start_sniff、download_video、batch_download_worker等核心逻辑,wx搜索虾搞AI实验室回复「视频下载」领取完整源码。)
运行依赖
pip install yt-dlp
pip install pillow
pip install tkinterdnd2 # 可选,支持拖拽
另外需要把FFmpeg的ffmpeg.exe和ffplay.exe放到程序目录下的ffmpeg/bin/里。
如果不想自己配,wx搜索虾搞AI实验室回复「视频下载」,我打了一个打包好的版本,解压就能用。
更多推荐


所有评论(0)