使用Puppeteer获取按钮触发的动态下载链接

使用Puppeteer获取按钮触发的动态下载链接

本文详细介绍了如何使用Puppeteer处理不直接包含URL的动态下载按钮。通过拦截网络请求,特别是利用page.waitForRequest和promise.all,可以在点击按钮后捕获到实际触发的下载链接,从而实现自动化下载,解决了传统html解析无法获取动态生成链接的问题。

1. 问题背景:动态下载链接的挑战

在自动化网页操作中,我们经常会遇到需要点击按钮来下载文件的情况。然而,许多现代网页应用(SPA或使用ajax)的下载按钮并不会直接在HTML中包含一个href属性指向下载链接。相反,它们通常会触发一个JavaScript事件,该事件随后向后端发起一个网络请求,从而启动文件下载。在这种情况下,传统的dom解析方法(如获取href属性)将无法获取到实际的下载URL。

例如,一个下载按钮可能看起来像这样,没有任何直接的URL信息:

<button type="button" class="v-btn ..." aria-label="Export CSV" title="Export CSV">   <span class="v-btn__content">     <i class="v-icon ... mdi-download ..."></i>   </span> </button>

要获取这种按钮所关联的下载链接,我们需要一种不同的策略:拦截并监听按钮点击后发出的网络请求。

2. 核心原理:网络请求拦截

Puppeteer提供了一个强大的功能:网络请求拦截(Network Request Interception)。通过page.waitForRequest()方法,我们可以等待并捕获页面发出的特定网络请求。当一个按钮点击后触发文件下载时,浏览器会向服务器发起一个下载请求。只要我们能在这个请求发出之前设置好监听器,就可以捕获到该请求的URL。

关键在于:

  • 时序性: 必须在点击按钮之前设置好请求监听器。
  • 过滤: 可能有多个请求发出,需要通过请求的URL模式、方法或类型来筛选出我们关心的下载请求。
  • 并发 点击按钮和等待请求这两个动作需要同时进行,或者说,等待请求的承诺需要在点击动作发生之前就处于监听状态。

3. 实现步骤与示例代码

以下是如何使用Puppeteer捕获动态下载链接的详细步骤和示例代码:

3.1 准备工作

首先,确保你已经安装了Puppeteer:

npm install puppeteer

3.2 自动化流程

我们将以一个实际案例为例:从data.ademe.fr网站下载一个csv文件。这个过程通常涉及两个点击:首先点击一个“下载数据”按钮(可能是一个通用按钮,用于展开下载选项),然后点击一个“导出CSV”按钮(实际触发下载)。

const puppeteer = require("puppeteer");  let browser;  (async () => {   try {     // 1. 启动浏览器实例     browser = await puppeteer.launch();     const [page] = await browser.pages(); // 获取默认页面或新页面      // 2. 导航到目标URL     const url = "https://data.ademe.fr/datasets/liste-des-entreprises-rge-2";     await page.goto(url, { waitUntil: "domcontentloaded" }); // 等待DOM内容加载完成      // 3. 定位并点击第一个触发下载流程的按钮(如果有的话)     // 这个按钮可能只是展开了下载选项,或者触发了数据加载     const initialBtn = await page.waitForSelector('[aria-label="Téléchargement des données"]');     await initialBtn.click();      // 4. 关键步骤:设置请求监听器并点击实际的下载按钮     // 使用 Promise.all 确保请求监听器在点击动作发生之前就已激活     const [request] = await Promise.all([       // 等待满足特定条件的网络请求       page.waitForRequest(req =>         // 过滤条件:请求URL以.csv结尾,或者包含特定的API路径         req.url().endsWith(".csv") ||         req.url().includes("data.ademe.fr/data-fair/api/v1/datasets/liste-des-entreprises-rge-2")       ),       // 定位并点击实际触发下载的按钮       (await page.waitForSelector('[aria-label="Export CSV"]')).click()     ]);      // 5. 获取并打印捕获到的下载链接     console.log("捕获到的下载URL:", request.url());      // 6. (可选)根据捕获到的URL下载文件     // 如果需要下载文件,可以调用一个辅助函数,例如:     // await downloadFile(request.url(), "downloaded_file.csv"); // 假设 downloadFile 是一个已定义的函数     // 参考 Stack overflow 上的下载文件函数:https://stackoverflow.com/a/51302466/6243352    } catch (err) {     console.error("发生错误:", err);   } finally {     // 7. 关闭浏览器实例     if (browser) {       await browser.close();     }   } })();

3.3 代码解析

  • browser = await puppeteer.launch();: 启动一个新的浏览器实例。
  • const [page] = await browser.pages();: 获取当前浏览器中打开的第一个页面。
  • await page.goto(url, { waitUntil: “domcontentloaded” });: 导航到目标网页。waitUntil: “domcontentloaded”表示等待DOM内容加载完成,通常比networkidle0更快,且适用于大多数情况。
  • await page.waitForSelector(‘[aria-label=”Téléchargement des données”]’);: 等待页面上出现具有特定aria-label属性的元素。这是定位按钮的关键。
  • await initialBtn.click();: 点击定位到的按钮。
  • Promise.all([…]): 这是实现请求拦截的关键。Promise.all会并行执行数组中的所有Promise,并等待它们全部完成。
    • page.waitForRequest(req => …): 这个Promise会等待页面发出一个符合我们条件的网络请求。传入的回调函数是一个谓词(predicate),当请求对象req满足条件时(例如,URL以.csv结尾,或者包含特定的API路径),该Promise就会被解析,并返回捕获到的Request对象。
    • (await page.waitForSelector(‘[aria-label=”Export CSV”]’)).click(): 这个Promise会等待实际的“导出CSV”按钮出现,然后点击它。
    • 通过将page.waitForRequest放在Promise.all中,我们确保了请求监听器在点击动作发生之前就已经处于活跃状态,从而不会错过请求。
  • request.url(): 从捕获到的Request对象中获取实际的下载URL。
  • 错误处理与资源释放: try…catch…finally结构确保了即使发生错误,浏览器实例也能被正确关闭。

4. 注意事项与优化

  • 请求过滤条件: page.waitForRequest的回调函数至关重要。你需要根据目标网站的实际情况来编写合适的过滤逻辑。常见的过滤条件包括:
    • req.url().endsWith(“.csv”) 或 endsWith(“.zip”) 等文件扩展名。
    • req.url().includes(“api/download”) 等API路径关键词。
    • req.method() === ‘GET’ 或 POST。
    • req.resourceType() === ‘document’ 或 xhr 等资源类型。
  • 时序问题: 如果点击按钮后立即发出请求,确保waitForRequest的设置在点击之前完成。Promise.all是解决此问题的优雅方式。
  • 多个请求: 如果点击一个按钮可能触发多个相关请求,你需要确保你的过滤条件足够精确,只捕获到你真正需要的下载请求。
  • 动态URL模式: 有些网站的下载URL可能包含动态参数(如时间戳、用户ID等)。只要这些参数不影响URL的整体模式识别,includes或match正则表达式仍然有效。
  • 文件下载: 捕获到URL后,Puppeteer本身不直接提供文件下载功能(它模拟的是浏览器行为,但不会将文件保存到本地)。你需要使用Node.JS的http/https模块或第三方库(如node-fetch或axios)来发起HTTP请求并保存文件。
  • 区域差异: 某些网站可能根据用户地理位置或语言设置提供不同的下载链接或按钮文本。在编写选择器和请求过滤条件时,需要考虑到这些潜在差异。

5. 总结

通过利用Puppeteer强大的网络请求拦截能力,我们可以有效地处理那些不直接暴露下载URL的动态按钮。page.waitForRequest结合Promise.all提供了一种健壮且灵活的方法,用于在自动化脚本中捕获并利用这些动态生成的下载链接,从而实现更复杂的网页自动化任务,例如批量下载文件。理解并熟练运用这一技术,将大大扩展Puppeteer在复杂场景下的应用能力。

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享