
本文介绍为何传统 html 解析无法获取网页中由 javascript 动态渲染的 pdf 链接,并提供基于 f# 的浏览器自动化解决方案(canopy + chromedriver),实现可靠、可等待、可交互的 pdf 链接提取。
在爬取类似 https://www.kodis.cz/lines/region?tab=232-293 这类现代前端网站时,你可能会发现:无论使用 FSharp.Data.HtmlProvider、HtmlAgilityPack 还是 HttpClient 获取原始 HTML,都无法提取到 PDF 文件链接(如 https://kodis-files.s3.eu-central-1.amazonaws.com/...pdf)。根本原因在于:这些 标签并非存在于服务端返回的初始 HTML 中,而是由 React 框架在浏览器中运行 JavaScript 后动态插入 DOM 的。
这意味着:
- 服务端响应中 内容为空或仅含占位符;
- 所有 PDF 链接均由客户端 JS 异步加载(可能通过 API 请求后渲染);
- 纯静态 HTML 解析器(包括 HtmlDocument.Load())只能看到“骨架”,看不到“血肉”。
✅ 正确解法:使用真实浏览器环境执行 JS,再提取渲染后的 DOM。
推荐方案:F# + Canopy 浏览器自动化
Canopy 是一个轻量、函数式风格的 F# 浏览器自动化库(底层基于 Selenium WebDriver),专为简洁、可读性强的 UI 测试与爬虫场景设计。
立即学习“Java免费学习笔记(深入)”;
✅ 快速上手步骤
-
安装依赖(在 .fsx 脚本顶部):
#r "nuget:canopy" #r "nuget:Selenium.WebDriver" #r "nuget:Selenium.WebDriver.ChromeDriver"
-
配置 ChromeDriver 路径(需提前下载匹配版本的 chromedriver.exe):
open canopy open canopy.classic
canopy.configuration.chromeDir
3. **启动浏览器并导航**: ```fsharp start chrome url "https://www.kodis.cz/lines/region?tab=232-293"
-
智能等待 + 提取 PDF 链接(关键!避免因加载延迟漏抓):
// 定义就绪条件:至少出现 11 个 PDF 卡片(根据实际页面结构调整) let pdfCardsLoaded () = (elements ".Card_wrapper__ZQ5Fp").Length >= 11
compareTimeout
// 提取所有含 PDF 的 href 属性 elements "a" |> List.filter (fun el -> let href = el.GetAttribute("href") System.String.IsNullOrEmpty(href) |> not && href.EndsWith(".pdf", System.StringComparison.OrdinalIgnoreCase)) |> List.iter (fun el -> let href = el.GetAttribute("href") printfn "PDF link: %s" href)
> ? 提示:若页面支持“加载更多”(如 “Další” 按钮),可模拟点击分页: > ```fsharp > click (elementWithText "button" "Další") // 或用更精确的选择器,如 "[data-testid='load-more']" > waitFor (fun () -> (elements ".Card_wrapper__ZQ5Fp").Length >= 22) > ``` #### ⚠️ 注意事项 - **务必设置 `waitFor`**:React 页面渲染有延迟,直接 `elements "a"` 可能返回空结果; - **使用 `GetAttribute("href")` 而非 `InnerText()`**:PDF 链接常位于空 `` 标签内(无文本内容),仅靠 `href` 属性存在; - **PDF 判断建议用 `.EndsWith(".pdf", OrdinalIgnoreCase)`**:避免误判查询参数(如 `?file=report.pdf&t=123`); - **生产环境请添加异常处理与资源清理**: ```fsharp try // ... 主逻辑 finally quit() // 关闭浏览器? 替代方案对比
方案 是否执行 JS 是否需浏览器 易用性 适用场景 HtmlAgilityPack / FSharp.Data ❌ ❌ ⭐⭐⭐⭐ 静态 HTML(服务端渲染 SSR) Puppeteer Sharp (.NET) ✅ ✅ ⭐⭐⭐ 需精细控制(如拦截请求、截图) Canopy ✅ ✅ ⭐⭐⭐⭐⭐ F# 项目首选:语法简洁、等待机制成熟、社区活跃 直接调用后端 API(如你已发现的 /api/schedules) N/A ❌ ⭐⭐⭐⭐⭐ 最优解(若接口稳定且无鉴权)——绕过渲染层,高效可靠 ✅ 最佳实践建议:优先逆向分析网络请求(Chrome DevTools → Network → XHR/Fetch)。若发现 PDF 列表由 GET /api/documents?tab=232-293 返回 JSON,则直接调用该 API(配合 HttpClient + JWT/cookie 处理)比浏览器自动化更稳定、更快、更易维护。
总之,面对 JS 渲染的 PDF 链接,放弃“静态解析幻想”,拥抱“真实浏览器执行”——Canopy 让这一过程在 F# 中既强大又优雅。








