You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 02:25:24