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

请求协助解密YouTube视频signatureCipher,修复视频链接提取脚本

修复YouTube视频链接解密与脚本失效问题

我编写了一段代码用于提取YouTube视频的最终链接,但该链接被signatureCipher加密,无法解密以获取文件大小:

import requests
import json
url = 'https://www.youtube.com/watch?v=RgzLnmTaCAU'
r = requests.get(url)
if r.status_code == 200:
    start = r.text.find('ytInitialPlayerResponse') + len('ytInitialPlayerResponse') + 3
    end = r.text.find('};', start) + 1
    json_str = r.text[start:end]
    data = json.loads(json_str)
    streaming_data = data['streamingData']
    formats = streaming_data['formats'][0]
    cipher = formats['signatureCipher']
    video_url = cipher.split('=')[3]
    print(video_url)

另外还有一个原本运行正常的脚本,针对上述特定URL突然失效,该脚本用于选择视频质量并查看对应视频数据(包括文件大小):

import requests
import json
#url = 'https://www.youtube.com/watch?v=EDmfFdAfytM'
url = "https://youtu.be/RgzLnmTaCAU"
r = requests.get(url)
if r.status_code == 200:
    start = r.text.find('ytInitialPlayerResponse') + len('ytInitialPlayerResponse') + 3
    end = r.text.find('};', start) + 1
    json_str = r.text[start:end]
    data = json.loads(json_str)
    streaming_data = data['streamingData']
    formats = streaming_data['formats']
    adaptive_formats = streaming_data['adaptiveFormats']
    print("================ Formats =================")
    format_labels = {}
    format_count = 1
    for video_data in formats:
        quality_label = video_data['qualityLabel']
        format_labels[format_count] = quality_label
        print(format_count, ": ", quality_label)
        format_count += 1
    print("================ Adaptive Formats =================")
    for adaptive_video_data in adaptive_formats:
        if 'qualityLabel' in adaptive_video_data:
            quality_label = adaptive_video_data['qualityLabel']
            format_labels[format_count] = quality_label
            print(format_count, ": ", quality_label)
            format_count += 1
    
    selected_format = int(input("\nEnter the number corresponding to the quality label you want to view: "))
    
    if selected_format in format_labels:
        selected_quality_label = format_labels[selected_format]
        print("\nSelected Quality Label: ", selected_quality_label)
        print("\nVideo Data for Selected Quality Label:\n")
        
        for video_data in formats:
            if video_data['qualityLabel'] == selected_quality_label:
                itag = video_data['itag']
                url = video_data['url']
                height = video_data['height']
                width = video_data['width']
                quality = video_data['quality']
                quality_label = video_data['qualityLabel']
                audio_quality = video_data['audioQuality']
                bitrate_mbps = round(video_data['bitrate'] / (1024 * 1024), 2)
                print("itag:", itag)
                print('URL:', url)
                print('Height:', height)
                print('Width:', width)
                print('Quality:', quality)
                print('Bitrate:', quality_label, f'({bitrate_mbps} Mbps)')
                print('Audio Quality:', audio_quality)
                size = requests.get(url, stream=True)
                file_size = size.headers['content-length'] or size.headers['Content-Length']
                print(f'File Size:, {int(file_size)/1024/1024:.3f} MB')
                print("\n")
        for adaptive_video_data in adaptive_formats:
            if 'qualityLabel' in adaptive_video_data and adaptive_video_data['qualityLabel'] == selected_quality_label:
                itag = adaptive_video_data['itag']
                url = adaptive_video_data['url']
                height = adaptive_video_data['height']
                width = adaptive_video_data['width']
                quality = adaptive_video_data['quality']
                quality_label = adaptive_video_data['qualityLabel']
                bitrate_mbps = round(adaptive_video_data['bitrate'] / (1024 * 1024), 2)
                print("itag:", itag)
                print('URL:', url)
                print('Height:', height)
                print('Width:', width)
                print('Quality:', quality)
                print('Bitrate:', quality_label, f'({bitrate_mbps} Mbps)')
                file_lenght = requests.get(url, stream=True)
                size_of_file = file_lenght.headers['content-length'] or file_lenght.headers['Content-Length']
                print(f'File Size:, {int(size_of_file)/1024/1024:.3f} MB')
                print("\n")
    else:
        print("Invalid Selection")
else:
    print('Error: Could not retrieve content from the URL')

问题根源

  1. signatureCipher处理错误:手动split解析参数不可靠,且YouTube对部分视频链接添加了签名验证,需要执行页面中的JS函数解密签名。
  2. 页面解析不稳定:用固定索引截取ytInitialPlayerResponse容易因页面结构变化失效。
  3. 请求被拦截:无请求头的裸请求容易被YouTube识别为爬虫,导致返回数据异常。

修复方案

依赖安装

先安装所需库:

pip install requests execjs urllib3

同时需要安装Node.js(execjs依赖Node执行JS代码)。

完整修复代码

import requests
import json
import re
import urllib.parse
import execjs

def get_youtube_video_data(url):
    # 模拟浏览器请求头,避免被拦截
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
        'Accept-Language': 'en-US,en;q=0.9',
        'Referer': 'https://www.youtube.com/'
    }
    
    r = requests.get(url, headers=headers)
    if r.status_code != 200:
        print('Error: Could not retrieve content from the URL')
        return
    
    # 用正则匹配ytInitialPlayerResponse,更稳定
    match = re.search(r'ytInitialPlayerResponse\s*=\s*({.*?});', r.text, re.DOTALL)
    if not match:
        print('Error: Failed to extract player response')
        return
    
    json_str = match.group(1)
    data = json.loads(json_str)
    streaming_data = data.get('streamingData', {})
    formats = streaming_data.get('formats', [])
    adaptive_formats = streaming_data.get('adaptiveFormats', [])
    
    # 提取签名解密JS函数
    js_func_name_match = re.search(r'sig\s*=\s*(.*?)\(sig\);', r.text)
    if not js_func_name_match:
        print('Error: Failed to extract signature decryption function name')
        return
    
    js_func_name = js_func_name_match.group(1)
    js_func_match = re.search(r'var\s+' + js_func_name + r'\s*=\s*function\(.*?\)\s*({.*?})', r.text, re.DOTALL)
    if not js_func_match:
        print('Error: Failed to extract signature decryption function body')
        return
    
    js_func_str = f'{js_func_name}(sig) {js_func_match.group(1)}'
    ctx = execjs.compile(js_func_str)
    
    def decrypt_signature(signature):
        return ctx.call(js_func_name, signature)
    
    # 整理所有格式选项
    format_labels = {}
    format_count = 1
    
    # 处理普通格式
    print("================ Formats =================")
    for video_data in formats:
        quality_label = video_data.get('qualityLabel', 'Unknown')
        format_labels[format_count] = {
            'label': quality_label,
            'data': video_data
        }
        print(format_count, ": ", quality_label)
        format_count += 1
    
    # 处理自适应格式
    print("================ Adaptive Formats =================")
    for adaptive_video_data in adaptive_formats:
        if 'qualityLabel' in adaptive_video_data:
            quality_label = adaptive_video_data['qualityLabel']
            format_labels[format_count] = {
                'label': quality_label,
                'data': adaptive_video_data
            }
            print(format_count, ": ", quality_label)
            format_count += 1
    
    # 用户选择格式
    try:
        selected_format = int(input("\nEnter the number corresponding to the quality label you want to view: "))
    except ValueError:
        print("Invalid input, must be a number")
        return
    
    if selected_format not in format_labels:
        print("Invalid Selection")
        return
    
    selected_item = format_labels[selected_format]
    selected_quality_label = selected_item['label']
    video_data = selected_item['data']
    
    print("\nSelected Quality Label: ", selected_quality_label)
    print("\nVideo Data for Selected Quality Label:\n")
    
    # 处理链接解密
    if 'signatureCipher' in video_data:
        cipher_params = urllib.parse.parse_qs(video_data['signatureCipher'])
        video_url = cipher_params['url'][0]
        signature = decrypt_signature(cipher_params['s'][0])
        # 拼接签名到URL
        video_url += '&signature=' + signature
    elif 'url' in video_data:
        video_url = video_data['url']
    else:
        print('Error: No valid video URL found')
        return
    
    # 提取视频信息
    itag = video_data.get('itag')
    height = video_data.get('height')
    width = video_data.get('width')
    quality = video_data.get('quality')
    bitrate_mbps = round(video_data.get('bitrate', 0) / (1024 * 1024), 2)
    audio_quality = video_data.get('audioQuality', 'N/A')
    
    print("itag:", itag)
    print('URL:', video_url)
    print('Height:', height)
    print('Width:', width)
    print('Quality:', quality)
    print('Bitrate:', selected_quality_label, f'({bitrate_mbps} Mbps)')
    print('Audio Quality:', audio_quality)
    
    # 获取文件大小
    try:
        size_response = requests.head(video_url, headers=headers, allow_redirects=True)
        file_size = size_response.headers.get('content-length') or size_response.headers.get('Content-Length')
        if file_size:
            print(f'File Size:, {int(file_size)/1024/1024:.3f} MB')
        else:
            print('File Size: Could not retrieve')
    except Exception as e:
        print(f'Error retrieving file size: {str(e)}')
    print("\n")

if __name__ == "__main__":
    # 测试URL
    url = "https://youtu.be/RgzLnmTaCAU"
    get_youtube_video_data(url)

关键修复点说明

  1. 稳定解析页面数据:用正则表达式匹配ytInitialPlayerResponse,避免固定索引截取的脆弱性。
  2. 解密signatureCipher:提取页面中的签名解密JS函数,用execjs在Python环境中执行解密,生成有效视频URL。
  3. 防拦截请求头:添加模拟浏览器的请求头,降低被YouTube识别为爬虫的概率。
  4. 优化文件大小获取:用head请求替代get请求,只获取响应头,减少带宽消耗和请求时间。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 10:02:37