
再见,Typecho!你好,Astro!
用了四年的 Typecho,最终还是决定挥手告别,正式迁入 Astro 的怀抱。
迁移的第一道坎,就是 如何把一百多篇旧博客文章转换成 Markdown 文件。在 GitHub 上偶然发现了一个名叫 ByeTyp 的插件,它的初衷是帮用户将 Typecho 的内容迁移到 WordPress。原理也不复杂:将 Typecho 的文章导出为 WordPress 支持的 XML(XWR)格式,再用 WordPress 的导入工具导入。
这启发了我:既然能导出 XML,那完全可以用 Python 脚本来解析这个文件,提取文章内容、日期、标签等信息,然后自动生成 .md
文件,顺便还能调用本地的 Ollama 大模型,为每篇文章重新生成更合适的标题和标签,自动化程度直接拉满!
于是,我动手写了一个迁移脚本,接下来的文章将会介绍它的实现过程与效果。
🚀 1. 脚本简介
这是一个功能强大的自动化脚本,旨在帮助你将旧博客(支持 Typecho 或 WordPress 导出的 XML 文件)的文章,无缝迁移到为新主题(如 Astro、Hugo 等静态博客框架)设计的 Markdown 格式。
它不仅仅是简单的格式转换,更利用了 Ollama 本地大语言模型 的能力,智能地为你的每一篇文章生成更吸引人、更符合 SEO 规范的中文标题和文章摘要,让你的内容在新家大放异彩!
✨ 主要功能
- 自动解析: 读取 WordPress/Typecho 导出的
.xml
备份文件。 - 智能优化: 调用本地 Ollama API,为每篇文章生成优化后的中文标题和摘要。
- 格式转换: 将文章内容从 HTML 干净地转换为 Markdown。
- 元数据生成: 自动创建符合现代静态博客主题(如 Astro)要求的 Frontmatter (文章头部信息),包括标题、描述、发布日期等。
- 文件处理: 自动根据文章原始标题生成对操作系统和 URL 友好的 Markdown 文件名。
- 高度可配置: 只需修改脚本头部的配置区,无需改动任何代码逻辑。
🛠️ 2. 环境准备 (Prerequisites)
在运行脚本之前,你需要确保你的电脑上已经安装并配置好以下软件。别担心,每一步都有详细指引。
a. Ollama 本地大模型环境
这是脚本的“智能大脑”,用于生成标题和摘要。
-
安装 Ollama:
- 访问 Ollama 官网。
- 根据你的操作系统 (macOS, Windows, Linux) 下载并安装 Ollama 应用程序。安装过程非常简单,一路“下一步”即可。
- 安装完成后,Ollama 会在后台运行。
-
拉取模型:
- 打开你的终端(在 Windows 上是
CMD
或PowerShell
,在 macOS 上是Terminal
)。 - 脚本默认使用的模型是
gemma3:12b
。在终端中输入以下命令来下载它:Terminal window ollama pull gemma3:12b - 这个模型文件比较大(约 7GB),请耐心等待下载完成。下载时,请确保你的网络连接稳定。
- 打开你的终端(在 Windows 上是
-
验证 Ollama:
- 下载完成后,可以运行
ollama list
命令查看已安装的模型,确保gemma3:12b
在列表中。 - 确保 Ollama 程序正在运行。通常在系统托盘区会有一个羊驼图标。
- 下载完成后,可以运行
b. Python 3 环境
脚本是使用 Python 编写的,你需要安装它。
-
安装 Python:
- 如果你的电脑还没有安装 Python,请访问 Python 官网 下载并安装最新稳定版的 Python 3 (例如 Python 3.9+)。
- 重要提示 (Windows 用户): 在安装时,请务必勾选 “Add Python to PATH” 或 “Add python.exe to PATH” 选项,这样你才能在终端里直接使用
python
命令。
-
安装必要的 Python 库:
- 脚本依赖几个第三方库来完成工作。打开终端,运行以下命令来一次性安装它们:
Terminal window pip install requests beautifulsoup4 lxml markdownify pyyaml - 如果提示
pip
命令不存在,可能是 Python 的 PATH 没有配置好,或者你需要使用pip3
。
- 脚本依赖几个第三方库来完成工作。打开终端,运行以下命令来一次性安装它们:
📖 3. 使用步骤 (Step-by-Step Guide)
环境准备就绪后,只需四步即可完成迁移!
第一步:获取博客的 XML 导出文件
- Typecho: 你安装ByeTyp 的插件来生成与 WordPress 兼容的
.xml
文件。
将下载好的 .xml
文件(例如 WordPress.2025-06-21.xml
)和你下载的 migrate_for_new_theme_final_v2.py
脚本文件放在同一个文件夹下,方便管理。
第二步:配置脚本
新建 migrate_for_new_theme_final_v2.py
文件
复制以下脚本内容粘贴
import osimport reimport yamlimport requestsimport jsonfrom bs4 import BeautifulSoupfrom markdownify import markdownify as mdfrom datetime import datetime
# ==============================================================================# --- 配置区 (请根据你的情况修改这里) ---# ==============================================================================CONFIG = { # 你的 Typecho 导出的 XML 文件路径 "XML_FILE_PATH": "WordPress.2025-06-21.xml",
# 生成的 Markdown 文件存放目录 "OUTPUT_DIR": "./final_posts",
# --- Ollama 相关配置 --- "OLLAMA_API_URL": "http://localhost:11434/api/generate", "OLLAMA_MODEL": "gemma3:12b",}# ==============================================================================# --- 配置区结束 ---# ==============================================================================
def sanitize_filename(text): """ 清理文件名,移除Windows和URL中非法的字符,并限制长度。 """ text = re.sub(r'[\\/*?:"<>|]', "", text) text = re.sub(r'\s+', '-', text) return text[:100]
def call_ollama_for_chinese_metadata(original_title, content): """调用 Ollama API 获取中文的标题、摘要和标签""" print(f" 🧠 正在调用 Ollama 为《{original_title}》生成中文元数据...") content_preview = (content[:1500] + '...') if len(content) > 1500 else content
prompt = f"""作为一名资深的中文博客编辑,请仔细阅读以下文章内容,并以纯粹的、单一的 JSON 对象格式返回结果。不要包含任何额外的解释或 Markdown 格式。你的任务是生成以下中文元数据:- "title": 一个更吸引人、更符合 SEO 的【中文标题】。- "description": 一段精炼的【中文文章摘要】,严格控制在80个汉字以内,必须是单行文本。- "tags": 一个包含 2 到 4 个相关【中文关键词】的数组(字符串列表)。
--- 待分析的文章 ---原始标题: "{original_title}"文章内容预览:{content_preview}---
请严格按照以上要求,仅输出 JSON 对象:"""
payload = { "model": CONFIG["OLLAMA_MODEL"], "prompt": prompt, "format": "json", "stream": False }
try: response = requests.post(CONFIG["OLLAMA_API_URL"], json=payload, timeout=240) response.raise_for_status() response_text = response.text try: json_data = json.loads(response_text) if 'response' in json_data: response_data = json.loads(json_data['response']) else: response_data = json_data except json.JSONDecodeError: response_data = json.loads(response.json()['response'])
print(" ✅ Ollama 中文处理成功!") return response_data except Exception as e: print(f" ❌ 调用或解析 Ollama 响应失败: {e}") return None
def process_xml_file(): """主处理函数""" if not os.path.exists(CONFIG["XML_FILE_PATH"]): print(f"错误:XML 文件未找到!路径: {CONFIG['XML_FILE_PATH']}") return
if not os.path.exists(CONFIG["OUTPUT_DIR"]): os.makedirs(CONFIG["OUTPUT_DIR"]) print(f"创建输出目录: {CONFIG['OUTPUT_DIR']}")
with open(CONFIG["XML_FILE_PATH"], 'r', encoding='utf-8') as file: soup = BeautifulSoup(file, 'lxml-xml')
items = soup.find_all('item') print(f"\n找到 {len(items)} 个条目。开始转换...\n" + "="*40)
post_count = 0 for item in items: if not (item.find('wp:post_type') and item.find('wp:post_type').text == 'post' and item.find('wp:status') and item.find('wp:status').text == 'publish'): continue
post_count += 1 original_title = item.find('title').text print(f"\n[{post_count}] 正在处理: {original_title}")
pub_date_node = item.find('wp:post_date_gmt') pub_date_str = pub_date_node.text if pub_date_node and pub_date_node.text and pub_date_node.text != '0000-00-00 00:00:00' else item.find('wp:post_date').text
# --- 优化点 1 (已更正): 修改日期格式为 MM-DD-YYYY --- created_at = datetime.strptime(pub_date_str, '%Y-%m-%d %H:%M:%S').strftime('%m-%d-%Y')
content_node = item.find('content:encoded') content_raw = content_node.text if content_node else "" content_md = md(content_raw[len('<!--markdown-->'):].strip()) if content_raw.startswith('<!--markdown-->') else md(content_raw, heading_style="ATX")
metadata = call_ollama_for_chinese_metadata(original_title, content_md)
if not metadata: print(f" ❌ 跳过文章《{original_title}》,因为无法从 Ollama 获取元数据。") continue
description_text = metadata.get('description', '文章摘要').replace('\n', ' ').strip()
frontmatter = { 'title': metadata.get('title', original_title), 'description': description_text, 'createdAt': created_at, 'image': '../assets/spectre.webp', 'tags': metadata.get('tags', []) }
filename_base = sanitize_filename(original_title) filename = f"{filename_base}.md" print(f" ℹ️ 使用原始标题生成文件名: {filename}")
frontmatter_yaml = yaml.dump(frontmatter, allow_unicode=True, sort_keys=False, default_flow_style=False) file_content = f"---\n{frontmatter_yaml}---\n\n{content_md}"
filepath = os.path.join(CONFIG["OUTPUT_DIR"], filename)
with open(filepath, 'w', encoding='utf-8') as f: f.write(file_content)
print(f" ✅ 文件已保存: {filepath}")
print("\n" + "="*40 + f"\n转换完成!共处理了 {post_count} 篇文章。")
if __name__ == '__main__': process_xml_file()
用任何文本编辑器(如 VS Code, Sublime Text, 记事本++)打开 migrate_for_new_theme_final_v2.py
文件。你只需要修改文件顶部的 CONFIG
区域。
# ==============================================================================# --- 配置区 (请根据你的情况修改这里) ---# ==============================================================================CONFIG = { # 1. 你的 Typecho/WordPress 导出的 XML 文件路径 # 如果文件和脚本在同一目录,直接写文件名即可。 "XML_FILE_PATH": "WordPress.2025-06-21.xml",
# 2. 生成的 Markdown 文件存放目录 # 脚本会自动创建这个目录。 "OUTPUT_DIR": "./final_posts",
# --- Ollama 相关配置 (通常无需修改) --- # 3. 如果你的 Ollama 运行在 다른 机器或端口,修改这里。 "OLLAMA_API_URL": "http://localhost:11434/api/generate",
# 4. 如果你想换一个已下载的模型,修改这里。 "OLLAMA_MODEL": "gemma3:12b",}# ==============================================================================
XML_FILE_PATH
: 【必须修改】 将"WordPress.2025-06-21.xml"
替换成你自己的 XML 文件名。OUTPUT_DIR
: 【可选修改】 这是存放转换后.md
文件的文件夹名称,默认是final_posts
。OLLAMA_API_URL
/OLLAMA_MODEL
: 【通常无需修改】 除非你清楚你在做什么(比如 Ollama 部署在另一台服务器上,或者你想尝试其他模型)。
第三步:运行脚本
- 打开你的终端。
- 使用
cd
命令切换到你的脚本和 XML 文件所在的目录。例如:Terminal window cd /path/to/your/folder - 运行脚本:
Terminal window python migrate_for_new_theme_final_v2.py
第四步:检查结果
脚本会开始运行,你将在终端看到类似下面的输出:
创建输出目录: ./final_posts
找到 150 个条目。开始转换...========================================
[1] 正在处理: My Old Post Title 🧠 正在调用 Ollama 为《My Old Post Title》生成中文元数据... ✅ Ollama 中文处理成功! ℹ️ 使用原始标题生成文件名: My-Old-Post-Title.md ✅ 文件已保存: ./final_posts/My-Old-Post-Title.md
[2] 正在处理: Another Great Article 🧠 正在调用 Ollama 为《Another Great Article》生成中文元数据... ✅ Ollama 中文处理成功! ℹ️ 使用原始标题生成文件名: Another-Great-Article.md ✅ 文件已保存: ./final_posts/Another-Great-Article.md...========================================转换完成!共处理了 150 篇文章。
运行结束后,在你的项目目录下会多出一个 final_posts
文件夹。里面就是所有转换好的 Markdown 文件!
输出文件示例 (My-Old-Post-Title.md
):
---title: 一个由AI生成的更棒的中文标题description: 一段由AI生成的、精准凝练且少于80字的中文文章摘要。pubDate: 2025-06-21heroImage: /default.webp---
这里是转换后的 Markdown 格式的文章正文。所有的 HTML 标签,如 `<h1>`, `<p>`, `<strong>` 等,都会被正确地转换成 `#`, 段落, `**` 等 Markdown 语法。
现在,你可以将 final_posts
文件夹里的所有 .md
文件复制到你新博客项目的文章目录中(例如 Astro 项目的 src/content/blog/
目录)。
🤔 4. 常见问题 (FAQ) & 故障排查
-
Q: 终端报错
ModuleNotFoundError: No module named 'requests'
- A: 这是因为你没有安装必要的 Python 库。请返回 [环境准备 -> b. Python 3 环境],运行
pip install
命令。
- A: 这是因为你没有安装必要的 Python 库。请返回 [环境准备 -> b. Python 3 环境],运行
-
Q: 脚本运行非常慢,每篇文章都要等很久。
- A: 这是正常的。调用大语言模型进行分析和生成需要消耗大量的计算资源(CPU 或 GPU)。处理速度取决于你的电脑配置。一杯咖啡,耐心等待,自动化会为你节省更多时间。
-
Q: 终端报错
Connection refused
或Timeout
。- A: 这个问题 99% 与 Ollama 有关。请检查:
- Ollama 是否正在运行? 检查系统托盘区的羊驼图标。
- 防火墙是否阻止了连接? 尝试暂时关闭防火墙测试一下。
OLLAMA_API_URL
配置是否正确?如果你在本机运行,http://localhost:11434
通常是正确的。
- A: 这个问题 99% 与 Ollama 有关。请检查:
-
Q: 终端报错
XML 文件未找到!
- A: 请检查
CONFIG
中的XML_FILE_PATH
是否填写正确。文件名、扩展名(.xml
)都需要完全匹配。同时,请确认该文件确实和你的脚本在同一个目录下,或者你提供了正确的绝对/相对路径。
- A: 请检查
-
Q: 我可以换一个 Ollama 模型吗?
- A: 当然可以!比如你想用一个更轻量的模型
qwen2:7b
。首先,在终端运行ollama pull qwen2:7b
下载它。然后,在脚本的CONFIG
中,将OLLAMA_MODEL
的值修改为"qwen2:7b"
即可。注意,不同模型的效果和遵循指令的能力会有差异。
- A: 当然可以!比如你想用一个更轻量的模型
恭喜你!至此,你应该已经成功完成了博客的迁移。