接了个活儿,要把一个跑在 Webflow 上的品牌官网完整搬回自己的静态服务器。一开始我以为这事挺简单:浏览器打开源码,另存为 HTML 文件,再把图片 CSS 链接理顺就行。真正动手才发现,Webflow/Framer 网站一键导出为干净的 HTML 文件这个需求,看着容易,实操里全是细节——页面源码里混着大量框架运行时脚本、按节点自动生成的 class 类名、一堆跨域资源引用,head 区块恨不得占半屏,但页面视觉上其实就五六个区块。
这篇文章把我自己做的“一键导出”工具和踩过的坑整理出来。它解决什么问题呢?就是把你用 Webflow、Framer 这类视觉建站工具做出的页面,从“只能在平台上跑”变成“一份你能读懂、能二次开发、能丢到任何静态服务器里的干净的 HTML 文件”。适合谁看?想把营销页从 Webflow/Framer 迁出来、想拿这些页面当邮件模板或单页落地页、或者单纯不想被平台绑定的人。
1. 为什么 Webflow/Framer 导出的页面又脏又乱
1.1 导出的不是 HTML,是“运行时现场”
先搞清楚一件反直觉的事:在 Webflow 的 Designer 里点的 Export,和在浏览器里看到的“另存为”,两者逻辑完全不同。Webflow 的生成机制基于可视化节点树,每个元素背后都带了一堆用于编辑器交互的数据属性,真正发布到 CDN 前才会做“编译式”输出,但编译结果仍然很“重”。
以 Webflow 页面为例,我见过比较典型的单页源码结构是这样的:head 里堆着一串 meta、预加载字体、link[rel=stylesheet] 引用若干 hash 命名的 CSS;header 和 section 里的 class 像 w-layout-blockcontainer、home-hero-wrapper 这类“结构词 + 业务词”混在一起,如果原来的画板层级用中文或者符号命名,还会被转义成更绕的 class;body 底部挂了一堆 script,有的是站点统计,有的是 Webflow 自己的交互脚本,有些是动效库。
Framer 这边更夸张一点。它默认并不像传统网页那样给你“一份完整 HTML”,更像是画布渲染 + 覆写数据的结构,很多元素是在浏览器里由脚本动态拼出来的。你直接 view-source 抓到的只是外壳。所以“一键导出干净的 HTML”这个问题,Framer 场景下要比 Webflow 更折腾,难点不在清理,而在“怎么拿到完整渲染后的结构”。
1.2 为什么这份代码会严重影响后续使用
如果你只是想在平台上继续改,那无所谓。但如果你跟我一样需要把页面交给客户、丢到 GitHub Pages 或者做二次开发,这些“脏东西”就是麻烦:
- 可读性极差:6 万行的单文件 HTML,你根本没法快速定位某个按钮在哪个区块,更别说拿去给团队协作。
- 依赖平台资源:页面字体、图片、CSS 经常引用
assets.website-files.com或framerusercontent.com这类外部域名,哪天源站域名调整、资源过期,页面就残了。 - 性能负担:运行时脚本、重复的 observers、整套动画内核被打包进一个页面里,带来的结果就是一个静态页要下载几百 KB 甚至几 MB 的 JS。
- 迁移困难:想接进 WordPress、Astro、11ty 这类项目,你需要的是一份可被模板化的结构,而不是一个“只能在原平台跑的黑盒”。
1.3 一键导出工具的目标边界
所以这个“一键导出”项目的边界其实很清晰:它不是一个全自动、所有页面类型通吃的工程化迁移系统,而是针对“营销页、落地页、作品集首页”这类内容形态的清洗器。
它在做的事是:抓取页面资源 → 解构 DOM → 移除框架脚本 → 下载本地化资源 → 规范化文档结构 → 输出可读的 HTML。它不解决的事同样重要:不支持 Webflow CMS 动态集合的完整还原,也不支持把 Framer 的响应式断点完美“翻译”成手写 CSS。做之前必须把范围框住,不然你会在“保留交互动效”和“代码干净”之间反复拉扯,最后哪个都做不好。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体设计思路与方案选型
2.1 为什么用 Node.js + cheerio,而不是 Python 或 Chrome 插件
我最早试过用 Python 的 BeautifulSoup,后来放弃了。BeautifulSoup 做静态 DOM 解析没问题,但这套流程里有大量“需要模拟浏览器策略”的环节,比如资源相对路径换算、CSS 里内嵌引用提取、DOM 操作后的序列化,Node.js 生态更顺手。尤其是 cheerio,它提供类似 jQuery 的选择器和遍历接口,处理批量移除、改属性这类操作效率很高。
之所以不直接用 Chrome 的 “Save as” 或 Puppeteer 无头浏览器完整渲染再存,是因为渲染后的 DOM 会引入更多自动化生成节点,比如 Framer 会给很多元素包一层绝对定位的容器,保存结果是“所见即所得”,但代码比原始产物还乱,二次编辑成本极高。我的工具以静态解析为主,Puppeteer 只作为 Framer 场景下“先执行脚本、拿最终 DOM 字符串”的前置手段。
