综合 / 开发工具 · 2026-10-24 10:45 · 4 阅读 · 0 赞
用 Playwright + 多 Agent 流水线一键还原任意网站
介绍 ai-website-cloner-template 的工程化思路:用 Playwright 抓取多视口截图、计算样式与交互状态,再通过规约驱动多个 Agent 并行生成组件,最后做视觉回归 QA,实测掘金首页还原度约 70% 以上。
一、前言
本文介绍一个能把精美网站一键复制的工程模板——ai-website-cloner-template 的用法与实现原理。
读完你能收获:
- AI 自动逆向解析网页的新思路;
- Playwright 的基本用法;
- ai-website-cloner-template 的整体设计。
用法很简单。第一步,把 GitHub 上的模板拉到本地:
git clone https://github.com/JCodesMore/ai-website-cloner-template.git
第二步,运行 Claude Code,执行 /clone-website(本质上是模板工程里的一个 skill):

以掘金(https://juejin.cn)为例,先看效果。
PC 端对比(左:生成 / 右:原图):

移动端对比(左:生成 / 右:原图):

多端兼容场景:

整体评估:
- UI 还原度实测在 70% 以上,尚未达到完美;
- 移动端适配等细节处理得比较好;
- 生成代码的可维护性尚可,但质量很大程度上取决于所用模型。
下面看内部是怎么处理的。
二、核心实现
2.1 核心原理

整个流程分为四个阶段。
Phase 1:摸底(Reconnaissance)
拿到目标 URL 后,先搞清楚它长什么样、怎么动。
- 截图:用 Playwright 启动真实浏览器,在三个标准视口(1440×900、768×1024、390×844)下分别截全页图和首屏图。用三个视口是为了验证响应式断点,而不是靠猜。
- 抓 design tokens:在浏览器里执行 JS,遍历所有 DOM 节点,用
getComputedStyle()聚合去重字体、字号、颜色、背景、圆角、间距。读的是最终生效的计算值,因此能穿透@apply、CSS 变量和媒体查询。 - 交互扫描:静态截图只能看到当前状态,但 tab 切换会换内容、滚动可能改变 header 样式、hover 可能让卡片变色。所以要模拟滚动、点击 tab、悬停,把每个交互态都抓一份。
- 产出:一批截图和一批 JSON(tokens.json、assets.json、topology.json 等),放在
docs/research/和docs/design-references/下。



Phase 2:搭骨架(Foundation)
摸底完成后先铺地基,后续组件才能开工。
- design tokens 落地:把抓到的颜色、字号映射到 Tailwind v4 的
@theme块和:rootCSS 变量,之后写组件时bg-primary、text-muted-foreground等 class 可直接使用。 - 字体策略:目标站用 Google Fonts 就走
next/font/google;像掘金这种纯系统字体(PingFang SC、Microsoft YaHei)则退回 system font stack,省掉外网请求。 - 图标去重:页面里可能有三四十个 SVG 节点,很多是重复的。脚本去重后通常只剩十几个,整理成 React 组件放进
icons.tsx。 - 资源下载:把图片、logo、favicon 批量下载到
public/images/和public/seo/,按用途分目录。下载不了的占位图用基于 id 的 hash 渐变色块顶替,保证每个 id 颜色唯一、视觉上合理。 - 产出:
globals.css、icons.tsx、public/images/全部就绪。
Phase 3:组件生成(Spec & Build)
这是流水线最关键的阶段,也最能体现“规约驱动”的思想。
- 写 spec 文件:先不写代码,而是为每个 section 写一份 markdown 规约(
*.spec.md),作为 builder agent 的施工合同,内容包括:- 目标组件长什么样(截图路径);
- 像素级样式(字号 17px、padding 12px 20px 0px、颜色 #252933,全部来自
getComputedStyle的真实值); - 真实内容数据(标题、作者、缩略图均从目标 DOM 抓取,不是 Lorem Ipsum);
- 交互模型(hover、点击、是否有滚动驱动)。
- 复杂度预算:单份 spec 不超过 150 行。掘金首页 12 个 section 就拆成 12 份 spec,分派给 12 个 builder agent。拆得越细,单个 agent 任务越聚焦,质量越高。
- worktree 并发:每个 builder agent 在独立的 git worktree 分支上工作,互不冲突。完成后由 orchestrator 逐个 merge 进 main,每次 merge 立刻跑一次
npm run check(lint + typecheck + build),保证 main 分支始终可构建。 - 产出:每个 section 一个 React 组件文件及其对应的 spec 文件。

Phase 4:拼装与 QA(Assembly & QA)
组件写完后拼成完整页面,并验证与目标站的相似度。
- 拼装:
page.tsx按 z-index 和断点组合所有组件。例如 header 是 sticky top(z-index: 250),移动端浮动下载按钮用position: fixed(z-index: 100),不能挡住 header 但要能盖住 footer。 - 视觉 QA:脚本同时截取三个视口下的“目标站”和“克隆站”,生成
target-*.png与clone-*.png并排对比。优先级依次为:- 尺寸差异(宽高、padding、gap,最容易暴露);
- 颜色差异(背景、文字、边框,肉眼最敏感);
- 布局差异(flex 方向、grid 列数、sticky 是否失效)。
整个过程中,核心是真实获取浏览器里的页面状态,Playwright 在其中起关键作用。
2.2 Playwright 简介
Playwright 是 Microsoft 开源的端到端测试与浏览器自动化库,可以用代码控制真实浏览器(Chrome、Firefox、Safari),自动完成点击、填写、截图、读取页面内容等操作。
在 Node 环境唤起浏览器:
// 1. 启动浏览器
const { chromium } = require('playwright');
const browser = await chromium.launch({ headless: true });
创建独立的隐身窗口:
// 2. 新建一个 context(独立隐身窗口)
const context = await browser.newContext();
借助这些能力可以完成 e2e 等自动化验证:
// 点击
await page.click('button#submit');
// 导航跳转
await page.goto('https://example.com');
也可以截图留存效果:
await page.screenshot({ path: 'screenshot.png' });
下面是常用 API 能力的总结:

三、具体实现思路
多视口截图
const viewports = [
{ name: "desktop-1440", width: 1440, height: 900 },
{ name: "tablet-768", width: 768, height: 1024 },
{ name: "mobile-390", width: 390, height: 844 },
];
for (const vp of viewports) {
const ctx = await browser.newContext({
viewport: { width: vp.width, height: vp.height },
locale: "zh-CN",
userAgent: "Mozilla/5.0 ... Chrome/131.0.0.0 ...",
});
const page = await ctx.newPage();
await page.goto(url, { waitUntil: "networkidle", timeout: 60000 });
await page.waitForTimeout(2500); // 等 hydration
await page.screenshot({ path: `${outDir}/${vp.name}-full.png`, fullPage: true });
await ctx.close();
}
三个标准视口各截一张 fullPage 图。注意 waitUntil: "networkidle" 之后还要再等 2.5 秒——掘金用了大量懒加载图片和 IntersectionObserver,networkidle 不等于“完全稳定”。
提取全局样式 tokens
用 page.evaluate() 执行 JS,遍历所有 DOM 节点调用 getComputedStyle():
const tokens = await page.evaluate(() => {
const all = document.querySelectorAll("*");
const fonts = new Set(), sizes = new Set(), colors = new Set();
for (const el of all) {
const s = getComputedStyle(el);
fonts.add(s.fontFamily);
sizes.add(s.fontSize);
if (s.color !== "rgba(0, 0, 0, 0)") colors.add(s.color);
}
return { fonts: [...fonts], sizes: [...sizes].sort(), colors: [...colors] };
});
掘金真实抓出来的 token:
主色蓝: rgb(30, 128, 255) → #1e80ff
页面底: rgb(242, 243, 245) → #f2f3f5
卡片底: rgb(255, 255, 255) → #ffffff
正文: rgb(37, 41, 51) → #252933
次要文字: rgb(81, 87, 103) → #515767
三级文字: rgb(134, 144, 156) → #86909c
边框: rgb(228, 230, 235) → #e4e6eb
危险红: rgb(246, 66, 66) → #f64242
字体族: PingFang SC, Microsoft YaHei, Helvetica Neue
强制交互扫描
这一步是整个 skill 的精华。静态截图只能看到“现在长什么样”,但站点是“活”的:tab 切换会换内容、滚动会让 header 变样式、hover 会让卡片换底色。
以掘金为例做一次完整 sweep:
// 滚动 sweep:截不同 scrollY 下的 header 样式
for (const y of [0, 300, 600, 900, 1200, 1500, 1800, 2094]) {
await page.evaluate((sy) => window.scrollTo({ top: sy, behavior: "instant" }), y);
await page.waitForTimeout(400);
const data = await page.evaluate(() => {
const header = document.querySelector("header");
return {
scrollY: window.scrollY,
headerBg: header ? getComputedStyle(header).backgroundColor : null,
headerShadow: header ? getComputedStyle(header).boxShadow : null,
};
});
scrollSnapshots.push(data);
}
// 点击 sweep:点“最新” tab 看内容是否变化
const tabClick = await page.evaluate(() => {
const target = [...document.querySelectorAll("a, button, div, span")].find(
(el) => el.textContent?.trim() === "最新" && el.children.length === 0
);
if (!target) return { ok: false };
target.click();
return { ok: true };
});
await page.waitForTimeout(1500);
const latestContent = await page.evaluate(() => {
return document.querySelectorAll(".entry").length; // 真实数据:1 张卡
});
实测发现掘金“最新” tab 切换后文章数从 16 降到 1,说明这不是纯 UI 切换,而是真实重新拉数据。克隆时用 useState 切两份静态 mock,生产部署则需要后端 API 配合。
页面拓扑映射
把所有 section 列出来,标注交互模型:
1. Header — 静态 (无 scroll 变化)
2. LeftSidebar — 点击 (导航)
3. ArticleList — 点击 (tab 切换)
4. ArticleCard — hover (subtle bg)
5. CtaBanner — 静态
6. RightRail — 静态 (刷新按钮 mock)
7. Footer — 静态
8. MobileAppPill — 静态 (position: fixed)
这一步的产出是 PAGE_TOPOLOGY.md,作为后续 spec 文件的索引。

以上就是 Phase 1。后续阶段会对样式、资源以及组件生成规范 spec 做系统化处理,这里不再展开。最后看 Phase 4 的视觉 Diff。
QA 脚本遍历三个视口,分别截取 target 和 clone:
for (const vp of viewports) {
const ourCtx = await browser.newContext({ viewport: vp });
const ourPage = await ourCtx.newPage();
await ourPage.goto("http://localhost:3010/", { waitUntil: "networkidle" });
await ourPage.screenshot({ path: `clone-${vp.name}-full.png`, fullPage: true });
const targetCtx = await browser.newContext({ viewport: vp });
const targetPage = await targetCtx.newPage();
await targetPage.goto("https://juejin.cn/", { waitUntil: "networkidle" });
await targetPage.screenshot({ path: `target-${vp.name}-full.png`, fullPage: true });
}
对比找差异的优先级:
- 尺寸差异:宽高、padding、gap(最容易暴露);
- 颜色差异:背景、文字、边框(肉眼最敏感);
- 布局差异:flex 方向、grid 列数、sticky 失效。
掘金实战中,mobile 一次性暴露了 3 个差异(tag wrap、author truncate、button-in-anchor),desktop 和 tablet 一次通过。
四、总结
ai-website-cloner-template 的价值不在于“能复制网站”——这本身就是 DevTools 的本职工作。它把“截图 + 抄代码”重构为一条可复用、可验证、可并发的工程流水线:先 Reconnaissance 抓计算值,再 Foundation 搭骨架,然后用 Spec & Build 的规约驱动多 Agent 并行生成,最后 Assembly & QA 做多视口视觉回归。先写 spec、再写代码这个顺序的颠倒,是从“AI 玩具”迈向“AI 工程”的分水岭。
建议 clone 一份到本地,挑一个欣赏的站点跑一遍,亲身感受“规约驱动 + 多 Agent 并行”的真实体感。