如何解决pytube循环下载YouTube播放列表时误将URL存为文件的报错
问题根因
这个报错和「把URL当文件名保存」没有关系,是三个问题叠加导致的:
- 代码用了无差别的裸
except:捕获,会吞掉所有前置错误:包括视频下载失败、文件名含Windows非法字符、pytube返回的默认文件名和实际落盘文件名不一致等,这些真实错误被隐藏后,后续音频提取逻辑拿不到正确的本地文件路径,才触发后续报错 - 你当前使用的旧版moviepy(适配Python3.7的版本普遍存在该问题)在
ffmpeg_audiowriter的close方法有已知缺陷:如果传入的输出路径无效、或者ffmpeg进程因为前置错误提前退出,此时强制关闭stdin管道就会抛出OSError: [Errno 22] Invalid argument。报错信息里夹带的URL是pytube对象的字符串表示被异常栈错误带出的内容,不是你代码主动传入的文件名 - 前58次循环没触发是因为这些视频的文件名刚好合法、下载过程也没出现异常,到第59次时要么视频标题包含Windows不允许的文件名字符(如
:?|这类YouTube标题常见符号),要么刚好碰到网络波动导致下载失败,错误被裸except吞掉后就触发了moviepy的这个bug
修复方案
不需要改成「全部下载完再提取音频」的逻辑,按下面调整代码即可实现边下载、边提取、边播放已完成音频的需求:
- 去掉裸异常捕获,明确打印错误信息,不要吞掉真实报错
- 下载前主动过滤文件名里的非法字符,同时使用pytube download方法返回的真实落盘路径,不要手动拼接路径
- 用上下文管理器加载视频文件,处理完自动释放文件句柄,避免长时间运行句柄泄漏
- 针对旧版moviepy的已知bug加兼容判断,只要音频文件实际生成成功就忽略该关闭报错
核心修复后代码:
import re import os import pytube from moviepy import VideoFileClip # 过滤Windows系统不允许的文件名字符 def sanitize_filename(filename): return re.sub(r'[\\/*?:"<>|]', "_", filename) def Youtube_playlist_downloader(): playlist_url = input("input playlist url here (note if it is a url with &index=number at the end that's a song url not a playlist url):") playlist = pytube.Playlist(playlist_url) # 提前创建目录避免路径不存在报错 os.makedirs("./videos", exist_ok=True) os.makedirs("./audio", exist_ok=True) for v in playlist.videos: print(f"Processing: {v.watch_url}") video_path = "" safe_name = "" try: video_obj = pytube.YouTube(v.watch_url) stream = video_obj.streams.get_lowest_resolution() safe_name = sanitize_filename(stream.default_filename) # 直接拿download返回的真实落盘路径,不要手动拼接 video_path = stream.download(output_path="./videos", filename=safe_name) except Exception as e: print(f"Failed to download video, err: {str(e)}") continue audio_path = "" try: audio_filename = f"{os.path.splitext(safe_name)[0]}{Audio_type}" audio_path = os.path.join("./audio", audio_filename) # 用with自动释放资源,避免句柄泄漏 with VideoFileClip(video_path) as clip: clip.audio.write_audiofile(audio_path) # 到这里音频已经写入完成,可以直接调用播放逻辑,不需要等待后续下载 except OSError as e: # 兼容旧版moviepy的stdin关闭误报:文件存在且大小正常就跳过报错 if e.errno == 22 and os.path.exists(audio_path) and os.path.getsize(audio_path) > 0: pass else: print(f"Failed to extract audio, err: {str(e)}") except Exception as e: print(f"Failed to extract audio, err: {str(e)}") Choice_choosing()
补充:如果后续还偶发该报错,可以把moviepy升级到1.0.3以上版本,官方已经修复了ffmpeg进程提前退出时强制关闭stdin抛错的问题。
内容的提问来源于stack exchange,提问作者Kurtis Gibson
相关产品推荐
相关产品推荐

