WPF转WinForms版YouTube下载器出现HTML错误,求排查
WPF转Windows Forms YouTube下载器HTML错误排查
我从GitHub获取了一款可正常运行的WPF版YouTube下载器C#代码,将其改写为Windows Forms版本后,暂时注释了播放列表下载功能,仅保留单视频下载,但运行时出现HTML错误,需要排查方法。
原WPF版核心代码
BeginDownload方法
public async Task BeginDownload() { if (!CheckInputs()) return; ChangeButtonStates(false); string link = videoLink.Text; string? downloadFormat = null; string savedDirectory = Properties.Settings.Default.savedDirectory; if (saveMP3.IsChecked == true) downloadFormat = "mp3"; else downloadFormat = "mp4"; try { cancellationToken = cancellationTokenSource.Token; // If the given URL contains "&list" it means it is playlist if (!link.Contains("&list")) await DownloadSingle(link, savedDirectory, downloadFormat); else await DownloadPlaylist(link, savedDirectory, downloadFormat); } catch (Exception ex) { new Thread(() => { MessageBox.Show($"An error occurred: \"{ex.Message}\"", "Downloader", MessageBoxButton.OK, MessageBoxImage.Error); }).Start(); } ChangeButtonStates(true); }
单视频与播放列表下载方法
public async Task DownloadSingle(string link, string path, string format) { // Needed for security var handler = new HttpClientHandler(); var httpClient = new HttpClient(handler, true); handler.UseCookies = false; // Get video data var youtube = new YoutubeClient(httpClient); var streamData = await youtube.Videos.GetAsync(link); var title = ReplaceInvalidCharacters(streamData.Title); var progress = new Progress<double>(value => { // To split the progress bar into two halves, fill one half and then the next, // maximum of both progress bars is 50 if (downloadProgressOne.Value != 50) { downloadProgressOne.Value = value * 100.00f; } else { downloadProgressTwo.Value = (value * 100.00f) - 50; } // Taskbar icon progress bar taskbarIcon.ProgressValue = value; downloadStatus.Text = $"Downloading... {Convert.ToInt32(value * 100.00f)}%"; }); try { // Download content await youtube.Videos.DownloadAsync(link, $"{path}\\{title}.{format}", o => o.SetContainer(format).SetPreset(ConversionPreset.UltraFast), progress, cancellationToken); } catch (TaskCanceledException) { new Thread(() => { MessageBox.Show($"Successfully cancelled the download of: \"{title}\"", "Downloader", MessageBoxButton.OK, MessageBoxImage.Information); }).Start(); File.Delete($"{path}\\{title}.{format}"); return; } catch (Exception ex) { new Thread(() => { MessageBox.Show($"Failed to download video: \"{title}\" due to an error.\n\nReason: \"{ex.Message}\"", "Downloader", MessageBoxButton.OK, MessageBoxImage.Warning); }).Start(); return; } new Thread(() => { MessageBox.Show($"Successfully downloaded video: \"{title}\"", "Downloader", MessageBoxButton.OK, MessageBoxImage.Information); }).Start(); } public async Task DownloadPlaylist(string link, string path, string format) { // Create a string list incase any videos fail to download List<string> failedVideosTitles = new(); string finalList = ""; int failedVideosAmount = 0; // Needed for security var handler = new HttpClientHandler(); var httpClient = new HttpClient(handler, true); handler.UseCookies = false; // Get playlist data var youtube = new YoutubeClient(httpClient); var playlistData = await youtube.Playlists.GetAsync(link); var playlistName = ReplaceInvalidCharacters(playlistData.Title); var total = await youtube.Playlists.GetVideosAsync(link); var totalNumber = total.Count; int currentNumber = 0; // Foreach video in the playlist try to download them as the desired format await foreach (var video in youtube.Playlists.GetVideosAsync(playlistData.Id)) { currentNumber++; var title = ReplaceInvalidCharacters(video.Title); // Skip download of video if it already exists if (File.Exists($"{path}\\{title}.{format}")) { downloadStatus.Text = $"Skipping {currentNumber}/{totalNumber}..."; await Task.Delay(100); continue; } var progress = new Progress<double>(value => { // To split the progress bar into two halves, fill one half and then the next, // maximum of both progress bars is 50 if (value < 0.5f || downloadProgressOne.Value < 50) { downloadProgressOne.Value = value * 100.00f; downloadProgressTwo.Value = 0; } else downloadProgressTwo.Value = (value * 100.00f) - 50; // Taskbar icon progress bar taskbarIcon.ProgressValue = value; downloadStatus.Text = $"Downloading... {currentNumber}/{totalNumber} - {Convert.ToInt32(value * 100.00f)}%"; }); try { // Download content await youtube.Videos.DownloadAsync(video.Id, $"{path}\\{title}.{format}", o => o.SetContainer(format).SetPreset(ConversionPreset.UltraFast), progress, cancellationToken); } catch (TaskCanceledException) { new Thread(() => { MessageBox.Show($"Successfully cancelled the download of playlist: \"{playlistName}\"\n\nFiles have not been deleted.", "Downloader", MessageBoxButton.OK, MessageBoxImage.Information); }).Start(); File.Delete($"{path}\\{title}.{format}"); return; } catch (Exception ex) { new Thread(() => { // Increase the failed videos amount by one and add the title to the list failedVideosAmount++; failedVideosTitles.Add($"\"{title}\""); MessageBox.Show($"Skipping download of video: \"{title}\" due to an error.\n\nReason: \"{ex.Message}\"", "Downloader", MessageBoxButton.OK, MessageBoxImage.Warning); }).Start(); } } if (failedVideosAmount != 0) { new Thread(() => { // Show a messagebox telling the user it failed to download X amount of videos MessageBox.Show($"Downloaded playlist: \"{playlistName}\" but failed to download {failedVideosAmount} of the videos.\n\nPress OK to see list of failed videos.", "Downloader", MessageBoxButton.OK, MessageBoxImage.Warning); // Loop for the length of the string list, build a final string containing // a list of titles of failed videos then display it in a messagebox for the user for (int i = 0; i < failedVideosTitles.Count; i++) { if (i == 0) { finalList = $"{finalList}{i + 1}. {failedVideosTitles[i]}."; } else { finalList = $"{finalList}\n\n{i + 1}. {failedVideosTitles[i]}."; } } MessageBox.Show(finalList, "Downloader", MessageBoxButton.OK, MessageBoxImage.Information); }).Start(); } else { new Thread(() => { // The entire playlist was downloaded successfully MessageBox.Show($"Successfully downloaded playlist: \"{playlistName}\"", "Downloader", MessageBoxButton.OK, MessageBoxImage.Information); }).Start(); } }
转换后的Windows Forms代码
BeginDownload方法
public async Task BeginDownload() { if (!CheckInputs()) return; ChangeButtonStates(false); string link = videoLink.Text; string downloadFormat = null; string savedDirectory = Properties.Settings.Default.savedDirectory; if (cmbDownloadType.SelectedIndex == 1) downloadFormat = "mp3"; else downloadFormat = "mp4"; try { cancellationToken = cancellationTokenSource.Token; //// If the given URL contains "&list" it means it is playlist //if (!link.Contains("&list")) await DownloadSingle(link, savedDirectory, downloadFormat); //else //await DownloadPlaylist(link, savedDirectory, downloadFormat); } catch (Exception ex) { new Thread(() => { MessageBox.Show($"An error occurred: \"{ex.Message}\"", "Downloader", MessageBoxButtons.OK, MessageBoxIcon.Error); }).Start(); } ChangeButtonStates(true); }
单视频下载方法
public async Task DownloadSingle(string link, string path, string format) { // Needed for security var handler = new HttpClientHandler(); var httpClient = new HttpClient(handler, true); handler.UseCookies = false; // Get video data var youtube = new YoutubeClient(httpClient); var streamData = await youtube.Videos.GetAsync(link); var title = ReplaceInvalidCharacters(streamData.Title); var progress = new Progress<double>(value => { // To split the progress bar into two halves, fill one half and then the next, // maximum of both progress bars is 50 if (downloadProgressOne.Value != 50) { downloadProgressOne.Value = (int)(value * 100.00f); } else { downloadProgressTwo.Value = (int)((value * 100.00f) - 50); } // Taskbar icon progress bar //taskbarIcon.ProgressValue = value; downloadStatus.Text = $"Downloading... {Convert.ToInt32(value * 100.00f)}%"; }); try { // Download content await youtube.Videos.DownloadAsync(link, $"{path}\\{title}.{format}", o => o.SetContainer(format).SetPreset(ConversionPreset.UltraFast), progress, cancellationToken); } catch (TaskCanceledException) { new Thread(() => { MessageBox.Show($"Successfully cancelled the download of: \"{title}\"", "Downloader", MessageBoxButtons.OK, MessageBoxIcon.Information); }).Start(); File.Delete($"{path}\\{title}.{format}"); return; } catch (Exception ex) { new Thread(() => { MessageBox.Show($"Failed to download video: \"{title}\" due to an error.\n\nReason: \"{ex.Message}\"", "Downloader", MessageBoxButtons.OK, MessageBoxIcon.Warning); }).Start(); return; } new Thread(() => { MessageBox.Show($"Successfully downloaded video: \"{title}\"", "Downloader", MessageBoxButtons.OK, MessageBoxIcon.Information); }).Start(); }
排查建议
- 获取完整异常信息:在所有catch块中,将
ex.Message替换为ex.ToString(),捕获完整的异常堆栈跟踪、内部异常等细节,准确定位HTML错误的触发位置(比如是GetAsync还是DownloadAsync阶段抛出的)。 - 修复UI线程访问问题:WinForms控件只能在创建它们的UI线程上操作,弹出MessageBox的新线程写法可能引发跨线程问题。建议改用
Invoke方法在UI线程显示弹窗,例如:this.Invoke((MethodInvoker)delegate { MessageBox.Show($"Failed to download video: \"{title}\" due to an error.\n\nReason: \"{ex.ToString()}\"", "Downloader", MessageBoxButtons.OK, MessageBoxIcon.Warning); }); - 验证依赖库版本:确认WinForms项目中使用的YouTube库(如YoutubeExplode)版本与原WPF项目完全一致,不同版本的库可能对YouTube页面HTML结构的解析逻辑不同,导致错误。
- 检查输入链接有效性:测试多个不同格式的YouTube视频链接(如
https://www.youtube.com/watch?v=XXX、https://youtu.be/XXX),排除因链接格式异常导致的解析错误。 - 调试网络请求结果:在
YoutubeClient.Videos.GetAsync(link)调用后,添加调试日志,查看返回的streamData是否包含有效数据,或者是否返回了反爬验证页面的HTML内容。 - 确认目录权限:检查保存目录
path是否存在且有写入权限,避免因文件写入失败引发的间接异常被误判为HTML错误。
内容的提问来源于stack exchange,提问作者user2180706
相关产品推荐
相关产品推荐

