WebXR 平面检测能力原本用于在增强现实场景中识别地面、桌面和墙壁,但它在不同浏览器、WebXR 运行时和图形驱动上的实现差异,已经形成了一组可用于浏览器指纹识别的特征。使用 R 语言编写网络爬虫时,如果能主动采集并分析这些特征,就能更准确地判断目标站点是否在利用 WebXR 进行设备识别,同时也能优化自身请求策略。本文围绕平面检测特征来源、R 语言采集方法和特征识别流程展开。

需要说明的是,WebXR 平面检测目前仍是相对前沿的 API,不同浏览器开启方式不一致。在 Chrome 中通常需要启用 WebXR Incubations 相关开关,或者使用支持 ARCore 的安卓设备。对爬虫而言,能否触发真实平面检测并不影响指纹采集,因为能力检测失败的类型、错误名称和返回结构同样具有区分价值。
一、WebXR平面检测暴露了哪些可识别特征
平面检测并不是一个简单的布尔值。当页面通过 navigator.xr.requestSession 申请 immersive-ar 会话并带上 plane-detection 特性时,不同环境会返回截然不同的结果。有些浏览器直接拒绝该特性,有些会接受但延迟到用户激活设备后才返回平面,还有一些无头浏览器虽然表面支持,却在帧数据里永远给出空列表。这些行为差异就是指纹识别的入口。
从实际采集角度看,值得关注的原始特征主要包括:XRPlane 对象的 orientation 方向枚举值是否完整;polygon 多边形顶点数量是否稳定,以及顶点坐标是否带有明显的浮点尾数特征;lastChangedTime 的时间精度是毫秒还是微秒;semanticLabel 语义标签是否包含 floor、wall、table 等类别,标签大小写和枚举顺序是否统一。这些字段在不同实现中经常出现细微偏差,组合后可以形成高辨识度指纹。
另一个容易被忽略的特征是运行时差异。WebXR 底层可能由 OpenXR、ARCore 或厂商私有运行时驱动,某些运行时会在全局对象或错误堆栈中留下名称线索。即使页面脚本没有主动暴露这些信息,爬虫也可以通过捕获 requestSession 的异常类型,例如 NotSupportedError、SecurityError 或 InvalidStateError,来判断当前环境属于真实设备、模拟器还是无头浏览器。
二、在R语言中采集平面检测数据
R 语言本身无法直接操作 WebXR,但可以通过 Chrome DevTools 协议或 WebDriver 驱动浏览器执行 JavaScript。这里推荐使用 chromote 包,它比 RSelenium 更轻量,能直接接收异步函数的返回结果。首先启动一个 Chromote 会话,并打开要检测的页面,然后注入脚本检查 navigator.xr 是否存在。
library(chromote)
b <- ChromoteSession$new()
b$Runtime$enable()
b$Page$navigate("https://目标站点")
res <- b$Runtime$evaluate(
expression = "(function() { return navigator.xr ? 'hasXR' : 'noXR'; })()",
awaitPromise = FALSE,
returnByValue = TRUE
)
cat(res$result$value)
这段代码只做了基础能力检测,但已经能区分完全不支持 WebXR 的普通浏览器和带 WebXR 运行时的环境。如果返回值为 noXR,说明当前无头浏览器没有暴露任何 XR 接口,后续平面检测自然无法进行。如果返回 hasXR,则可以继续申请会话并读取平面数据。
要深入采集平面检测特征,需要注入更完整的异步脚本。下面这段 JavaScript 会尝试申请 immersive-ar 会话,要求启用 plane-detection 特性。如果申请成功,它会读取第一帧中的 detectedPlanes,提取方向、顶点数、语义标签和最后变更时间。即使申请失败,错误名称和错误信息也会作为指纹返回。
(async function() {
if (!navigator.xr) return {xr: false};
const supported = await navigator.xr.isSessionSupported('immersive-ar');
let session = null;
let planes = [];
try {
session = await navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['plane-detection'],
optionalFeatures: []
});
const frame = await new Promise(function(resolve) {
session.requestAnimationFrame(resolve);
});
if (frame.detectedPlanes) {
planes = frame.detectedPlanes.map(function(p) {
return {
orientation: p.orientation,
vertexCount: p.polygon ? p.polygon.length : 0,
semanticLabel: p.semanticLabel || null,
lastChanged: p.lastChangedTime || 0
};
});
}
await session.end();
} catch (err) {
return {xr: true, supported: supported, error: err.name, message: err.message};
}
return {xr: true, supported: supported, planes: planes};
})()
把这段脚本保存为独立的 webxr_probe.js 文件,R 代码就可以读取文件内容并通过 Runtime$evaluate 执行。使用 awaitPromise = TRUE 可以等待异步函数结束,returnByValue = TRUE 则要求浏览器把结果直接以值形式返回,而不是返回引用。拿到返回值后,用 jsonlite::fromJSON 解析即可。
library(chromote)
library(jsonlite)
b <- ChromoteSession$new()
b$Runtime$enable()
js_code <- paste(readLines("webxr_probe.js"), collapse = "\n")
res <- b$Runtime$evaluate(
expression = js_code,
awaitPromise = TRUE,
returnByValue = TRUE
)
fp <- fromJSON(res$result$value, simplifyVector = FALSE)
cat(fp$supported)
cat(fp$error)
在实际运行时,无头 Chrome 通常会返回 NotSupportedError 或者直接拒绝 plane-detection 特性。这种情况下,错误名称和 supported 的布尔值就构成了一个有效指纹。如果使用带有 WebXR 模拟能力的浏览器或真实安卓设备,fp$planes 中会包含平面对象,后续就可以进行更细致的几何和语义特征提取。
三、平面检测指纹的提取与特征识别
原始平面数据往往包含大量顶点坐标,直接存储和比对效率很低。更合适的做法是从中提取统计特征,例如平面数量、顶点数均值、方向标签分布、语义标签集合以及时间戳粒度。这些聚合特征既保留了指纹的区分能力,又降低了数据维度。
vertex_counts <- unlist(lapply(fp$planes, function(p) p$vertexCount)) orientation_table <- table(unlist(lapply(fp$planes, function(p) p$orientation))) semantic_labels <- unlist(lapply(fp$planes, function(p) p$semanticLabel)) print(mean(vertex_counts)) print(sd(vertex_counts)) print(orientation_table) print(unique(semantic_labels))
有了这些特征,就可以对不同来源的采集结果进行相似度计算。例如语义标签集合可以使用 Jaccard 相似度,顶点数分布可以比较均值和标准差,方向标签可以比较各方向平面的占比。通过给不同特征分配权重,能形成一个稳定的平面检测指纹向量。对于设备识别任务,这个向量可以作为分类器的输入;对于反爬判断,可以观察目标站点的 WebXR 脚本是否在特定无头环境下产生明显异常。
需要注意的是,平面检测指纹存在一定漂移。真实环境中光照变化、摄像头移动和算法更新都会导致检测到的平面数量和顶点坐标波动。因此不建议用单次采集结果作为硬性匹配依据,更适合做时间窗口内的聚合。可以在 R 中用滑动窗口对多次采集的顶点数取中位数,过滤掉偶发异常。无头环境由于不存在真实平面,通常会返回固定空值或固定错误,这种稳定性本身也可以作为一种识别特征。
综合来看,WebXR 平面检测指纹虽然不如 Canvas 或 WebGL 指纹那样普遍,但在涉及 AR、VR 或沉浸式交互的站点中具有很高的区分价值。R 语言爬虫通过 Chromote 或 WebDriver 注入探测脚本,可以低成本地采集这些特征。即使无头环境无法获得真实平面数据,错误类型、支持状态和运行时差异仍然足以辅助设备识别和反爬策略调整。后续如果目标站点在服务端根据平面检测结果做风控,爬虫也能提前知道自身暴露了哪些特征,从而做出更合适的请求伪装。