网站逆向重建:从手动扒站到AI流水线的完整实践 说实话第一次接到“把一个线上网站反过来重建一遍”的需求时我的第一反应是打开 DevTools 选中所有节点右键复制外层 HTML然后新建一个 index.html 粘贴进去。结果十分钟之内就翻车字体不见了、布局挤成一团、轮播图只剩第一张原站的动态数据全变成空壳。后来我意识到真正的问题不是“我没复制全”而是“我把网页当成文件复制却忘了它本来是个工程”。这也是我后来整理出 ai-website-cloner-template 的起点把“网站逆向重建”这件事从一次性手工活变成一条可以反复执行、可以套用到下一个项目的可复制流水线。这篇文章要讲的就是我在这个模板上的完整设计思路和落地过程包括每一步为什么这么选型、踩了哪些坑、踩完之后怎么修的、以及它怎么用在真正的项目里。适合正在做网站改版、外包开发或者想用 AI 批量生成同类型站点的朋友参考。我尽量把能抄作业的部分都写出来不能抄的部分也会把判断逻辑讲清楚。1. 从手动扒站到流水线传统网站重建的三个死穴1.1 手动复制为什么总在交付前翻车在我动手做这个模板之前先复盘了过往几次暴力扒站失败的案例。手动复制的坑集中在三点一是局部截取 HTML 片段很容易丢掉外部依赖CSS 变量、字体文件、图标字体这些资源一旦没跟过来页面打开时看着“差不多”鼠标一动、菜单一展开就碎成一片二是很多现代站点已经用 JavaScript 在客户端动态渲染浏览器源码里根本不是最终内容直接复制得到的是一大堆立即执行函数和空容器三是复制下来的文件完全没有工程化结构样式、脚本、图片全部平铺在一个目录里别说二次开发连找一个按钮的样式都要靠全文搜索。这三个问题都不是技术门槛高而是缺少一条标准处理链路。人脑在手工扒站时会下意识偷懒看到某个模块长得复杂就只复制视觉上“够用”的部分看到某个样式有冲突就顺手删掉。这种不确定的过程不可能沉淀成方法论每次交付质量都取决于当天状态而“能不能复现”恰恰是项目交付的底线。1.2 为什么 AI 参与能把这件事变成流水线传统方案里也有自动化工具比如离线浏览器、整站下载器能把整个站点拉下来。但拉下来的充其量是原始文件代码质量永远停留在“能打开”而不是“可维护”的级别。AI 模型的参与改变了这一点它可以理解 HTML 结构、CSS 样式和 JS 行为然后重新生成一套分层清晰、命名规范、组件化的代码。换句话说ai-website-cloner-template 做的事情不是“存档”而是“翻译”——把浏览器看到的渲染结果翻译成一份新的、可维护的工程源码。所以真正需要沉淀的从来不是抓取脚本本身。抓取工具到处都是难的是“怎么让 AI 稳定复现原站效果”的那套引导流程给模型看什么、按什么顺序喂信息、怎么校验输出、怎么把站点差异隔离出去。这些才是一个模板的真正价值。把流程固定下来之后新项目只是换了一组输入参数流水线本身不需要重写。1.3 技术栈选型Playwright cheerio 多模态大模型的组合逻辑工具上我做过几轮对比最终定下来的是 Playwright、cheerio 和多模态大模型这套组合。抓取层用 Playwright 而不是简单的 HTTP 请求是因为现代网站大量依赖 JS 渲染必须用无头浏览器等页面真正绘制完成后再拿 DOM。直接请求拿到的 HTML 常常只是空壳很多内容根本不在里面。解析层用 cheerio它跑在 Node 生态里对嵌套标签和动态 class 的处理比手写正则可靠得多而且方便和后续工具串联。生成层是核心也是我调整最多的地方。单纯把一份 HTML 文本丢给模型CSS 细节大概率会丢颜色、间距、字体永远对不上。我的做法是把“页面截图 结构化 DOM 描述 交互说明”一起交给多模态大模型让它同时看到视觉结果和结构信息生成的还原度才过得去。单模态方案我也试过结论是不值得省这一步。1.4 这个模板到底适合谁用如果你只是偶尔需要保存一个页面离线阅读完全用不着这套流水线浏览器自带的“另存为”就够了。这套东西更适合几类人给客户做官网重建的独立开发者、需要把线上站点作为参考快速出首版的原型团队、以及要把一批展示型站点改造成统一技术栈的项目组。前提是这些站点都在授权范围内这一点我放到最后专门讲。核心收益很直白第一次跑通可能需要一整天之后每接一个新项目只要改配置、重跑流水线半天内就能拿到一个工程化基础版。省下的时间远远超过搭模板本身花的成本。2. 全链路拆解采集、语义拆分、AI 生成、模板固化2.1 采集环节资源和渲染快照都要抓全流水线的第一步是采集用 Playwright 打开目标页面。这里有一个很容易被忽略的细节等待页面加载完成的时机。我的脚本设置的是 networkidle也就是等到网络空闲之后再取 DOM。如果只等 load 事件动态内容经常还没渲染出来拿到的 DOM 会缺一半。const { chromium } require(playwright); async function capture(url) { const browser await chromium.launch(); const page await browser.newPage({ viewport: { width: 1440, height: 900 } }); await page.goto(url, { waitUntil: networkidle, timeout: 30000 }); await page.screenshot({ path: shot-home.png, fullPage: true }); const dom await page.content(); const assets await page.evaluate(() { const urls []; document.querySelectorAll(img, script, link).forEach(el { if (el.src) urls.push(el.src); if (el.href el.rel stylesheet) urls.push(el.href); }); return urls; }); return { dom, assets }; }采集阶段还需要注意多尺寸截图。我通常至少截三个宽度移动端、平板、桌面端。原因后面会讲AI 生成时如果只有桌面端视觉参考响应式部分就全靠猜。资源下载要做去重和本地化图片、字体、CSS 里的相对路径都要改成新的目录结构。我第一次做的时候漏掉了 CSS 内嵌的 url() 资源导致字体大面积 404这个坑值得单独拿出来讲。2.2 语义拆分把 5000 行 HTML 压成 AI 能懂的中间表示直接把上万行 HTML 塞给模型它会丢失重点还会被广告脚本、统计代码干扰。我的做法是把原始 DOM 转成一份中间表示 JSON里面只保留三部分内容骨架树、样式摘要、交互描述。骨架树不是完整 DOM而是把 div 嵌套压缩成区块列表比如“header 包含 logo、nav、cta”“main 包含三个卡片区块”。这样做的直接好处是大幅降低 token 开销同时引导模型按区块生成组件化代码。样式摘要里记录 CSS 变量、字体声明、媒体查询断点。交互描述则记录页面上的点击行为、滚动效果、弹窗逻辑。这一步用 cheerio 遍历 DOM屏蔽掉广告位和统计脚本之后输出 JSON。过程中我发现一个比较坑的点很多站点的标签语义很弱div 套 divclass 命名也是乱的。有些元素明明视觉上是按钮标签却是 div。所以中间表示里还要做一层“视觉角色映射”把 class 名称和它在页面上的实际作用对应起来否则模型很容易迷失。2.3 AI 生成环节Prompt 要同时给三样东西生成阶段是还原度的分水岭。我的经验是不要只给一句话加一段 HTML要构造结构化 Prompt。第一段写项目背景和技术栈约束比如“基于 Vue 3 Tailwind只生成静态页面”第二段放中间表示 JSON第三段放视觉规格包括截图、颜色清单、字体清单、断点顺序。Prompt 里还要要求模型先输出组件清单再输出代码。这一步很重要组件清单确定了后续维护时组件边界才清晰。生成时温度参数必须调低我一般固定在 0.1 左右。因为这里是逆向重建不是创意写作稳定性优先。模型自由发挥的后果是文案被改、结构被加戏还原度直线下降。为了减少 token 浪费大页面我建议拆成区块分别生成最后再合并。合并时要注意样式变量冲突两个区块如果各自声明了 --color-primary后者会覆盖前者。这种 bug 截图很难看出来但整套设计系统的色板一下就崩了。2.4 模板固化把站点差异隔离到 config从中间表示和生成结果里可以挑出每个站点不同的部分站点名称、主色、Logo 文案、导航链接、联系方式、备案号等等。这些值绝不能写死在代码里而是抽到 config 目录下的一个 YAML 文件里。ai-website-cloner-template 的 template 就在这里体现——抓下来的站点是 A但生成出来的代码是一个占位符版本下次遇到同类型的站点 B改掉 config 再跑一次生成就得到 B 的基础版。site: name: Example Corp primaryColor: #0F4C81 logo: /assets/logo.svg nav: - label: 首页 href: / - label: 产品 href: /products pages: home: sections/home.yaml products: sections/products.yaml变量抽取后还要处理动态内容静态化的问题。原站可能是 CMS 渲染的我们重建时不一定做后台只保留静态数据数据源来自 config 或 mock JSON。这一步决定了流水线能否在纯静态环境下跑起来也为后续接真实接口留好了口子。3. 克隆结果对不上原站三个真实根因与完整排查链路3.1 字体和图标整片缺失采集层漏了样式表隐藏资源第一个常见失败是生成页面里字体和图标全部塌掉。排查时我习惯先看浏览器控制台结果发现一大堆 404。定位到根因是原站的字体通过 CSS font-face 加载而采集脚本只下载了 link 和 img 的资源完全没有解析 CSS 里的 url() 引用。修复手段是在采集环节加一步把 CSS 文本拉下来用正则提取 url(...) 目标并下载到 fonts 目录同时把 CSS 内容里的路径重写为相对路径。图标同理如果原站是 iconfont直接下载字体文件如果是 SVG symbol则是把 sprite 文件整个抓下来。这个问题的根源可以总结为采集只覆盖了 DOM 可见资源忽略了样式表引用的隐藏资源。只要是想做完整克隆这一步绕不过去。3.2 首屏正常、向下滚动后布局乱套响应式断点信息缺失第二个根因更隐蔽。桌面端首屏截图看起来没毛病但滚动到下面很多区块完全错位。我一开始怀疑是生成代码段落丢失后来发现是响应式断点的问题。原站用了媒体查询桌面端和移动端是不同的栅格布局而 AI 生成时只参考了桌面端截图把两列布局硬编码成了永远两列。修复方式分两层。采集层在截图的时候就要同时记录 document.styleSheets 里的 media 规则摘要把这些断点信息写进中间表示。生成层则在 Prompt 里显式列出断点值并附上移动端截图。我加了这个逻辑之后折叠后布局错乱的问题基本绝迹。这里想提醒一句截图对比只能发现“看起来不一样”如果源站本身是响应式的单图对比完全不够断点信息和多尺寸截图必须同步给模型。3.3 列表与详情页全是骨架动态数据没有静态化第三个根因针对动态页面。很多站点的列表是客户端发请求然后渲染出来的Playwright 抓到的是渲染后的 DOM这没问题。但重新生成后的静态页面不会再有任何接口需要预留 mock 数据层。如果原站列表是分页渲染的重建版本没处理分页看起来就是“只有首屏数据点下一页没反应”。解决方式是采集时把列表接口返回的 JSON 保存下来再生成一个 mock server或者直接内联到静态数据文件里。模板里可以加一个“数据源开关”将来接真实接口时不用改业务组件。这个问题的难度不在技术实现而在流程意识很多人不做数据层以为页面长一样就够了结果交付后一交互就露馅。3.4 完整排查链路复盘先分层再动手复盘下来这三个问题分别来自采集层、理解层和生成层。我现在的排查方法是先分类再动手前端表现异常优先查资源和断点功能行为异常优先查事件和路由数据差异异常优先查 XHR 与 CMS。每跑完一次克隆先产出一份差异清单去做分层归属。现象怀疑层根因修复手段字体图标 404采集层CSS 内资源未下载解析样式表 url() 并本地化折叠后布局错乱理解层/生成层只给了桌面端截图缺少断点信息记录 media 摘要Prompt 补充响应式截图分页/列表无数据生成层没有静态化 XHR 数据保存接口 JSON生成 mock 数据层这套排查链路现在写进了模板的脚本里每次生成完自动跑一遍差异检测把“凭感觉看像不像”变成“对着数据看哪里不像”。4. 模板如何在不同项目间复制目录约定与一键配置4.1 目录约定模板、配置、输出三层分离我把模板项目设计成三层templates 放通用组件与页面骨架config 放每个具体站点的变量output 放最终生成的工程。为什么这么分因为如果在模板层混入站点特有内容复用时就得到处改代码如果 config 管不住所有差异项目就会变成一次性代码。一个新站点复用时只有 config 需要认真改代码层尽量不动。这也是为什么前面花大力气做变量抽取——如果变量散落在组件里模板复用就是一句空话。目录约定一旦确定整个团队的工作方式就稳了大家知道该去哪里改什么不会出现同一个人在同一份代码里找半天入口的情况。4.2 新站点怎么“一键开跑”四步标准操作新项目上手的流程我固定为四步复制模板作为新工程目录编辑 config 里的品牌、导航、颜色执行采集脚本更新参考截图与 DOM 快照调用 AI 生成脚本重新产出页面代码最后跑一遍校验脚本对比生成页截图和参考截图的像素差异。这四步里最容易偷懒的是校验但最不能省的也是校验。像素对比我用本地脚本截两张图计算差异率超过阈值就自动提示检查对应区块。机器只能发现不一样人工再判断哪些不一样可以接受。比如有的客户 Logo 是动态生成的每次访问都略有不同这种差异就要在模板里配置白名单不然后面每次跑都会被标记成失败。4.3 源站改版后如何同步局部更新而不是全量重跑网站重建完成不代表一劳永逸。客户源站改了 banner、换了文案手里的重建版本也要跟着改。这时候千万不要重新跑全流程全量重跑会引入大量不必要的变更改一个按钮把整个首页代码都换一遍回归测试成本完全失控。我的做法是只抓改动的页面用网页快照对比找出变化区块然后只把变化区段的路径和 HTML 发给模型让它替换对应组件的局部内容。同步完还要回归测试关键路径导航点击、锚点滚动、表单校验。这套局部更新逻辑做进流水线后维护成本低了很多。4.4 给团队用的验收清单模板落地后我要求团队提交前过一遍验收清单所有静态资源是否来自本地 assets关键文案是否读自 config桌面、移动、平板三种宽度的截图是否通过没有授权说明文件的站点不允许入库动态模块是否有 mock 数据入口。这五条每一条背后都对应一次真实返工。模板的 README 开头我写了一句话先改配置再动代码。这句话帮我挡住了很多无效的个性化需求。团队协作时最大的风险不是技术问题而是有人绕过流水线手工改输出改完之后模板的复用能力就断了。5. 最小可用版本关键脚本与跑通 Demo 时的依赖问题5.1 采集脚本的资源下载实现完整采集脚本的核心除了 Playwright 抓 DOM还要补一个资源下载函数。我用 Node 的 fs 和 https 模块把 assets 转为本地文件并把原 URL 路径映射到本地相对路径。const fs require(fs); const https require(https); function download(url, output) { return new Promise((resolve, reject) { https.get(url, res { if (res.statusCode ! 200) return reject(new Error(url - res.statusCode)); const ws fs.createWriteStream(output); res.pipe(ws); ws.on(finish, () { ws.close(resolve); }); }).on(error, reject); }); }实际脚本里还必须处理重定向、mime 类型、路径防穿越。偷懒的直接后果是字体文件下载成了 HTML 错误页或者下载路径里出现上级目录跳转把项目结构写乱。这些边界情况不写进去流水线在真实站点上跑不了几个就会挂掉。5.2 AI 生成脚本的 CLI 封装生成层我封装成一个 CLI参数包括输入目录、中间表示 JSON、输出目录node gen.js --ref ./.cache/home.json --shot ./.cache/home.png \ --out ./output --config ./config/site.yaml --temp 0.1temperature 0.1 是刻意设置的避免模型自由发挥。Prompt 模板单独放在 generators 目录这个文件本身也是模板的一部分换项目时通常不用改。调用云端模型时要在代码里限制最大 token 和超时时间首页这种大页面一次生成不完就拆成区块。5.3 输出校验截图对比加离线加载测试校验脚本我会做两层。第一层是截图对比用 Playwright 打开 output 目录的页面截图再和缓存里的参考截图做差异率计算阈值通常设 5%。第二层是真实离线加载测试关闭全部外部请求之后再刷新页面看有没有报错。为什么要第二层因为 diff 率低不代表没问题。文字内容一模一样但字体文件没本地化截图根本看不出来只有断网刷新才会暴露。这个经验是我从一次“看起来完美”的交付里学来的客户那边内网环境无法访问外网资源整站直接白屏。5.4 跑通 Demo 最容易卡住的三个依赖问题新手跑这个模板最容易卡住的地方就三个Playwright 第一次启动要下载浏览器内核网络不好会让人误以为脚本卡死cheerio 处理畸形 HTML 时某些标签会自动补全导致骨架树多出空节点项目根目录的路径如果写死成绝对路径换机器后所有相对路径全断。这三个问题我都写进了 README但看过不代表踩过建议第一次跑的时候找有经验的人远程陪跑一遍比自己对着报错折腾半小时快得多。6. 逆向重建的合规边界哪些网站能碰哪些不能碰6.1 授权来源比技术能力更优先在把技术流程落地之前先问自己这个网站到底有没有权利去克隆我给自己定的规矩是只有三类站点可以碰自己拥有的站点、团队内部需要迁移改版的存量站点、客户明确在合同里授权可以重建的站点。其他情况下技术完全可行也不要去碰。逆向重建出来的代码如果脱离授权范围很容易构成对原站著作权和商业外观的侵害。项目里的 README 我特意放了一段授权声明模板要求每个新站点入库前必须附上授权证明文件路径。这不是走形式而是为了避免将来说不清。很多纠纷不是发生在技术环节而是发生在“当初好像默认可以”的模糊地带。6.2 采集频率与 robots.txt脚本也要讲分寸流水线的采集脚本虽然只抓静态资源但本质仍是程序化访问。因此要控制并发、限制频率避免对源站服务器造成压力。采集前检查目标域名的 robots.txt明确禁止爬取的相关路径直接跳过。这不是装样子而是为了不给后续业务埋雷源站日志一旦被审计高频抓取可能成为授权链条上最说不清的一环。有时候客户会说“那站是友商的抄一下没事”。这种需求我通常不接不是技术做不到而是交付物一旦和源站高度相似对方反手一个投诉整个项目都会受牵连。流水线里加一个 robots 检测脚本只需要二十分钟但省掉的麻烦可能是几个月的纠纷。6.3 字体、图片和第三方素材的版权陷阱这个坑比较隐蔽。网页代码本身可能没问题但字体文件、付费图片、icon 图标往往有独立版权。把原站的字体文件下载到本地不等同于有权在新交付的站点里继续使用。所以模板里对资源做了标记在 config 中给每个 asset 增加 license 字段记录来源和授权信息。拿不准的素材一律替换成开源替代品。比如用思源黑体替换商业字体用可商用图库图片替换付费图库。这不是保守而是做过外包的人都懂素材版权是甲方最容易忽视的雷。很多项目代码没问题最后栽在了一张小图上。6.4 交付前的合规自检清单最后分享一份我每次交付前都会过的自检清单确认源站归属于授权主体检查 robots.txt 与采集频率是否合规核对字体与图片 license 字段禁止克隆包含用户隐私数据的页面输出代码里不得保留原站后端接口密钥或敏感配置。这些都通过后才会把 output 目录交付出去。流水线可以追求还原度但还原度不能凌驾于合规之上。一个可复制的模板如果复制的是风险那还不如每次手动老老实实地从空项目开始搭。工具是中性的用途取决于使用者手里的授权和边界。这个项目做到现在我最深的体会是把网站逆向重建做成可复制流水线真正的难点一直都不在“逆向”的技术本身而在于能不能把流程拆到足够细、把变量抽得足够干净、把合规边界守得足够稳。如果你也想在自己的项目里试建议别一上来就追求高还原度先用一个简单落地页跑通整条链路再把采集、生成、校验三个环节逐个加固。跑通一次之后你会发现它慢慢就从一次性脚本长成了一个真正可复制的模板工程。