一 前言
哈囉,大家好,我是 alien,今天我們來講一個能夠將精美網站一鍵複製的功能——ai-website-cloner-template 的使用和實作原理。
透過本篇文件你能收穫到哪些內容?
ai-website-cloner-template 的用法十分簡單:
第一步:把 GitHub 上的模板下載到本地:
js 代碼解讀複製代碼git clone https://github.com/JCodesMore/ai-website-cloner-template.git
第二步:執行 Claude Code,執行 /clone-website(這個本質上是模板工程維度的 skill)

廢話不說,用掘金的網站為例子(juejin.cn),我們先來看一下實現的效果:
pc 端效果對比(左:生成/右:原圖):

移動端效果對比(左:生成/右:原圖):

多端相容場景:
生成的程式碼整體效果評估下來:
接下來我們來看一下 ai-website-cloner-template 內部是如何處理的。

ai-website-cloner-template 原理可以分為四個部分:
Phase 1:摸底(Reconnaissance)
拿到目標 URL 後,第一件事是搞清楚「它到底長啥樣、怎麼動」。
我們來看一下:



Phase 2:搭骨架(Foundation)
摸完底,開始鋪地基。這一步必須先做完,後續所有元件才能開工。
Phase 3:元件生成(Spec & Build)
這是流水線最關鍵的階段,也是最能體現「規約驅動」思想的地方。
寫 spec 檔案:先不為每個 section 寫程式碼,而是先寫一份 markdown 格式的規約文件(*.spec.md)。這份文件是 builder agent 的施工合約,裡面寫清楚:

Phase 4:拼裝與 QA(Assembly & QA)
所有元件都寫完了,現在要拼成完整頁面,並驗證它跟目標站是否真的像。
視覺 QA:用腳本同時截三個視口下的「目標站」和「我們做的」,生成 target-.png 和 clone-.png,並排對比。優先級:
在實作過程中,核心的邏輯就是要真實地取得瀏覽器裡面的頁面狀態,Playwright 起到了核心作用。
先看一下 Playwright 是什麼?
Playwright 是一個由 Microsoft 開源的、用於端到端測試和瀏覽器自動化的函式庫。它讓你用程式碼控制真實的瀏覽器(Chrome、Firefox、Safari),自動完成點擊、填寫、截圖、讀取頁面內容等操作。
比如我們想在 Node 環境喚起瀏覽器,那麼可以直接呼叫對應的 API:
js 代碼解讀複製代碼// 1. 啟動瀏覽器
const { chromium } = require('playwright');
const browser = await chromium.launch({ headless: true });
還可以建立一個獨立的隱身視窗:
js 代碼解讀複製代碼// 2. 新建一個 context(獨立隱身視窗)
const context = await browser.newContext();
操作瀏覽器,本質上我們可以借助這個能力去完成 e2e 等自動化測試驗證:
js 代碼解讀複製代碼// 點擊
await page.click('button#submit');
// 導航跳轉
await page.goto('https://example.com');
還可以截圖用來取得效果:
js 代碼解讀複製代碼await page.screenshot({ path: 'screenshot.png' });
感興趣的同學可以研究一下,這裡是整理的一些 API 能力:

介紹完核心原理,我們挑一些重點場景,來看一下核心實作:
多視口截圖:
typescript 代碼解讀複製代碼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 秒 —— juejin 用了大量懶載入圖片和 IntersectionObserver,networkidle 不等於「完全穩定」。
樣式還原:提取全域樣式 tokens:
page.evaluate() 跑一段 JS,遍歷所有 DOM 節點呼叫 getComputedStyle():
typescript 代碼解讀複製代碼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] };
});
juejin 的真實 token 抓出來後是這個樣子:
text 代碼解讀複製代碼主色藍: 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 會讓卡片換底色。
以 juejin 為例做一遍完整 sweep:
typescript 代碼解讀複製代碼// 捲動 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 張卡
});
實測發現 juejin 的「最新」 tab 切換後文章數從 16 降到 1,說明這不是純 UI 切換,是真實重新拉資料。克隆時用 useState 切兩份靜態 mock,但生產部署需要後端 API 配合。
頁面拓撲對映:
把所有 section 列出來,標註互動模型:
text 代碼解讀複製代碼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:
typescript 代碼解讀複製代碼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 });
}
對比找差異的優先級:
juejin 實戰中 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 並行」的真實體感。先動手的人,先看到未來。感謝閱讀,我們下篇見。希望的朋友可以按讚 + 收藏,支持更新 AI 場景的「奇思妙想」。