如何在Flask Jinja2的HTML中调用Python对象方法?(YouTube下载器)
解决Flask+Pytube YouTube下载器的itag选择下载问题
我正在用Pytube和Flask开发YouTube视频下载器,其他功能都完成了,就差实现:用户选择下拉框里的itag选项后,调用Pytube的get_by_itag()方法完成视频下载。以下是我的现有代码:
现有Flask代码
from flask import Flask, render_template, request from pytube import YouTube app = Flask(__name__) @app.route("/") def index(): return render_template("index.html", data=None) @app.route("/download", methods=["POST", "GET"]) def downloadVideo(): if request.method == "POST": url = request.form["videourl"] if url: yt = YouTube(url) title = yt.title thumbnail = yt.thumbnail_url streams = yt.streams.filter(file_extension='mp4') data = [title, thumbnail, streams, yt] return render_template("index.html", data=data) if __name__ == "__main__": app.run(debug=True)
现有HTML代码
<!DOCTYPE html> <html> <head> <title> Youtube Downloader </title> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="stylesheet" href="static/css/style.css"> </head> <body> <div class="main"> <div class="header"> <div> <img src="static/img/icon.png" width="48" height="48"> <h2> Youtube Downloader </h2> </div> <div> <p> Convert and download youtube videos </p> <p> in MP4 for free </p> </div> </div> {% if not data %} <div class="dform"> <form action="http://127.0.0.1:5000/download", method="POST"> <div class="inputfield"> <input type="input" name="videourl" placeholder="Search or Paste youtube link here" autocomplete="off"> <button type="submit"> Download </button> </div> </form> </div> {% else %} <div class="videoinfo"> <img src="" class="thumbnail"> <h2> {{data[0]}} </h2> </div> <div class="quality"> <select id="streams"> {% for stream in data[2][:3] %} <option value="{{stream.itag}}"> {{stream.resolution}} </option> {% endfor %} </select> </div> {% endif %} </div> <script type="text/javascript"> const image = document.querySelector(".thumbnail"); const select = document.querySelector("select"); let url = `{{data[1]}}`; if (image) { image.src = `${url}`; window.addEventListener('change', function() { var option = select.options[select.selectedIndex].value; console.log(option); {% set stream = data[3].get_by_itag(option) %} {% stream.download() %} }); } </script> </body> </html>
核心问题分析
原代码的错误在于混淆了服务器端模板渲染和客户端JS执行的时机:
- Jinja2语法(
{% ... %})在服务器生成页面时就会执行,此时客户端的option变量还未产生,data[3].get_by_itag(option)会直接报错;就算不报错,stream.download()也会在页面加载时就执行,而非用户选择后触发。 YouTube对象(data[3])无法被模板序列化,传递给前端会引发异常。
解决方案
1. 修改Flask后端代码
新增专门处理下载的路由,接收视频URL和itag参数,在服务器端完成下载后返回文件给客户端;同时调整原路由,不再传递YouTube对象,改为传递视频原始URL。
from flask import Flask, render_template, request, send_file from pytube import YouTube import os app = Flask(__name__) # 创建临时下载目录(自动创建不存在的目录) DOWNLOAD_FOLDER = "temp_downloads" os.makedirs(DOWNLOAD_FOLDER, exist_ok=True) @app.route("/") def index(): return render_template("index.html", data=None) @app.route("/download", methods=["POST", "GET"]) def fetch_video_info(): if request.method == "POST": url = request.form["videourl"] if url: yt = YouTube(url) title = yt.title thumbnail = yt.thumbnail_url streams = yt.streams.filter(file_extension='mp4') # 替换yt对象为原始视频URL,供前端传递给下载路由 data = [title, thumbnail, streams, url] return render_template("index.html", data=data) @app.route("/download-video", methods=["GET"]) def download_video(): # 获取前端传递的参数 video_url = request.args.get("url") itag = request.args.get("itag") if not video_url or not itag: return "缺少必要参数", 400 # 服务器端执行下载逻辑 yt = YouTube(video_url) stream = yt.streams.get_by_itag(itag) file_path = stream.download(output_path=DOWNLOAD_FOLDER) # 返回文件给客户端触发下载 return send_file(file_path, as_attachment=True) if __name__ == "__main__": app.run(debug=True)
2. 修改HTML前端代码
调整JS逻辑,当用户选择itag后,通过构造下载链接跳转到后端的下载路由,触发文件下载。
<!DOCTYPE html> <html> <head> <title> Youtube Downloader </title> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="stylesheet" href="static/css/style.css"> </head> <body> <div class="main"> <div class="header"> <div> <img src="static/img/icon.png" width="48" height="48"> <h2> Youtube Downloader </h2> </div> <div> <p> Convert and download youtube videos </p> <p> in MP4 for free </p> </div> </div> {% if not data %} <div class="dform"> <!-- 去掉硬编码的action地址,使用相对路径 --> <form action="/download" method="POST"> <div class="inputfield"> <input type="input" name="videourl" placeholder="Search or Paste youtube link here" autocomplete="off"> <button type="submit"> 获取视频信息 </button> </div> </form> </div> {% else %} <div class="videoinfo"> <!-- 直接在模板中赋值缩略图地址 --> <img src="{{data[1]}}" class="thumbnail"> <h2> {{data[0]}} </h2> </div> <div class="quality"> <select id="streams"> {% for stream in data[2][:3] %} <option value="{{stream.itag}}"> {{stream.resolution}} </option> {% endfor %} </select> <!-- 添加下载按钮,提升用户体验 --> <button id="startDownload">开始下载</button> </div> {% endif %} </div> <script type="text/javascript"> const select = document.querySelector("#streams"); const downloadBtn = document.querySelector("#startDownload"); // 从模板获取原始视频URL const videoUrl = "{{data[3]}}"; if (downloadBtn) { downloadBtn.addEventListener('click', function() { const selectedItag = select.value; if (!selectedItag) { alert("请先选择画质选项"); return; } // 构造下载链接,转义URL避免特殊字符问题 const downloadLink = `/download-video?url=${encodeURIComponent(videoUrl)}&itag=${selectedItag}`; // 跳转链接触发下载 window.location.href = downloadLink; }); } </script> </body> </html>
补充说明
- 临时下载目录可以定期清理,避免磁盘空间被占用;
- 前端使用
encodeURIComponent处理视频URL,防止特殊字符导致参数传递错误; - 建议保留下载按钮而非自动触发下载,减少用户误操作;
- 可以进一步优化:添加下载进度提示、错误处理逻辑等。
内容的提问来源于stack exchange,提问作者Prajjwal Pathak
相关产品推荐
相关产品推荐

