把 Markdown 写的门店动态批量导入 Framer CMS:脚本读取
posts/*.md的标题、日期、分类、封面和正文,正文转成 HTML 供“格式化文本”字段使用,同时检查重复 slug、日期格式和非 https 图片,输出可导入的 CSV。
先看清成品
脚本 2 个测试通过;样例 3 篇文章输出 3 行 CSV,并报出 3 个问题(封面不是 https、slug 重复、日期格式错)。没有在 Framer 中实际导入,字段映射界面以 Framer 为准。
注意:Framer 的计划、价格和自定义域名、CMS 条目数等限额以官方价格页为准,本文不列具体数字。只使用你有权使用的图片、字体和文案;不要共享账号。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
| 文章集合 | Title、Slug、Date、Category、Cover、Content |
| 转换脚本 | Markdown → HTML,导入后保留标题、加粗、列表、链接 |
| 导入前检查 | slug 重复、日期、图片地址 |
需要准备哪些环境?
| 项目 | 要求 | 说明 |
|---|---|---|
| Framer 项目 | 有 CMS 的计划 | 条目上限以价格页为准 |
| Python | 3.9 以上 | pip install markdown |
| 图片 | 公开可访问的 https 地址 | 导入时 Framer 会读取图片 |
文件结构
26-framer-B-src/
├── md_to_framer_csv.py
├── test_convert.py
└── posts/
├── 01-opening.md
├── 02-beans.md # 故意用 http 图片
└── 03-dup.md # 故意重复标题、错误日期
第 1 步:在 Framer 建集合
CMS → 新建集合 门店动态,字段:
| 字段 | 类型 |
|---|---|
| Title | 纯文本(集合自带标题字段) |
| Slug | Slug(集合自带) |
| Date | 日期 |
| Category | 选项或纯文本 |
| Cover | 图片 |
| Content | 格式化文本 |
第 2 步:写文章
posts/01-opening.md:
---
title: 新店开业,第一周全场八折
date: 2026-11-01
category: 门店动态
cover: https://images.example.com/opening.jpg
---
## 开业时间
**11 月 1 日** 起正式营业,营业时间 10:00–20:00。
- 前 50 位到店顾客送挂耳咖啡
- 会员注册送积分
第 3 步:转换脚本
md_to_framer_csv.py:
"""把 posts/*.md(带简单头信息)转成 Framer CMS 可导入的 CSV。
头信息格式(文件开头,--- 包围):title / date / cover / category / slug(可选)
正文 Markdown 转成 HTML,导入时映射到“格式化文本(Formatted Text)”字段。
"""
import csv, re, sys, unicodedata
from datetime import date
from pathlib import Path
import markdown
FIELDS = ["Title", "Slug", "Date", "Category", "Cover", "Content"]
def slugify(s):
s = unicodedata.normalize("NFKC", s).lower()
s = re.sub(r"[^\w\u4e00-\u9fff]+", "-", s).strip("-")
return re.sub(r"-{2,}", "-", s)[:60]
def parse(text):
m = re.match(r"^---\n(.*?)\n---\n(.*)$", text, re.S)
if not m: raise ValueError("缺少 --- 头信息")
meta = dict(line.split(":", 1) for line in m.group(1).splitlines() if ":" in line)
return {k.strip(): v.strip() for k, v in meta.items()}, m.group(2)
def convert(paths):
rows, problems, slugs = [], [], set()
for p in sorted(paths):
try:
meta, body = parse(Path(p).read_text(encoding="utf-8"))
except ValueError as e:
problems.append(f"{p.name}: {e}"); continue
title = meta.get("title", "")
slug = meta.get("slug") or slugify(title)
if not title: problems.append(f"{p.name}: 缺少 title")
if slug in slugs: problems.append(f"{p.name}: slug “{slug}” 重复")
slugs.add(slug)
try: date.fromisoformat(meta.get("date", ""))
except ValueError: problems.append(f"{p.name}: date 应为 YYYY-MM-DD")
cover = meta.get("cover", "")
if cover and not cover.startswith("https://"): problems.append(f"{p.name}: cover 需要是 https 公开图片地址")
rows.append({"Title": title, "Slug": slug, "Date": meta.get("date", ""), "Category": meta.get("category", ""),
"Cover": cover, "Content": markdown.markdown(body.strip(), extensions=["tables"])})
return rows, problems
if __name__ == "__main__":
rows, problems = convert(Path(sys.argv[1]).glob("*.md"))
with open(sys.argv[2], "w", encoding="utf-8", newline="") as f:
w = csv.DictWriter(f, fieldnames=FIELDS); w.writeheader(); w.writerows(rows)
print(f"写出 {sys.argv[2]}:{len(rows)} 条")
for p in problems: print("⚠", p)
sys.exit(1 if problems else 0)
pip install markdown pytest
python md_to_framer_csv.py posts framer_import.csv
python -m pytest -q
样例的本机实际输出:
写出 out.csv:3 条
⚠ 02-beans.md: cover 需要是 https 公开图片地址
⚠ 03-dup.md: slug “新店开业-第一周全场八折” 重复
⚠ 03-dup.md: date 应为 YYYY-MM-DD
(本机运行时输出文件名用的是 out.csv。)第一行的 Content 列为:
<h2>开业时间</h2>
<p><strong>11 月 1 日</strong> 起正式营业,营业时间 10:00–20:00。</p>
<ul>
<li>前 50 位到店顾客送挂耳咖啡</li>
<li>会员注册送积分</li>
</ul>
改完源文件,直到没有 ⚠ 再导入。
第 4 步:导入
- 打开集合,点工具栏的 Import,选择 CSV。
- 逐列确认映射:Content → 格式化文本,Cover → 图片,Date → 日期。
- 先用 2~3 篇文章试导,检查列表页和详情页,再导入全部。
检查结果:详情页里标题、加粗、列表、链接都正常显示;条目数与 CSV 行数一致。
调试示例
slug 默认由中文标题生成,例如 新店开业-第一周全场八折。如果 Framer 导入时提示 slug 不可用,或者你希望网址更短,在头信息中加 slug: opening-week 指定英文 slug,脚本会优先使用。
出错了怎么办?
| 现象 | 原因 | 解决 |
|---|---|---|
| 正文显示成 HTML 源码 | Content 映射成了纯文本 | 改为格式化文本字段 |
| 图片没导进来 | 图片地址不是公开 https 或有防盗链 | 换成公开地址 |
| 日期错一天 | 时区换算 | 只写日期 YYYY-MM-DD |
| 重复导入后条目翻倍 | slug 不一致 | 保持 slug 稳定,用于更新已有条目 |
| 中文乱码 | CSV 编码不是 UTF-8 | 使用脚本输出,不要用 Excel 另存 |
模板规则
- 内容以 Markdown 为准,CSV 只是中间产物。
- slug 一旦发布不改,避免旧链接失效。
- 先试导少量,再全量。
项目资源
| 用途 | 地址 |
|---|---|
| Framer CMS CSV 导入 | https://www.framer.com/academy/lessons/csv-import |
| Python-Markdown | https://python-markdown.github.io/ |
| Framer 价格 | https://www.framer.com/pricing |
把内容放在 Markdown 里写、用脚本检查后再导入,CMS 里就不会出现重复页面和失效图片;以后换建站工具,内容也能原样带走。