,真实链接藏在 JS 执行后的 DOM 树深处。
要可靠提取此类数据,核心思路是:让 R 控制一个真实浏览器环境,完整执行页面 JS,再从渲染后的 DOM 中精准定位并读取数据。以下是以 Immostat 市场数据页 中“Office Investment Prices (gross € psqm)”图表为例的完整实现流程:
✅ 步骤 1:启动无头浏览器并加载页面
使用 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 已挂载✅ 步骤 2:通过语义化 XPath 定位动态 iframe
不依赖易变的 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$value
if (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 内;若结构变化,需用浏览器开发者工具重新验证路径。
✅ 步骤 3:解析 iframe 页面中的图表初始化脚本
目标 iframe 页面内含 Google Charts 初始化代码(如 var dataIDF = google.visualization.arrayToDataTable([...]))。我们用 rvest 提取该 <script> 块,提取关键行,并将其改造为可直接返回 JSON 的表达式:</script>
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$value
chart_data_list <- parse_json(json_str)✅ 步骤 4:结构化为规范数据框
原始解析结果为嵌套列表(首行为列名,后续行为数据行),需标准化列名与类型:
# 第一行作为列名,其余行转为命名列表
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
? 关键注意事项与优化建议
-
稳定性优先:避免硬编码 XPath 或类名,始终以用户可见文本(如 h2 标题)为定位基准;
-
错误防御:添加 tryCatch() 检查 iframe_src 和 json_str 是否为空,防止网络波动或页面改版导致中断;
-
性能考量:chromote 启动开销较大,若需高频采集,建议复用会话(b$Browser$close() 前保持连接);
-
替代方案:RSelenium 同样可行,但 chromote 更轻量、调试更直观;若需跨平台部署,可考虑 playwright(R 包 pwa);
-
合规提醒:遵守 robots.txt 及网站 Terms of Service;对高频请求添加合理延时,必要时设置 User-Agent。
此方法不依赖逆向分析复杂 JS 参数(如 Thunderbolt 的 35 参数 API),而是直击渲染结果,兼具鲁棒性与可维护性——只要图表标题和 iframe 逻辑结构不变,脚本即可长期有效。