如何用Python的youtube-dl获取YouTube播放列表首个视频并解决报错
问题描述
我是Python新手,已经实现了YouTube视频转换功能,但输入播放列表URL时会出现JSON解析错误,添加--playlist-start 1参数后问题仍存在,而且YouTube短视频也无法正常运行。以下是我的代码:
App.py代码
from flask import Flask, request, render_template, session, url_for, redirect, send_file from extractor import extract_video_data_from_url app = Flask(__name__) @app.route("/") def index(): return render_template('index.html') @app.route("/download", methods=["POST"]) def download(): video_url = request.form["video_url"] video_data = extract_video_data_from_url(video_url) return render_template("Download.html",video_data=video_data)
extractor.py代码
import os import json def extract_format_data(format_data): extension = format_data["ext"] format_name = format_data["format"] url = format_data["url"] return { "extension": extension, "format_name": format_name, "url": url } def extract_video_data_from_url(url): command = f'youtube-dl "{url}" -j' output = os.popen(command).read() video_data = json.loads(output) title = video_data["title"] formats = video_data["formats"] thumbnail = video_data["thumbnail"] formats = [extract_format_data(format_data) for format_data in formats] return { "title": title, "formats": formats, "thumbnail": thumbnail }
解决方案
核心问题分析
- 播放列表JSON解析失败:youtube-dl处理播放列表时,
-j参数会输出每行一个独立JSON对象(对应播放列表里的每个视频),你的代码用json.loads()直接解析全部输出,多个JSON连在一起必然导致解析错误。 - 短视频支持不足:youtube-dl已停止维护,对YouTube Shorts这类新特性支持差,换成活跃分支
yt-dlp可解决。 - 命令执行不安全:
os.popen无法有效捕获错误,改用subprocess模块更可靠。
具体修改步骤
1. 安装yt-dlp
替换youtube-dl为yt-dlp,执行:
pip install yt-dlp
2. 修改extractor.py
import subprocess import json def extract_format_data(format_data): extension = format_data["ext"] format_name = format_data["format"] url = format_data.get("url", "") # 避免部分格式无下载链接导致报错 return { "extension": extension, "format_name": format_name, "url": url } def extract_video_data_from_url(url): # 使用yt-dlp替代youtube-dl,支持Shorts和播放列表 command = ["yt-dlp", "-j", url] try: # 捕获命令输出,按行处理多JSON内容 result = subprocess.run(command, capture_output=True, text=True, check=True) output_lines = result.stdout.strip().split("\n") video_list = [] for line in output_lines: if line: video_data = json.loads(line) title = video_data["title"] formats = video_data["formats"] thumbnail = video_data.get("thumbnail", "") # 过滤无下载链接的格式 formatted_formats = [extract_format_data(fd) for fd in formats if fd.get("url")] video_list.append({ "title": title, "formats": formatted_formats, "thumbnail": thumbnail }) # 单个视频返回对象,播放列表返回列表 return video_list[0] if len(video_list) == 1 else video_list except subprocess.CalledProcessError as e: raise Exception(f"获取视频数据失败: {e.stderr}") except json.JSONDecodeError as e: raise Exception(f"解析JSON失败: {str(e)}")
3. 修改App.py添加错误处理
from flask import Flask, request, render_template, session, url_for, redirect, send_file from extractor import extract_video_data_from_url app = Flask(__name__) @app.route("/") def index(): return render_template('index.html') @app.route("/download", methods=["POST"]) def download(): video_url = request.form["video_url"] try: video_data = extract_video_data_from_url(video_url) # 标记是否为播放列表 is_playlist = isinstance(video_data, list) return render_template("Download.html", video_data=video_data, is_playlist=is_playlist) except Exception as e: # 传递错误信息到首页显示 return render_template("index.html", error=str(e))
4. 调整Download.html模板(处理播放列表)
{% if error %} <p style="color: red;">{{ error }}</p> {% endif %} {% if is_playlist %} <h1>播放列表内容</h1> {% for video in video_data %} <div style="margin: 20px 0; padding: 10px; border: 1px solid #eee;"> <h2>{{ video.title }}</h2> {% if video.thumbnail %} <img src="{{ video.thumbnail }}" alt="{{ video.title }}" style="max-width: 300px;"> {% endif %} <div style="margin-top: 10px;"> {% for fmt in video.formats %} {% if fmt.url %} <a href="{{ fmt.url }}" download="{{ video.title }}.{{ fmt.extension }}" style="margin-right: 10px;"> {{ fmt.format_name }} ({{ fmt.extension }}) </a><br> {% endif %} {% endfor %} </div> </div> {% endfor %} {% else %} <h1>{{ video_data.title }}</h1> {% if video_data.thumbnail %} <img src="{{ video_data.thumbnail }}" alt="{{ video_data.title }}" style="max-width: 500px;"> {% endif %} <div style="margin-top: 20px;"> {% for fmt in video_data.formats %} {% if fmt.url %} <a href="{{ fmt.url }}" download="{{ video_data.title }}.{{ fmt.extension }}" style="margin-right: 10px;"> {{ fmt.format_name }} ({{ fmt.extension }}) </a><br> {% endif %} {% endfor %} </div> {% endif %}
内容的提问来源于stack exchange,提问作者warstha
相关产品推荐
相关产品推荐

