H5页面一键分层实战:用AI Skill提取图层树与设计资源
2026/9/5 5:56:33 网站建设 项目流程

在做前端重构或者设计稿二次修改时,最麻烦的局面不是代码本身难懂,而是手里只有一个线上 H5 页面,却找不到对应的 PSD 或设计源文件。活动页急着改版、落地页要换视觉、旧项目需要拆出可复用的组件图,一旦没有分层源文件,很多人只能回到“截图 + 多边形套索 + 手动抠图”的老路上。近两年 AI 编码工具开始普及 Skill 机制,把“识别页面结构、提取视觉资源、输出分层图层树”这类重复工作封装成一条可复用命令。这篇内容就来拆解如何把一个“一键把 H5 页面分层”的 Skill 做成真实可运行的工作台能力,并且能输出结构化的图层树、资源目录和标注图。

做这种自动分层工具,难点不在于打开一个页面,而在于怎么从 HTML、CSS 和渲染结果中提炼出设计师理解的那种“图层关系”。浏览器最终渲染出来的是一个平面结果,原设计稿中的背景组、文案组、按钮组、遮罩层已经被压平成 DOM 树。如果只在截图层面处理,只能得到一块整体位图,无法重新编辑。因此更务实的做法是:让 Skill 调度脚本读取页面结构,把视觉区域归纳成带边界和层级的图层树,再配合页面资源抽取和视觉标注图,帮助使用者快速还原成可编辑的工程文件。下面会按照从概念到实现、再到排错和落地的顺序,把这条链路完整展开。

1. H5 页面分层的核心问题:浏览器只给了渲染结果,没有给源文件

1.1 工作中真正需要“分层”的场景

需要把 H5 页面分层的场景,通常不是程序设计阶段,而是页面已经上线之后的二次设计。

常见场景包括:

  • 运营反馈活动页需要更换头图,但当年的设计源文件已经丢失。
  • 设计师想参考某个 H5 页面的版式,重新绘制成自己的设计稿。
  • 前端要从老页面中抽取一套可复用的图标、背景图或卡片资源,用于新项目。
  • 测试环境中的页面需要按区域拆分做视觉走查,不能只靠整页长截图。
  • 自研运营活动平台需要把别的渠道的页面结构迁移到自家模板体系。

这些场景的共同点都是“先有页面,后有源文件”,或者“页面即最终产物”。如果直接截一整张长图,后续无论是修改按钮文案,还是换一张背景底图,都要在图像编辑器里重新用选区切割,可编辑性非常差。

把 H5 页面拆成分层结构,核心目的就是把设计师或前端修改时关心的视觉信息重新分离出来:哪些是顶层弹窗、哪些是内容卡片、哪些是背景图、哪些是文本块、哪些是图标资源。只有把这些信息拆开,后续才能继续在页面设计工具或代码工程里进行局部调整。

1.2 为什么人工切图拆层效率很低

H5 页面和传统平面设计稿最大的差异是:H5 的视觉边界并不总等于 DOM 标签边界。

一个外层容器可能同时承担背景色、圆角、边框、文案定位和点击区域,内部又嵌套了七八个子元素;一个文本块可能被浏览器自动拆成多个行盒;一个按钮的阴影效果可能在 CSS 里由伪元素绘制。人工切图时,需要不断用开发者工具查看元素,再判断哪些元素应该合并为一个图层,哪些元素应该作为独立资源导出。页面越大,这种判断题越多,并且操作重复性极高。

如果页面还依赖异步接口返回数据,那么截取整页时的状态可能和线上数据态不一致;如果页面包含弹窗、轮播、播放器等组件,整页截图不能表达它们的层级关系,因为弹窗在 DOM 层面始终存在,只是平时不显示,而截图只能记录当前可见状态。

因此把关注点放在“整体截图”上是走不通的,应该把分层动作拆解成几类信息的提取:DOM 结构、渲染矩形位置、CSS 关键样式、图片资源地址。这些信息经过合并和过滤后,才能组装成设计师能够理解的图层树。

1.3 Skill 封装让“分层经验”变成可复用流程

Skill 在一键工作台里的价值,是把过去写在文档里的检查清单和人工判断逻辑,变成可执行的脚本,并允许 AI Agent 根据用户命令自动调度。

对于 H5 页面分层来说,Skill 可以约定好输入参数、执行顺序和输出格式。当用户说“分析这个 H5 页面的分层结构”时,Skill 不是简单去访问一次 URL,而是按预定流程完成页面加载、结构采集、视觉分类、资源输出和结果汇总。这样一套经验可以被团队复用,不同的成员只要调用同一套 Skill,就能得到格式统一的输出。

还需要注意的是,Skill 不适合把全部判断都硬编码在脚本中。不同页面的特殊性很强,有的页面以文本为主,有的页面以图片为主,有的页面完全由 Canvas 绘制。分层规则要保留一定的可调整空间,脚本负责输出基础数据,Agent 或使用者负责对异常页面做决策,这样的分工才能让 Skill 既稳定又灵活。

2. 把一个 H5 页面拆成哪些“层”,先定清楚再写脚本

2.1 页面可以拆解成语义层、视觉层和资源层

在动手实现之前,需要先统一概念。一个 H5 页面并不只有一种“层”。

从语义上看,页面由头部区域、内容区块、底部导航、弹窗遮罩等结构组成。从视觉编辑角度看,页面又可以分为背景层、插图层、文本层、图标层、按钮层、浮层。从代码资源角度看,页面由 HTML 片段、CSS 类、图片 URL、字体文件和音频视频地址组成。

做分层工具时,需要在三者之间建立映射关系:

  • DOM 中的 section/div/view 容器可以映射成视觉编辑中的组或文件夹。
  • img 标签或带 background-image 的元素可以映射成图片层,输出时保留原始 URL。
  • 文本元素可以映射成文本图层,即使无法直接还原成可编辑文字的 PSD 文本,也要在标注图里给出位置和文字内容。
  • fixed 定位或 z-index 很高的元素可以映射成顶层浮层。
  • video、iframe、canvas 这类特殊元素需要单独归类,因为它们在普通设计工具中并无直接等价物,要降级为占位层。

分层工具输出给设计工具之前,先要把这些分类拟定好,否则后续代码只是对标签做“是否包含图片”的判断,很难覆盖真实页面。

2.2 从浏览器中可以拿到四类基础信息

浏览器中的 DOM API 能提供完成这个任务所需要的全部输入。

第一类是文档树,通过document.body可以遍历到页面所有标签,并得到它们的标签名、class 和 id。第二类是渲染矩形,通过getBoundingClientRect()能得到每个元素相对视口的坐标和尺寸。第三类是计算样式,通过getComputedStyle()能得到元素的背景色、背景图、圆角、透明度、定位方式等关键属性。第四类是资源地址,图片链接、视频链接、iframe 地址通常可以直接从元素属性上取得。

有了这四类基础信息,分层脚本其实扮演的角色就是一个“结构分析器”:把浏览器已经压平的 DOM 重新按视觉区块组织起来。这里最需要注意的问题是,DOM 树深度并不等于视觉层级。很多 DOM 节点只是包裹容器,没有背景、没有图片、没有文字,强行把它们输出成图层只会让结果杂乱无章。所以分层脚本必须内置过滤与合并规则。

2.3 分层策略:结构优先,必要时允许降级

真实 H5 页面不是设计软件输出的画板,代码结构和视觉层级之间常常出现不一致。

例如一个背景层在一个 div 上,另一个 div 里包含多个图片绝对定位,还有一个按钮使用绝对定位浮在图片上方。此时如果只靠 DOM 递归顺序,可能把背景层排在内容层之后。正确做法是先提取每个候选元素的矩形和z-index,再按以下策略处理:

  • 尺寸覆盖整个页面且没有交互内容的背景元素,归入背景层。
  • 尺寸接近父容器且同时包含背景样式和文本内容的元素,判断为主卡片。
  • z-index为正数且使用 fixed 定位的元素,归入顶层浮层。
  • 如果一个容器内的所有子元素都相对该容器绝对定位,并且子元素视觉上排列整齐,就把容器提升为组合层。
  • 如果页面整体由 canvas 绘制,无法解析内部元素,就降级输出成“canvas 页面”,只保留区域和资源地址。

工具不可能解决 100% 页面,但对常见页面能跑出稳定结果,已经在工程上具备价值。

2.4 Skill 工作台中的各角色分工

在这个自动分层 Skill 中,人、Agent、脚本、输出文件各承担不同角色。

人可以只给出 URL 或页面文件路径,并在最终结果中确认哪一层应该合并、哪一层应该拆分。Agent 负责解释用户的模糊指令,把“帮我看看这是怎么布局的”转成具体参数,并读取脚本输出向用户解释异常。脚本负责稳定提取页面数据和生成中间结果,减少 Agent 的幻觉风险。输出目录则承担人机可读的中转。

用户指令 | v Skill 校验输入参数 | v Playwright 页面渲染脚本 | v layer-tree.json + assets 资源 | v 可视化标注图 + 层级摘要 | v 用户决策与二次修正

这样的分工能够保证:只要是页面可以正常渲染,脚本输出的基础结构就是确定的;而关于“哪个元素更像一个独立图层”的主观判断,由用户或 Agent 在结果上继续做微调,而不是让脚本全自动做不可追溯的猜测。

3. 环境准备与 Skill 项目结构

3.1 技术选型与版本要求

实现这个 Skill 不需要非常重的技术栈,核心只需要浏览器自动化和 Node.js 脚本。

技术组成可以按下面的方式安排:

  • Node.js 18 及以上版本,用于运行命令行脚本。
  • Playwright,用于无头浏览器加载页面并执行 DOM 分析。
  • JSON 文件,作为图层树的通用中间格式。
  • 一个支持 Skill 机制的 AI 编码工作台,用于接收用户自然语言指令。
  • 可选 Python PIL 或 Sharp,用于后期生成视觉标注图。

如果原始页面是本地 HTML 文件,可以先通过静态服务器启动;如果页面是线上 URL,则直接让脚本访问。为了避免外部页面变化导致结果不稳定,建议先用自己可以控制的页面做验证。

下面是环境检查清单:

检查项检查方式通过标准
Node.js 已安装node -v输出 v18 以上
Playwright 已安装npm ls playwright无 missing 报错
浏览器二进制可用npx playwright install chromium无下载失败提示
技能目录可用ls skills/h5-page-layers能看到 SKILL.md
测试页面可访问curl -I 本地测试地址返回 200

需要特别注意的是 Playwright 的浏览器二进制需要单独安装。有些环境下npm install playwright后不下载浏览器文件,执行时就会报browserType.launch: Executable doesn't exist。这种情况需要先运行一次npx playwright install chromium

3.2 推荐目录结构

Skill 的目录结构要兼顾可读性和可执行性,建议参考下面的组织方式:

skills/h5-page-layers/ ├── SKILL.md ├── scripts/ │ ├── analyze-page.mjs │ ├── generate-overview.mjs │ └── export-resources.mjs ├── examples/ │ └── output/ │ ├── layer-tree.json │ ├── overview.png │ └── assets/ └── docs/ └── classification.md

SKILL.md 是技能描述文件,它告诉 AI Agent 该在什么时候启用这个 Skill,需要接收哪些参数,最终会输出什么。scripts 目录存放真正被调用的脚本。examples 目录里可以放一份预期输出样例,用于测试和验收。

在真实项目中,不一定严格要求每个脚本都预先写完,可以让 Agent 根据页面情况动态补建脚本,但底层固定的“读取 DOM 属性”“输出 JSON”“按矩形切割区块”等能力尽量预置,减少 Agent 在步骤上的随意性。

3.3 编写 SKILL.md 时的关键字段

SKILL.md 的写法决定 Agent 是否能正确调用这个能力。需要写清楚触发条件、参数约束和执行流程。

下面是一份可以直接改用的示例:

--- name: h5-page-layers description: 分析 H5 页面的视觉分层结构,输出图层树、资源目录和区域标注图。当用户要求拆分 H5 页面、查看 H5 布局层级、生成分层图时使用。 version: 0.1.0 requires: - playwright - nodejs args: - name: url description: 目标 H5 页面地址,可以是 http/https 本地地址 required: true - name: output description: 输出目录,默认 ./layer-output required: false run: - node scripts/analyze-page.mjs {url} {output} - node scripts/generate-overview.mjs {output}

配置说明:

  • name要唯一,尽量体现核心功能。
  • description中要包含触发关键词,让 Agent 遇到“H5 页面分层”、“把页面拆开”、“查看布局层级”这些描述时能匹配到。
  • args用来约束用户输入,避免 Agent 随意猜测 URL。
  • run里的脚本顺序要固定,先分析页面,再生成标注图。

注意,不同 Skill 机制对配置字段的兼容范围不同。真实落地时,先以目标工作台支持的方式为准,不要直接照搬某个固定格式。在配置完成后,应该用一条非常明确的示例指令测试 Agent 是否识别到了这个 Skill。

3.4 准备一个本地受控页面

正式用线上页面调试前,建议先准备一个结构清晰、包含背景图、文本、按钮和弹窗的本地页面。

下面这段 HTML 可以作为最小测试样例:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>分层测试页</title> <style> body { margin: 0; font-family: Arial, sans-serif; background: #f5f5f5; } .hero { position: relative; width: 375px; height: 300px; background-image: url('https://example.com/hero-bg.png'); background-size: cover; } .hero-title { position: absolute; left: 20px; bottom: 40px; color: #fff; font-size: 22px; } .card { width: 343px; margin: 16px; padding: 16px; background: #fff; border-radius: 12px; } .footer-btn { position: fixed; bottom: 20px; left: 16px; width: 343px; height: 44px; line-height: 44px; text-align: center; background: #1677ff; color: #fff; border-radius: 22px; z-index: 100; } </style> </head> <body> <div class="hero"> <div class="hero-title">活动主标题</div> </div> <div class="card"> <h3>卡片标题</h3> <p>这是一段卡片内容文本,用于验证文本层的提取。</p> </div> <div class="footer-btn">立即参与</div> </body> </html>

这个测试页包含背景图、绝对定位文本、卡片容器、fixed 按钮,四类典型分层对象都齐全。通过本地静态服务器或直接打开文件均可。

检查测试页可访问后,再运行第一个分析脚本。

4. 核心实现:一键提取 H5 页面图层树

4.1 主脚本 analyze-page.mjs 的职责

analyze-page.mjs要完成的事情可以拆成四个步骤:

  • 启动浏览器并访问目标页面。
  • 等待页面完全渲染,避免异步资源未加载导致结果不完整。
  • 遍历可见元素,提取尺寸、位置、层级、文本和样式。
  • 将结果转换为图层树 JSON 并写入输出目录。

下面的代码是主流程示例:

import { chromium } from 'playwright'; import { mkdir, writeFile } from 'node:fs/promises'; import path from 'node:path'; const url = process.argv[2]; const outputDir = process.argv[3] ?? './layer-output'; if (!url) { console.error('Usage: node analyze-page.mjs <url> [output]'); process.exit(1); } const browser = await chromium.launch({ headless: true }); const page = await browser.newPage({ viewport: { width: 375, height: 812 }, deviceScaleFactor: 2, }); console.log('Opening page:', url); await page.goto(url, { waitUntil: 'networkidle' }); await page.waitForTimeout(1000); const tree = await page.evaluate(() => { const root = []; const seen = new Set(); function isVisible(el) { const style = window.getComputedStyle(el); if (style.display === 'none' || style.visibility === 'hidden') return false; if (Number(style.opacity) === 0) return false; if (el.getClientRects().length === 0) return false; const rect = el.getBoundingClientRect(); return rect.width > 4 && rect.height > 4; } function createLayerInfo(el) { const rect = el.getBoundingClientRect(); const style = window.getComputedStyle(el); const layer = { id: el.id || '', tag: el.tagName.toLowerCase(), className: typeof el.className === 'string' ? el.className.slice(0, 200) : '', text: (el.innerText || '').trim().slice(0, 100), src: el.currentSrc || el.getAttribute('src') || '', rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height), }, style: { position: style.position || 'static', zIndex: style.zIndex || 'auto', background: style.backgroundColor || 'transparent', backgroundImage: style.backgroundImage || 'none', borderRadius: style.borderRadius || '0px', opacity: style.opacity || '1', }, children: [], }; return layer; } function walk(el) { if (seen.has(el)) return null; if (!isVisible(el)) return null; if (/^(SCRIPT|STYLE|META|LINK|NOSCRIPT)$/i.test(el.tagName)) return null; seen.add(el); const node = createLayerInfo(el); for (const child of el.children) { const childNode = walk(child); if (childNode) { node.children.push(childNode); } } return node; } const bodyLayer = walk(document.body); if (bodyLayer) root.push(bodyLayer); return root; }); await mkdir(outputDir, { recursive: true }); const result = { url, device: '375x812', generatedAt: new Date().toISOString(), tree, }; const outputPath = path.join(outputDir, 'layer-tree.json'); await writeFile(outputPath, JSON.stringify(result, null, 2), 'utf8'); await browser.close(); console.log('Done:', outputPath);

这段代码能跑通基础流程,但结果会比较冗长,因为几乎把 body 下的每个 div 都记录了下来。真实使用时,还需要在该脚本之后加入两个处理步骤:过滤无意义容器,合并大面积背景层。

4.2 过滤与合并规则

过滤规则解决的是“元素太多”的问题。可以按照下面几个方向做:

  • 删除没有内容、没有背景、没有尺寸的包裹层。
  • 删除文本内容少于 1 个字且没有背景图的 div。
  • 删除显示在视口外且没有固定定位的元素。
  • 判断一个子容器是否和父容器矩形几乎相同且无独立视觉属性,如果是则跳过该子容器,只递归它的孙节点。

合并规则解决的是“图层太碎”的问题。常见合并场景是:一整张图片作为卡片背景时,父 div 有背景图,子 div 有文字,二者应该合并成一个“图文卡片层”,而不是把背景图和文字拆成孤立的背景层与文本层。

因此脚本里可以增加一个 simplify 阶段:遍历树节点时,如果父节点具备背景、拥有至少一个文本子节点,而且子节点数量不多,则把子节点的文本和位置信息合并到父节点,再删除子节点。

简化逻辑可以参考:

function simplify(node) { if (!node.children || node.children.length === 0) return node; node.children = node.children.map(simplify); const hasVisual = node.style.backgroundImage !== 'none' || (decodeColor(node.style.background).a > 0) || node.src; const textChildren = node.children.filter((child) => child.text.trim().length > 0); if (hasVisual && textChildren.length === 1 && node.children.length < 3) { const textChild = textChildren[0]; node.text = textChild.text; node.children = node.children.filter((child) => child !== textChild); } return node; }

这种逻辑不能保证对所有页面都合适,但可以作为一个可调策略起点。真实页面更合理的方式是在分类规则里维护“容器类型”字段,由它决定是否允许合并。

4.3 导出资源目录

除了图层树 JSON,还需要把页面中的图片资源和背景图地址单独保存成资源清单。原因在于导出到设计工具时,图像素材最好单独保留原始文件,而不是让人从 JSON 里去抠 URL。

资源导出脚本可以读取layer-tree.json,遍历所有src字段,去重后输出一个assets.json

import { readFile, writeFile, mkdir } from 'node:fs/promises'; import path from 'node:path'; const outputDir = process.argv[2] ?? './layer-output'; const treePath = path.join(outputDir, 'layer-tree.json'); const raw = await readFile(treePath, 'utf8'); const data = JSON.parse(raw); const assets = []; function walk(node) { if (node.src) assets.push(node.src); (node.children || []).forEach(walk); } (data.tree || []).forEach(walk); const mixedAssets = await Promise.all(assets.map(async (src) => { try { const res = await fetch(src); const buffer = Buffer.from(await res.arrayBuffer()); const ext = path.extname(new URL(src).pathname) || '.png'; const name = `asset-${assets.indexOf(src)}${ext}`; const filePath = path.join(outputDir, 'assets', name); await mkdir(path.dirname(filePath), { recursive: true }); await writeFile(filePath, buffer); return { source: src, local: `assets/${name}` }; } catch (error) { return { source: src, local: '', error: error.message }; } })); await writeFile(path.join(outputDir, 'assets.json'), JSON.stringify(mixedAssets, null, 2)); console.log('Assets exported.');

这里使用 Node.js 18 以后的fetch下载资源,比较方便。但要注意,下载远程资源时需要确认页面来源是否允许使用这些图片,学习环境中更建议使用本地图片或已授权页面。

4.4 生成可视化标注图

只有 JSON 文件还不能称作“一键分层”。使用者希望快速看到页面被划分成了哪些区域。因此需要根据元素矩形位置生成一张标注图,用框和编号标出每个主要分层的边界。

生成标注图可以用两种方式:在浏览器里临时绘制 canvas,再保存为截图;或者用 Sharp 直接叠加矩形框。其中在浏览器里绘制再截图的方式依赖较少,实现也更直观。

思路是:加载原页面,在原页面上注入半透明红色矩形和白色编号文本,然后截取整页图片,得到一张带编号的全景图。

import { chromium } from 'playwright'; const url = process.argv[2]; const outputDir = process.argv[3] ?? './layer-output'; const treePath = `${outputDir}/layer-tree.json`; const browser = await chromium.launch({ headless: true }); const page = await browser.newPage({ viewport: { width: 375, height: 812 } }); await page.goto(url, { waitUntil: 'networkidle' }); await page.evaluate(async (treePath) => { const res = await fetch('file://' + treePath); const data = await res.json(); const overlay = document.createElement('div'); overlay.style.position = 'fixed'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100%'; overlay.style.zIndex = '99999999'; overlay.style.pointerEvents = 'none'; function addRect(node, index) { if (!node.rect) return index; const box = document.createElement('div'); box.style.position = 'absolute'; box.style.left = node.rect.x + 'px'; box.style.top = node.rect.y + 'px'; box.style.width = node.rect.width + 'px'; box.style.height = node.rect.height + 'px'; box.style.border = '2px solid #ff4d4f'; box.style.boxSizing = 'border-box'; box.style.zIndex = '99999999'; overlay.appendChild(box); const label = document.createElement('span'); label.textContent = index; label.style.position = 'absolute'; label.style.left = node.rect.x + 'px'; label.style.top = node.rect.y + 'px'; label.style.background = '#ff4d4f'; label.style.color = '#fff'; label.style.fontSize = '12px'; label.style.padding = '2px 5px'; label.style.zIndex = '99999999'; overlay.appendChild(label); return index + 1; } let count = 1; function walk(node) { count = addRect(node, count); (node.children || []).forEach(walk); } (data.tree || []).forEach(walk); document.body.appendChild(overlay); }, treePath); await page.screenshot({ path: `${outputDir}/overview.png`, fullPage: true }); await browser.close();

需要注意,直接使用file://读取 JSON 在某些浏览器策略下会被限制,更合适的方式是把 JSON 内容直接注入页面,或启动一个本地静态服务。这段代码的重点在于说明完整思路:把图层树映射到浏览器可视区域中绘制矩形,再截图得到标注底图。

4.5 图层树到 PSD 分层的扩展思路

如果想进一步把图层树导入 Photoshop,并不一定都需要生成 PSD 文件。更稳定的做法是先生成一层中间描述文件,再用 Photoshop 的 Automation 脚本(如 JSX 或 UXP)读取 JSON,在软件中生成图层组。

由于 Photoshop 对文本和动态图层的还原存在限制,直接生成原始视觉 PSD 的完成度取决于页面复杂度。在工程上,更可控的输出形式分为两类:

  • 每一层导出透明 PNG 和矩形的合成图,这类结果适合在图像编辑工具中做简单合成。
  • 输出一个“图层组结构”到 Photoshop,每个分组保留矩形占位和对应的图像素材地址,方便设计师在软件内重新调整。

从这个角度看,一键分层的产物不应该只押注在一种格式上,JSON 作为中间格式,后续无论转 PSD 结构、Figma 代码还是前端模板,都能继续复用。

5. 关键参数与分类策略会让你少走很多弯路

5.1 分层参数速查表

实际运行脚本时,下面这些参数直接影响输出质量。

参数作用推荐值设置过大设置过小
最小可见宽高过滤过小元素8-16px丢失小图标、小标签输出大量描点和分隔线
可见透明度阈值过滤透明隐藏层0.01过滤半透明遮罩层无法去除隐藏元素
视口宽度移动端 H5 显示宽度375拉伸布局影响坐标超宽页面显示不全
最大文本长度避免 JSON 被全文充满100 字符丢失长文案占用过多资源
Z 轴忽略阈值忽略低层级微小变动auto浮层归错类层级太乱

这些参数在脚本中不要写死,建议做成环境变量或命令行参数,方便不同页面调整。

5.2 图层类型与优先级

在设计工具中,图层顺序通常是从下往上排列。一个 H5 页面可以按下面的优先级输出:

优先级图层类型识别线索导出建议
1页面背景body 背景色/背景图、铺满容器的图保留为根背景层
2背景分区大面积 background 元素的容器合并进组合层
3图片媒体层img、video、iframe独立资源
4卡片容器有背景 + 圆角 + 子文本的元素作为组合层
5文本层h1/p/span 中可见文本文本字段
6图标资源小尺寸 img 或 background-image独立资源
7交互原件button/a/input 且有明确矩形标注为按钮层
8浮层fixed 或 z-index 高放到图层树最顶

脚本输出 JSON 时,如果按这个优先级排序,后续转可视化时更容易直接作为图层顺序使用。

5.3 用规则控制 Agent 而不是让 Agent 自由发挥

Agent 在执行 Skill 时容易出现的误区是:为了“完成理解用户意图”而提前猜测输出。比如用户只说“看看这页”,Agent 可能直接给一段自然语言总结,而没有真正产出可复用文件。

因此 SKILL.md 里的执行步骤必须约束“先看文件,再给结论”。建议在指令模板里固定检查顺序:

1. 确认 URL 可访问。 2. 执行 analyze-page.mjs 生成 layer-tree.json。 3. 查看 layer-tree.json 是否存在以及文件大小是否合理。 4. 执行 generate-overview.mjs 生成标注图。 5. 打开标注图,逐块检查区域是否和页面主要模块对应。 6. 最后输出简明摘要,说明每一层代表页面中的什么部分。

这样就把“如何做”从 Agent 的临场判断变成流程规范,减少结果不稳定。

6. 运行验证与常见问题排查

6.1 运行一次并看到完整的输入输出

完成目录准备后,测试指令可以这样构思:

node scripts/analyze-page.mjs http://localhost:8080/index.html ./layer-output

如果本地还没有静态服务器,可以用 Python 快速启动一个:

python3 -m http.server 8080

运行后预期输出如下:

Opening page: http://localhost:8080/index.html Done: ./layer-output/layer-tree.json

同时输出目录里会出现layer-tree.json。如果后续执行标注图脚本,还会出现overview.png

查看 JSON 时,可以通过命令行快速检查文件是否生成:

cat ./layer-output/layer-tree.json | head -n 50

如果 JSON 里能看到tree数组,并且元素中包含backgroundImagetextrect等字段,说明第一阶段跑通。

6.2 常见问题与处理方案

这里整理实际操作中发生频率较高的几个问题。

问题现象可能原因检查方式处理建议
脚本提示 Playwright 找不到浏览器未安装 chromium执行npx playwright install chromium安装后重试,构建环境可把浏览器路径写入环境变量
页面加载超时接口慢、图片多、有长轮询查看等待时间与networkidle状态改成waitUntil: 'load',或增加waitForSelector等待关键节点
图层树 JSON 过大没有过滤装饰层jq length查看数组长度调高最小尺寸,加入 simplify 合并阶段
标注图与视觉错位页面发生滚动或弹窗出现分析时锁定视口高度固定 viewport,并在截图前关闭弹窗或移除 fixed 元素
异步内容缺失数据由 Ajax 渲染检查最终 DOM 中是否有文本使用page.waitForLoadState('networkidle'),或等待特定元素出现
下载图片 403图片服务器限制来源查看 assets.json 中的 error 字段设置 Playwright 请求头中的 referer 或改用浏览器页面内下载方式

6.3 排查顺序建议

如果一个页面分层结果明显不符合预期,不要急着去改代码。先按下面的顺序排查:

  1. 确认页面在普通浏览器中打开状态正常,是否有登录墙或区域限制。
  2. 确认脚本等待的资源已经渲染完成,不是只抓到了骨架屏。
  3. 检查 layer-tree.json 中最顶层的节点是否覆盖了页面主体。
  4. 检查某个关键模块是否出现在 DOM 中,如果出现但被过滤,说明当前阈值或规则太严格。
  5. 用最小页面逐步增加复杂度,定位是哪条规则导致误判。
  6. 如果页面是整屏 canvas,跳过该页面或将其降级为单张画布层。

这条顺序能覆盖大多数“脚本能跑,但结果不对”的问题。

6.4 验收一个分层结果的检查表

建议每次交付分层结果时,按照下面的检查表确认质量:

  • 图层树中是否能看到页面根模块,没有被无限嵌套的 div 淹没。
  • 所有主要背景图是否被识别,并能对应到实际资源地址。
  • 页面主要文本是否存在,位置是否大致正确。
  • fixed 浮层是否被排到图层树末尾且标记为浮层。
  • 标注图上不同区块是否覆盖了页面主要视觉区域,没有把相邻卡片完全粘连。
  • 资源下载清单中是否出现错误请求。
  • 同一页面重复运行两次,输出结构是否基本稳定。

如果检查表上有任意一项不通过,应该先修正规则,而不是直接把结果交给下游。

7. 从“能跑脚本”到生产可用:边界与最佳实践

7.1 先给输出格式定规范,再谈精细化

一个分层 Skill 在团队内推行之前,最重要的不是追求百分之百自动识别准确率,而是先让输出格式稳定。建议团队内部先约定layer-tree.json的字段命名、图层类型枚举、资源目录结构,让所有接收到该结果的下游工具都能消费同一套中间格式。

这种规范化带来的收益会在跨人协作时体现得尤其明显:设计师拿到标注图后可以指出“这一块应该合并成一张完整背景图”,前端拿到 JSON 后可以调整生成策略,而整个过程不需要各自维护不同的解析脚本。

字段命名方面建议使用小驼峰或短横线风格,所有坐标统一基于视口宽度为 375 的设计尺寸,这样后续导入设计工具时不需要再做一次语义换算。

7.2 注意页面权限和资源边界

脚本自动访问线上页面、读取结构并下载资源,这在工程上是正常的浏览器行为,但实际运行时要尊重页面所属方的访问规则。

  • 不能把自动分层工具用于绕过登录权限抓取非公开页面。
  • 不能利用该工具下载无版权图片素材用于商业设计。
  • 如果页面明确设置了跨域限制或 CDN 反爬策略,脚本下载资源时可能失败,这是正常现象。
  • 团队内使用时,优先把 Skill 用于自己拥有源权限的活动页,或用于已经获得授权分析的项目。

从安全合规角度看,这类工具最安全的组织形态是“内部 H5 页面视觉还原工具”,而不是一个可以随意分析任何第三方站点的网络爬虫。因此默认建议把 URL 限定在公司域名或本地服务范围。

7.3 学习环境与生产环境的差异

在学习环境中,脚本只要能在本机跑出一个完整的 layer-tree.json 就算成功。生产环境还需要考虑更多方面:

  • 脚本执行账号是否具备访问页面的权限,登录态如何注入。
  • 浏览器运行在哪个环境中,是否需要单独维护浏览器二进制。
  • 输出结果保存位置是否需要统一归档,是否按日期和页面版本管理。
  • 长时间运行大量页面时,如何控制 Playwright 并发数量和内存占用。
  • 运行结束后是否清理临时截图和缓存目录。
  • 是否要给核心图层树结构增加版本号,以便后续修改解析规则时保持兼容。

在这些方面,建议先在小组规模和 20-50 个页面的样本上验证,确认错误率可接受后再扩大到更多页面。

7.4 下一阶段的演进方向

当前实现主要是基于 DOM 和 CSS 的规则化分层。对于结构清晰的 H5 页面,这种方法稳定、成本低、可解释性强。但如果遇到复杂视觉合成、大量使用 canvas 绘制、或者图标与背景交织严重的页面,规则化方法的瓶颈就会比较明显。

演进方向有三个:

  • 引入视觉大模型对整页截图做区域识别,把“截图区域”映射到“视觉语义”,让 Agent 理解哪些区块是 Banner、导航、商品卡、弹窗,而不只依赖 DOM 标签。
  • 基于多帧截图分析动效页面,识别轮播图、弹层、底部弹出的不同状态,输出动态图层状态序列。
  • 把图层树结果转成组件代码片段,让前端可以直接从活动 H5 页面还原成 Vue/React 模板,减少重复开发。

在落地过程中,比较推荐的路径是先用规则化脚本保证最基本的页面能跑通,再逐步把大模型识别作为规则方法的补充。两者结合之后,Skill 才能真正覆盖那些“一眼能看出结构,但 DOM 关系已经乱成一团”的页面。

本文的核心技术判断是:H5 页面分层的难点不是“截一张图”,而是把 DOM 树翻译成视觉分层树,同时筛选出有编辑价值的容器并保留可追溯的矩形坐标和样式信息。Skill 的价值则在于把这一套容易出错的人工判断过程固化为工作台命令,让页面分析和结果输出变得可重复、可传入团队协作流程。

如果要把这个能力落地到自己的项目中,第一步建议不要先写复杂规则,而是先做两个最小动作:把一个真实业务 H5 跑进 layer-tree.json,再人工标注出理想的分层结果,对比差异后把其中高频的差异点写成过滤或合并规则。这样迭代十来个页面之后,这套 Skill 会比一开始就去追逐“全自动完美分层”产生更大的实际价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询