You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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>

补充说明

  1. 临时下载目录可以定期清理,避免磁盘空间被占用;
  2. 前端使用encodeURIComponent处理视频URL,防止特殊字符导致参数传递错误;
  3. 建议保留下载按钮而非自动触发下载,减少用户误操作;
  4. 可以进一步优化:添加下载进度提示、错误处理逻辑等。

内容的提问来源于stack exchange,提问作者Prajjwal Pathak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 19:55:16