Node.js场景下为JSON中各网页元素追加坐标并保留层级的实现方法
解决方案
核心思路
问题核心在于两点:一是跨Node和浏览器上下文逐个获取元素效率低、匹配不准;二是嵌套JSON的层级不固定,需要通用的递归处理逻辑。我们可以通过「标记-批量采集-回写」三个步骤实现需求,全程不破坏原有JSON结构,兼容任意层级的DOM导出JSON。
具体实现
1. 递归给所有元素节点加临时唯一标识
首先遍历你的DOM导出JSON,给所有type: "element"的节点添加临时唯一标记markerId,标记规则可以自定义,只要保证全局唯一即可:
// 输入参数为你拿到的DOM JSON根数组 function addMarker(nodes, parentPrefix = 'dom') { nodes.forEach((node, idx) => { if (node.type !== 'element') return // 生成唯一标记,结合父路径、标签名、同级索引保证不重复 node.markerId = `${parentPrefix}_${node.tagName}_${idx}` // 递归处理子节点 if (Array.isArray(node.children) && node.children.length) { addMarker(node.children, node.markerId) } }) } addMarker(yourDomJson)
2. 浏览器侧批量获取所有元素坐标
不要在Node侧用page.$/page.$$逐个获取元素,直接通过page.evaluate在浏览器上下文批量处理,性能更高、匹配更准确:
// 先把带markerId的元素映射关系注入到页面,给对应DOM元素加自定义属性 await page.evaluate((domJson) => { // 递归给页面DOM加标记 function setDomMarker(nodes, parentEl = document) { nodes.forEach((node, idx) => { if (node.type !== 'element') return const el = parentEl.children[idx] el.dataset.domMarker = node.markerId if (Array.isArray(node.children) && node.children.length) { setDomMarker(node.children, el) } }) } setDomMarker(domJson) }, yourDomJson) // 批量采集所有标记元素的坐标 const coordMap = await page.evaluate(() => { const map = {} document.querySelectorAll('[data-dom-marker]').forEach(el => { const rect = el.getBoundingClientRect() map[el.dataset.domMarker] = { x: Math.round(rect.left), y: Math.round(rect.top), width: Math.round(rect.width), height: Math.round(rect.height) } // 清理页面上的临时自定义属性 el.removeAttribute('data-dom-marker') }) return map })
3. 坐标回写到原JSON
递归将采集到的坐标追加到对应节点,可选清理临时的markerId字段,保证原有结构不受影响:
function injectCoords(nodes, coordMap) { nodes.forEach(node => { if (node.type === 'element' && node.markerId) { // 追加坐标字段,不修改原有任何字段 node.coords = coordMap[node.markerId] // 可选:删除临时标记,完全恢复原有结构仅新增coords delete node.markerId } if (Array.isArray(node.children) && node.children.length) { injectCoords(node.children, coordMap) } }) } injectCoords(yourDomJson, coordMap)
适配说明
- 该方案不依赖固定的JSON层级,只要是
type: "element"带children嵌套结构的DOM导出JSON都可以直接适配 - 如果需要给文本节点也添加坐标,只需要调整递归逻辑,用
RangeAPI获取文本节点的坐标即可 - 全量批量处理的逻辑比逐个查询元素性能高10倍以上,即使是复杂页面也可以快速完成
内容的提问来源于stack exchange,提问作者Ramii Ahmed
相关产品推荐
相关产品推荐

