如何用Selenium C#通过PDF Viewer实现PDF自动化下载?
问题描述
我希望使用JavaScriptExecutor下载PDF。点击PDF链接后会在新窗口打开在线PDF Viewer,该窗口的工具栏包含打印、下载等功能,手动可点击下载按钮完成下载。我尝试通过JavaScriptExecutor自动化该场景,发现工具栏位于嵌套shadow root中,复制的JS路径在控制台可定位并点击元素,但在代码中执行时抛出“Cannot read properties of null(reading 'ShadowRoot')”异常。
使用的代码如下:
IWebDriver driver; driver.FindElement(By.Xpath("Xpath_of_PDF_Link")).Click(); driver.SwitchTo().Window(driver.WindowHandles.Last()); Thread.Sleep(2000); IJavaScriptExecutor js = (IJavaScriptExecutor)driver; IWebElement downloadButton = (IWebElement)js.ExecuteScript("return document.querySelector(\"#viewer\").shadowRoot.querySelector(\"#toolbar\").shadowRoot.querySelector(\"#downloads\").shadowRoot.querySelector(\"#download\")"); Thread.Sleep(1000); var element = "argument[0].click()"; js.ExecuteScript(element,downloadButton);
请问是否有可行的解决方案或思路?我编程经验尚浅,感谢任何建议,如需其他信息请告知。
解决方案思路
1. 替换硬等待为显式等待+分步检查Shadow DOM
Thread.Sleep是硬等待,无法确保页面和Shadow DOM完全加载,这是导致null异常的核心原因。改用分步检查+循环等待的方式,确保每一层Shadow DOM都加载完成后再继续:
// 切换到新窗口后初始化显式等待 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); // 先等待viewer元素存在 wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.querySelector('#viewer') !== null")); // 执行整合了等待逻辑的JS脚本,直接完成点击 IJavaScriptExecutor js = (IJavaScriptExecutor)driver; js.ExecuteScript(@" function getDownloadBtn() { // 逐层检查Shadow DOM const viewer = document.querySelector('#viewer'); if (!viewer?.shadowRoot) return null; const toolbar = viewer.shadowRoot.querySelector('#toolbar'); if (!toolbar?.shadowRoot) return null; const downloads = toolbar.shadowRoot.querySelector('#downloads'); if (!downloads?.shadowRoot) return null; return downloads.shadowRoot.querySelector('#download'); } // 循环等待按钮加载,最多尝试10次 let btn = null; let tries = 0; while (!btn && tries < 10) { btn = getDownloadBtn(); if (!btn) await new Promise(r => setTimeout(r, 500)); tries++; } btn?.click(); ");
2. 直接请求PDF文件URL(更稳定的方案)
如果能拿到PDF的直接下载地址,完全可以跳过Viewer页面,直接用HTTP请求下载,避免操作复杂的Shadow DOM:
- 先获取PDF链接的
href属性 - 判断是否为直接PDF文件URL(通常以
.pdf结尾) - 用
HttpClient直接下载文件
示例代码:
// 获取PDF链接元素 IWebElement pdfLink = driver.FindElement(By.Xpath("Xpath_of_PDF_Link")); string pdfUrl = pdfLink.GetAttribute("href"); // 直接下载PDF文件 if (!string.IsNullOrEmpty(pdfUrl)) { using (HttpClient client = new HttpClient()) { // 如果需要登录态,可复制浏览器的Cookie到请求头 client.DefaultRequestHeaders.Add("Cookie", driver.Manage().Cookies.GetCookieNamed("sessionId").Value); byte[] pdfContent = await client.GetByteArrayAsync(pdfUrl); File.WriteAllBytes(@"C:\Your\Save\Path\file.pdf", pdfContent); } }
3. 修改浏览器配置,直接下载PDF
通过配置ChromeOptions,让浏览器跳过在线Viewer,直接下载PDF文件,这是最简单的方案:
ChromeOptions options = new ChromeOptions(); // 让Chrome直接下载PDF,不打开在线Viewer options.AddUserProfilePreference("plugins.always_open_pdf_externally", true); // 设置默认下载目录 options.AddUserProfilePreference("download.default_directory", @"C:\Your\Download\Folder"); // 初始化驱动时传入配置 IWebDriver driver = new ChromeDriver(options);
这样点击PDF链接后,浏览器会自动下载文件,无需任何后续操作。
内容的提问来源于stack exchange,提问作者vivek joshi
相关产品推荐
相关产品推荐

