本文介绍如何使用 chromote 驱动无头 chrome,自动定位并提取由 javascript 动态渲染的 iframe 中的图表原始数据(如 google charts),绕过静态 html 无法获取动态内容的限制,并最终结构化为 r 数据框。
本文介绍如何使用 chromote 驱动无头 chrome,自动定位并提取由 javascript 动态渲染的 iframe 中的图表原始数据(如 google charts),绕过静态 html 无法获取动态内容的限制,并最终结构化为 r 数据框。
现代网页(尤其是基于 Wix、Thunderbolt 等可视化建站平台构建的站点)常将交互式图表封装在 <iframe> 中,其 src 地址并非硬编码于主页面 HTML,而是由前端 JavaScript 运行时动态生成并注入 DOM。这意味着仅靠 httr::GET() + rvest 解析原始 HTML 必然失败——你看到的只是占位 <div id="comp-iw3d16s21">,真实链接藏在 JS 执行后的 DOM 树深处。
要可靠提取此类数据,核心思路是:让 R 控制一个真实浏览器环境,完整执行页面 JS,再从渲染后的 DOM 中精准定位并读取数据。以下是以 Immostat 市场数据页 中“Office Investment Prices (gross € psqm)”图表为例的完整实现流程:
使用 chromote 创建会话,导航至目标页面,并等待 JS 渲染完成(loadEventFired() + 短暂休眠确保 iframe 加载):
library(chromote)library(rvest)library(jsonlite)library(dplyr)b <- ChromoteSession$new()b$Page$navigate("https://www.php.cn/link/0c031fe92fdc7596e4f20c56c661319d")b$Page$loadEventFired()Sys.sleep(0.5) # 确保 iframe DOM 已挂载
不依赖易变的 ID 或 class,而是以标题文本 "Office Investment Prices (gross € psqm)" 为锚点,用 XPath 向下查找关联的 iframe 元素并提取 src:
iframe_src <- b$Runtime$evaluate(' const xpath = '//h2[text()="Office Investment Prices (gross € psqm)"]/../following-sibling::div//iframe'; const iframe = document.evaluate(xpath, document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null).iterateNext(); iframe ? iframe.getAttribute("src") : null;')$result$valueif (is.null(iframe_src)) stop("未找到目标 iframe")iframe_src#> "https://www-immostat-com.filesusr.com/html/ae915e_6e3e7e9d19bc8d5af1f3e4b96ae5c686.html"
⚠️ 注意:XPath 中使用 following-sibling::div//iframe 是因实际 DOM 结构中 iframe 嵌套在兄弟 div 内;若结构变化,需用浏览器开发者工具重新验证路径。
目标 iframe 页面内含 Google Charts 初始化代码(如 var dataIDF = google.visualization.arrayToDataTable([...]))。我们用 rvest 提取该 <script> 块,提取关键行,并将其改造为可直接返回 JSON 的表达式:
script_content <- read_html(iframe_src) %>% html_element(xpath = "//script[contains(., 'var dataIDF')]") %>% html_text()# 提取并重写:将 arrayToDataTable(...) 替换为 JSON.stringify(...)data_js <- script_content %>% str_split("") %>% unlist() %>% str_subset("var dataIDF") %>% str_replace("var dataIDF = google.visualization.arrayToDataTable(", "JSON.stringify(")# 在已运行的 Chrome JS 环境中执行,直接获得 JSON 字符串json_str <- b$Runtime$evaluate(data_js)$result$valuechart_data_list <- parse_json(json_str)
原始解析结果为嵌套列表(首行为列名,后续行为数据行),需标准化列名与类型:
# 第一行作为列名,其余行转为命名列表header <- chart_data_list[[1]]data_rows <- chart_data_list[-1]# 统一转换:数值列自动识别(避免字符串化)df <- data_rows %>% lapply(setNames, header) %>% do.call(rbind, args = _) %>% as.data.frame(stringsAsFactors = FALSE) %>% # 强制数值列转 numeric(示例中第二列为价格) mutate(`Greater Paris Region` = as.numeric(`Greater Paris Region`))head(df)#> Quarter Greater Paris Region#> 1 Q1 2006 4490#> 2 Q2 2006 4631#> 3 Q3 2006 4803#> 4 Q4 2006 4837#> 5 Q1 2007 4953#> 6 Q2 2007 5064
此方法不依赖逆向分析复杂 JS 参数(如 Thunderbolt 的 35 参数 API),而是直击渲染结果,兼具鲁棒性与可维护性——只要图表标题和 iframe 逻辑结构不变,脚本即可长期有效。