You need to enable JavaScript to run this app. Discord.py为play命令实现ytsearch及解决KeyError:formats报错
优惠活动
大模型
产品
解决方案
定价
更多

Discord.py为play命令实现ytsearch及解决KeyError:formats报错

KeyError: 'formats' 核心触发原因

当使用搜索关键词查询时,youtube-dl返回的并非单个视频的元数据,而是搜索结果集合的列表级对象:

  • 直接传入YouTube链接时,extract_info()返回单视频元数据,结构包含formats、webpage_url等字段,匹配原有Video类的取值逻辑
  • 传入搜索关键词时,返回结构为播放列表级别的容器,本身不存在formats字段,所有匹配的视频元数据都嵌套在entries列表中。原有代码直接对这个容器对象取video["formats"],自然触发键不存在的报错。

另外原有代码直接取formats[0]的逻辑也存在问题:formats列表默认按清晰度从低到高排序,第0项通常是低分辨率带视频的流,并不适合音乐Bot的音频播放需求。

完整兼容实现步骤

1. 修正youtube-dl配置

替换原有简陋的配置,补充搜索、音频质量相关参数:

ytdl_options = {
    'noplaylist': True, # 禁用播放列表解析,只取单个匹配结果
    'default_search': 'ytsearch', # 对非URL输入强制走YouTube搜索,取第一个匹配结果
    'quiet': True, # 关闭冗余的下载过程日志
    'no_warnings': True,
    'format': 'bestaudio/best', # 自动选择最优音频流,无需手动遍历formats数组
}
ytdl = youtube_dl.YoutubeDL(ytdl_options)

2. 改造Video类适配两种返回结构

增加结构判断逻辑,统一提取单视频的元数据,不管输入是URL还是搜索关键词:

class Video:
    def __init__(self, query):
        raw_info = ytdl.extract_info(query, download=False)
        
        # 兼容搜索/播放列表的返回结构,取出嵌套的真实视频信息
        if 'entries' in raw_info:
            video_info = raw_info['entries'][0]
        else:
            video_info = raw_info
        
        self.url = video_info["webpage_url"]
        self.title = video_info.get("title", "未知曲目")
        self.stream_url = video_info["url"]

3. 修正play命令的参数接收逻辑

原有命令定义async def play(ctx, url)只能接收无空格的单段输入,用户输入多关键词搜索时只会收到第一个单词,需要改成接收命令后全量文本的形式:

@bot.command()
async def play(ctx, *, query: str):
    # 先校验用户是否在语音频道
    if not ctx.author.voice:
        await ctx.send("请先加入语音频道再执行点歌操作")
        return
    
    # 初始化当前服务器的播放队列
    if ctx.guild.id not in musics:
        musics[ctx.guild] = []
    
    client = ctx.guild.voice_client
    # 未连接语音频道时自动加入用户所在频道
    if not client:
        client = await ctx.author.voice.channel.connect()
    
    # 捕获搜索失败的异常,避免Bot崩溃
    try:
        video = Video(query)
    except Exception:
        await ctx.send("未找到匹配的歌曲,请更换关键词重试")
        return
    
    # 处理队列逻辑
    if client.is_playing() or client.is_paused():
        musics[ctx.guild].append(video)
        await ctx.send(f"已加入播放队列:{video.title}")
    else:
        await ctx.send(f"开始播放:{video.title}")
        play_song(client, musics[ctx.guild], video)
长期优化提示
  • 原youtube-dl库已停止维护,可无缝替换为社区维护的yt-dlp库,接口兼容现有代码,能大幅减少流地址解析失败的问题,仅需修改导入语句即可
  • 现有play_song逻辑中需要补充FFmpeg的连接超时、重连参数,避免长连接音频播放中断
  • 队列初始化时建议提前为每个已加入的服务器创建空队列,减少首次点歌的空键报错

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.24 06:06:04