如何在Puppeteer-Sharp中等待文件下载完成?
在Puppeteer-Sharp中等待文件下载完成的方案
固定延迟确实不可靠,你可以通过监听下载请求的响应结合文件系统状态检查来实现精准等待,以下是两种具体实现方式:
方法一:使用WaitForResponseAsync
这种方式更直接,先预定义响应匹配条件,点击下载后等待符合条件的响应完成,再检查文件是否写入完毕。
完整代码示例
string xpath = "//a"; IElementHandle ret = await page.WaitForXPathAsync(xpath); await ret.FocusAsync(); // 获取系统默认下载文件夹路径 string downloadsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads"); // 预定义等待下载响应的任务:匹配200状态码且包含附件类型的响应 var responseTask = page.WaitForResponseAsync(response => response.Status == 200 && response.Headers.TryGetValue("Content-Disposition", out var disposition) && disposition.Contains("attachment")); // 触发下载点击 await ret.ClickAsync(); // 等待下载响应完成 var downloadResponse = await responseTask; // 从响应头提取文件名 string fileName = null; if (downloadResponse.Headers.TryGetValue("Content-Disposition", out var disposition)) { // 匹配常见的filename格式,兼容带引号、UTF-8编码的情况 var match = System.Text.RegularExpressions.Regex.Match(disposition, @"filename(?:\*=UTF-8''|=)""?([^"";]+)""?", System.Text.RegularExpressions.RegexOptions.IgnoreCase); if (match.Success) { fileName = match.Groups[1].Value; // 解码UTF-8格式的文件名(比如filename*=UTF-8''%E6%B5%8B%E8%AF%95.txt) fileName = System.Web.HttpUtility.UrlDecode(fileName); } } // 若响应头无法提取,从请求URL末尾获取文件名 if (string.IsNullOrEmpty(fileName)) { fileName = Path.GetFileName(downloadResponse.Request.Url); } string filePath = Path.Combine(downloadsPath, fileName); // 等待文件下载完成:检查文件存在且大小稳定(避免文件还在写入磁盘) DateTime startTime = DateTime.Now; long previousFileSize = -1; while (true) { if (File.Exists(filePath)) { var fileInfo = new FileInfo(filePath); long currentFileSize = fileInfo.Length; // 连续两次检查大小一致,判定为下载完成 if (currentFileSize == previousFileSize) { break; } previousFileSize = currentFileSize; } // 超时处理:30秒未完成则抛出异常 if ((DateTime.Now - startTime).TotalSeconds > 30) { throw new TimeoutException("文件下载超时"); } await Task.Delay(500); // 每隔500毫秒检查一次 }
方法二:监听Response事件
通过注册页面的Response事件,捕获所有响应,筛选出下载类响应后再等待文件写入。
完整代码示例
string xpath = "//a"; IElementHandle ret = await page.WaitForXPathAsync(xpath); await ret.FocusAsync(); string downloadsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads"); IResponse downloadResponse = null; string fileName = null; // 定义响应处理委托,方便后续移除事件监听 EventHandler<ResponseCreatedEventArgs> responseHandler = async (sender, e) => { var response = e.Response; if (response.Status == 200 && response.Headers.TryGetValue("Content-Disposition", out var disposition) && disposition.Contains("attachment")) { downloadResponse = response; // 提取文件名,逻辑同方法一 var match = System.Text.RegularExpressions.Regex.Match(disposition, @"filename(?:\*=UTF-8''|=)""?([^"";]+)""?", System.Text.RegularExpressions.RegexOptions.IgnoreCase); if (match.Success) { fileName = System.Web.HttpUtility.UrlDecode(match.Groups[1].Value); } else { fileName = Path.GetFileName(response.Request.Url); } } }; // 注册响应事件 page.Response += responseHandler; // 触发下载点击 await ret.ClickAsync(); // 等待捕获到下载响应 var timeoutTask = Task.Delay(30000); while (downloadResponse == null && !timeoutTask.IsCompleted) { await Task.Delay(100); } if (timeoutTask.IsCompleted) { throw new TimeoutException("未捕获到下载请求响应"); } // 移除事件监听,避免内存泄漏 page.Response -= responseHandler; // 等待文件写入完成,逻辑同方法一 string filePath = Path.Combine(downloadsPath, fileName); DateTime startTime = DateTime.Now; long previousFileSize = -1; while (true) { if (File.Exists(filePath)) { var fileInfo = new FileInfo(filePath); long currentFileSize = fileInfo.Length; if (currentFileSize == previousFileSize) { break; } previousFileSize = currentFileSize; } if ((DateTime.Now - startTime).TotalSeconds > 30) { throw new TimeoutException("文件下载超时"); } await Task.Delay(500); }
注意事项
- 如果自定义了浏览器的下载目录,需要将代码中的
downloadsPath替换为对应的路径。 - 部分网站的
Content-Disposition头格式可能特殊,需根据实际情况调整正则表达式。 - 超时时间可根据文件大小、网络环境灵活调整,避免不必要的等待。
- 大文件下载时,必须检查文件大小稳定,因为响应完成后,浏览器可能还在后台写入磁盘。
内容的提问来源于stack exchange,提问作者PeterSchwennesen
相关产品推荐
相关产品推荐

