yt-dlp

输入油管视频链接输出真实视频地址预览。

安装库

pip install yt-dlp

输入链接获取真实视频地址

import yt_dlp

def get_video_url(youtube_url):
    try:
        # 设置 yt-dlp 选项,确保获取最佳的视频和音频质量,并输出调试信息
        ydl_opts = {
            'quiet': True,  # 不显示调试信息
            'format': 'bestvideo+bestaudio/best',  # 获取最佳的视频和音频流
            'noplaylist': True,  # 不处理播放列表
            'outtmpl': '%(title)s.%(ext)s',  # 下载时使用原文件名
        }

        # 使用 yt-dlp 提取视频信息
        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            info_dict = ydl.extract_info(youtube_url, download=False)

            # 查找包含视频流和音频流的有效 URL
            for format in info_dict['formats']:
                # 确保格式包含视频和音频,并且是有效的视频流
                if 'url' in format and format.get('vcodec') != 'none' and format.get('acodec') != 'none':
                    return format['url']

            return "未找到有效的视频 URL"

    except Exception as e:
        return f"错误: {str(e)}"

if __name__ == "__main__":
    # 输入 YouTube 视频 URL
    youtube_url = input("请输入 YouTube 视频 URL: ")

    # 获取真实的视频 URL
    real_video_url = get_video_url(youtube_url)

    print(f"真实视频 URL: {real_video_url}")

需要验证Cookie

https://chromewebstore.google.com/Get cookies.txt LOCALLY

Export All Cookies

import yt_dlp

def get_video_url(youtube_url):
    try:
        ydl_opts = {
            'quiet': True,
            'format': 'bestvideo+bestaudio/best',
            'noplaylist': True,
            'cookiefile': '/home/ubuntu/cookies.txt',  # 这里指定你的cookie文件路径
            'outtmpl': '%(title)s.%(ext)s',
        }

        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            info_dict = ydl.extract_info(youtube_url, download=False)

            for f in info_dict['formats']:
                if 'url' in f and f.get('vcodec') != 'none' and f.get('acodec') != 'none':
                    return f['url']

            return "未找到有效的视频 URL"

    except Exception as e:
        return f"错误: {str(e)}"

if __name__ == "__main__":
    youtube_url = "https://www.youtube.com/watch?v=-9EM5_VFlt8"
    real_video_url = get_video_url(youtube_url)
    print(f"真实视频 URL: {real_video_url}")

前端应用支持链接批量导入

import subprocess
import sys

def install_packages():
    packages = [
        "Flask>=2.0.0",
        "yt-dlp>=2025.6.30"
    ]
    try:
        subprocess.check_call([sys.executable, "-m", "pip", "install", *packages])
        print("依赖安装完成!")
    except subprocess.CalledProcessError as e:
        print(f"安装依赖失败: {e}")
        sys.exit(1)

install_packages()

import os
import tempfile
from flask import Flask, request, render_template_string, redirect, url_for, flash, send_file
import yt_dlp
from werkzeug.utils import secure_filename

app = Flask(__name__)
app.secret_key = 'your_secret_key_here'

app.config['MAX_CONTENT_LENGTH'] = 300 * 1024  # 300KB上传限制
ALLOWED_EXTENSIONS = {'txt'}

parsed_results = {}

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def get_video_info(youtube_url, cookiefile=None):
    ydl_opts = {
        'quiet': True,
        'noplaylist': True,
    }
    if cookiefile:
        ydl_opts['cookiefile'] = cookiefile
    with yt_dlp.YoutubeDL(ydl_opts) as ydl:
        info = ydl.extract_info(youtube_url, download=False)
    return info

def get_best_video_url(youtube_url, cookiefile=None):
    ydl_opts = {
        'quiet': True,
        'noplaylist': True,
        'format': 'bestvideo+bestaudio/best',
    }
    if cookiefile:
        ydl_opts['cookiefile'] = cookiefile
    with yt_dlp.YoutubeDL(ydl_opts) as ydl:
        info = ydl.extract_info(youtube_url, download=False)
        best_url = info.get('url')
        if not best_url:
            for f in info.get('formats', []):
                if f.get('vcodec') != 'none' and f.get('acodec') != 'none':
                    best_url = f.get('url')
                    break
        return best_url

INPUT_PAGE = '''
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>YouTube批量视频解析下载</title>
<style>
 body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
    Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
  background: #f0f4f8;
  margin: 0;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #333;
  padding: 20px;
  box-sizing: border-box;
}

.container {
  background: #fff;
  padding: 40px 48px;
  border-radius: 16px;
  box-shadow: 0 12px 30px rgb(0 0 0 / 0.1);
  width: 100%;
  max-width: 700px;
  box-sizing: border-box;
  text-align: center;
}

h1 {
  font-weight: 700;
  font-size: 28px;
  margin-bottom: 40px;
  user-select: none;
  color: #222;
}

form {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

label {
  font-size: 16px;
  color: #555;
  user-select: none;
  text-align: left;
  margin: 0 auto;
  max-width: 600px;
}

input[type=file],
textarea,
input[type=text] {
  margin: 0 auto;
  font-size: 16px;
  max-width: 600px;
  width: 90%;
  padding: 8px 12px;
  border: 2px solid #3b82f6;
  border-radius: 8px;
  box-shadow: inset 0 4px 12px rgb(0 0 0 / 0.1);
  outline: none;
  resize: vertical;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
  box-sizing: border-box;
}

input[type=file]:focus,
textarea:focus,
input[type=text]:focus {
  border-color: #2563eb;
  box-shadow: 0 0 14px #2563eb;
}

textarea {
  height: 160px;
  min-height: 120px;
  max-height: 300px;
  line-height: 1.5;
}

button {
  width: 220px;
  padding: 16px 0;
  margin: 0 auto;
  font-size: 22px;
  font-weight: 700;
  color: white;
  background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%);
  border: none;
  border-radius: 14px;
  cursor: pointer;
  box-shadow: 0 6px 18px rgb(59 130 246 / 0.6);
  transition: background 0.3s ease, box-shadow 0.3s ease;
  user-select: none;
}

button:hover {
  background: linear-gradient(90deg, #2563eb 0%, #1e40af 100%);
  box-shadow: 0 8px 22px rgb(37 99 235 / 0.7);
}

.error {
  margin-top: 24px;
  color: #dc2626;
  font-weight: 600;
  user-select: none;
  max-width: 600px;
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

a.download-link {
  display: block;
  margin-top: 30px;
  font-size: 18px;
  color: #3b82f6;
  text-decoration: none;
}

a.download-link:hover {
  text-decoration: underline;
}

/* 响应式适配 */
@media (max-width: 768px) {
  .container {
    padding: 30px 24px;
  }
  h1 {
    font-size: 24px;
    margin-bottom: 30px;
  }
  button {
    width: 100%;
    font-size: 20px;
    padding: 14px 0;
  }
  input[type=file],
  textarea,
  input[type=text] {
    width: 100%;
    max-width: none;
  }
  label {
    max-width: none;
  }
  a.download-link {
    font-size: 16px;
  }
}

@media (max-width: 480px) {
  h1 {
    font-size: 20px;
    margin-bottom: 20px;
  }
  button {
    font-size: 18px;
    padding: 12px 0;
  }
}

</style>
</head>
<body>
  <div class="container">
    <h1>批量YouTube视频解析下载</h1>
    <form method="post" enctype="multipart/form-data">
      <label>
上传<a href="https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc" target="_blank" style="margin-left:8px; font-size:14px; color:#3b82f6; text-decoration:none;">Cookie</a> 文件(仅限txt,最大100K,选填):</label>

      <input type="file" name="cookiefile" accept=".txt" />
      
      <label>上传链接 TXT 文件(每行一个链接,选填):</label>
      <input type="file" name="linkfile" accept=".txt" />
      
      <label>或者直接在这里输入视频链接(多行,每行一个链接):</label>
      <textarea name="linktextarea" placeholder="https://www.youtube.com/watch?v=..." ></textarea>
      
      <button type="submit">开始解析链接</button>
    </form>
    {% with messages = get_flashed_messages() %}
      {% if messages %}
        <div class="error">{{ messages[0] }}</div>
      {% endif %}
    {% endwith %}
    {% if download_url %}
      <a href="{{ download_url }}" class="download-link" download>⬇️ 点击这里下载解析结果TXT文件</a>
    {% endif %}
  </div>
</body>
</html>
'''

RESULT_PAGE = '''
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>解析结果 - {{ info.title }}</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
        Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
      background: #f5f7fa;
      margin: 20px;
      color: #333;
      text-align: center;
    }
    h1 {
      font-weight: 700;
      font-size: 26px;
      margin-bottom: 20px;
    }
    .container {
      max-width: 720px;
      margin: 0 auto;
      background: #fff;
      padding: 24px 28px;
      border-radius: 12px;
      box-shadow: 0 10px 30px rgb(0 0 0 / 0.08);
    }
    h2 {
      font-weight: 700;
      font-size: 20px;
      margin-bottom: 16px;
      word-break: break-word;
    }
    img {
      max-width: 320px;
      border-radius: 12px;
      margin-bottom: 20px;
      box-shadow: 0 6px 12px rgb(0 0 0 / 0.08);
    }
    .btn-grid {
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      gap: 14px 18px;
      margin-top: 20px;
    }
    .download-btn {
      background-color: #3b82f6;
      color: white;
      border-radius: 10px;
      padding: 14px 22px;
      font-size: 15px;
      font-weight: 600;
      text-decoration: none;
      display: inline-flex;
      align-items: center;
      gap: 6px;
      box-shadow: 0 3px 10px rgb(59 130 246 / 0.45);
      transition: background-color 0.3s ease;
      user-select: none;
      cursor: pointer;
      white-space: nowrap;
      border: none;
      min-width: 150px;
      justify-content: center;
    }
    .download-btn:hover {
      background-color: #2563eb;
    }
    .no-audio-icon {
      font-size: 16px;
      color: #f87171;
      user-select: none;
    }
    a.back-link {
      display: inline-block;
      margin-top: 20px;
      font-size: 14px;
      color: #3b82f6;
      cursor: pointer;
      text-decoration: none;
    }
    a.back-link:hover {
      text-decoration: underline;
    }
    @media (max-width: 600px) {
      img {
        max-width: 100%;
      }
      .btn-grid {
        justify-content: center;
      }
      .download-btn {
        min-width: 120px;
        font-size: 13px;
        padding: 10px 16px;
      }
    }
  </style>
</head>
<body>
  <h1>解析结果</h1>
  <div class="container">
    <h2>{{ info.title }}</h2>
    <img src="{{ info.thumbnail }}" alt="视频封面" />
    <div>
      <button class="download-btn" onclick="window.open('{{ info.url }}', '_blank')">
        ⬇️ 下载视频(自动选择最佳画质)
      </button>
      <button class="download-btn" onclick="window.open('{{ info.thumbnail }}', '_blank')">
        🖼️ 下载封面
      </button>
    </div>

    <h3 style="margin-top: 30px;">更多视频分辨率下载选项</h3>
    <div class="btn-grid">
      {% for f in formats %}
        {% if f.vcodec != 'none' %}
          <a class="download-btn" href="{{ f.url }}" target="_blank" download>
            {{ f.format_note or f.format }} ({{ f.ext }})
            {% if f.filesize %} - {{ (f.filesize / 1024 / 1024) | round(2) }}MB{% endif %}
            {% if f.acodec == 'none' %}
              <span class="no-audio-icon" title="无声音轨">🔇</span>
            {% endif %}
          </a>
        {% endif %}
      {% endfor %}
    </div>

    <h3 style="margin-top: 30px;">所有音频格式</h3>
    <div class="btn-grid">
      {% for f in formats %}
        {% if f.vcodec == 'none' and f.acodec != 'none' %}
          <a class="download-btn" href="{{ f.url }}" target="_blank" download>
            {{ f.format_note or f.format }} ({{ f.ext }})
            {% if f.filesize %} - {{ (f.filesize / 1024 / 1024) | round(2) }}MB{% endif %}
          </a>
        {% endif %}
      {% endfor %}
    </div>

    <a href="{{ url_for('index') }}" class="back-link">← 返回首页</a>
  </div>
</body>
</html>
'''

@app.route('/', methods=['GET', 'POST'])
def index():
    error = None
    url = ''
    download_url = None

    if request.method == 'POST':
        cookie_path = None
        cookiefile = request.files.get('cookiefile')
        if cookiefile and cookiefile.filename != '':
            if not allowed_file(cookiefile.filename):
                flash('只允许上传 txt 格式的 cookie 文件。')
                return render_template_string(INPUT_PAGE, error=None, url='')
            filename = secure_filename(cookiefile.filename)
            with tempfile.NamedTemporaryFile(delete=False) as tmp:
                cookiefile.save(tmp.name)
                cookie_path = tmp.name

        linkfile = request.files.get('linkfile')
        if linkfile and linkfile.filename != '':
            # 批量上传链接txt,批量解析,生成下载文件
            if not allowed_file(linkfile.filename):
                flash('只允许上传 txt 格式的链接文件。')
                if cookie_path and os.path.exists(cookie_path):
                    os.remove(cookie_path)
                return render_template_string(INPUT_PAGE, error=None, url='')
            try:
                content = linkfile.stream.read().decode('utf-8')
                link_lines = [line.strip() for line in content.splitlines() if line.strip()]
            except Exception:
                flash('读取链接文件失败,请确认编码为UTF-8')
                if cookie_path and os.path.exists(cookie_path):
                    os.remove(cookie_path)
                return render_template_string(INPUT_PAGE, error=None, url='')

            if not link_lines:
                flash('上传的链接文件为空或无有效链接。')
                if cookie_path and os.path.exists(cookie_path):
                    os.remove(cookie_path)
                return render_template_string(INPUT_PAGE, error=None, url='')

            results = []
            for url_line in link_lines:
                try:
                    video_url = get_best_video_url(url_line, cookiefile=cookie_path)
                    if video_url:
                        results.append(f"{url_line} {video_url}")
                    else:
                        results.append(f"解析失败 {url_line}")
                except Exception as e:
                    results.append(f"解析异常 {url_line} 错误: {str(e)}")

            if cookie_path and os.path.exists(cookie_path):
                os.remove(cookie_path)

            tmp = tempfile.NamedTemporaryFile(delete=False, mode='w', encoding='utf-8', suffix='.txt')
            tmp.write('\n'.join(results))
            tmp.close()

            file_id = os.path.basename(tmp.name)
            parsed_results[file_id] = tmp.name
            download_url = url_for('download_file', file_id=file_id)

            return render_template_string(INPUT_PAGE, download_url=download_url, error=None, url='')

        else:
            # 没上传路径txt,走单链接解析(文本框)
            url = request.form.get('linktextarea', '').strip()
            if not url:
                flash('请输入视频链接或上传链接文件。')
                if cookie_path and os.path.exists(cookie_path):
                    os.remove(cookie_path)
                return render_template_string(INPUT_PAGE, error=None, url='')

            try:
                data = get_video_info(url, cookiefile=cookie_path)
                parsed_results[url] = data
                if cookie_path and os.path.exists(cookie_path):
                    os.remove(cookie_path)
                return redirect(url_for('result', video_url=url))
            except Exception as e:
                error = f"解析失败: {e}"
                if cookie_path and os.path.exists(cookie_path):
                    os.remove(cookie_path)

    return render_template_string(INPUT_PAGE, error=error, url=url, download_url=download_url)


@app.route('/result')
def result():
    video_url = request.args.get('video_url')
    data = parsed_results.get(video_url)
    if not data:
        return redirect(url_for('index'))
    return render_template_string(RESULT_PAGE, info=data, formats=data.get('formats', []))


@app.route('/download/<file_id>')
def download_file(file_id):
    filepath = parsed_results.get(file_id)
    if filepath and os.path.exists(filepath):
        response = send_file(filepath, as_attachment=True, download_name='parsed_results.txt')
        def cleanup(response):
            try:
                os.remove(filepath)
                parsed_results.pop(file_id, None)
            except Exception:
                pass
            return response
        response.call_on_close(cleanup)
        return response
    else:
        flash("下载文件不存在或已过期")
        return redirect(url_for('index'))


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000)