2856 字
14 分钟
再见,Typecho!你好,Astro!

再见,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 本地大模型环境#

这是脚本的“智能大脑”,用于生成标题和摘要。

  1. 安装 Ollama:

    • 访问 Ollama 官网
    • 根据你的操作系统 (macOS, Windows, Linux) 下载并安装 Ollama 应用程序。安装过程非常简单,一路“下一步”即可。
    • 安装完成后,Ollama 会在后台运行。
  2. 拉取模型:

    • 打开你的终端(在 Windows 上是 CMDPowerShell,在 macOS 上是 Terminal)。
    • 脚本默认使用的模型是 gemma3:12b。在终端中输入以下命令来下载它:
      Terminal window
      ollama pull gemma3:12b
    • 这个模型文件比较大(约 7GB),请耐心等待下载完成。下载时,请确保你的网络连接稳定。
  3. 验证 Ollama:

    • 下载完成后,可以运行 ollama list 命令查看已安装的模型,确保 gemma3:12b 在列表中。
    • 确保 Ollama 程序正在运行。通常在系统托盘区会有一个羊驼图标。

b. Python 3 环境#

脚本是使用 Python 编写的,你需要安装它。

  1. 安装 Python:

    • 如果你的电脑还没有安装 Python,请访问 Python 官网 下载并安装最新稳定版的 Python 3 (例如 Python 3.9+)。
    • 重要提示 (Windows 用户): 在安装时,请务必勾选 “Add Python to PATH”“Add python.exe to PATH” 选项,这样你才能在终端里直接使用 python 命令。
  2. 安装必要的 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 文件

复制以下脚本内容粘贴

migrate_with_ollama_v3_final.py
import os
import re
import yaml
import requests
import json
from bs4 import BeautifulSoup
from markdownify import markdownify as md
from 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 部署在另一台服务器上,或者你想尝试其他模型)。

第三步:运行脚本#

  1. 打开你的终端。
  2. 使用 cd 命令切换到你的脚本和 XML 文件所在的目录。例如:
    Terminal window
    cd /path/to/your/folder
  3. 运行脚本:
    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-21
heroImage: /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 命令。
  • Q: 脚本运行非常慢,每篇文章都要等很久。

    • A: 这是正常的。调用大语言模型进行分析和生成需要消耗大量的计算资源(CPU 或 GPU)。处理速度取决于你的电脑配置。一杯咖啡,耐心等待,自动化会为你节省更多时间。
  • Q: 终端报错 Connection refusedTimeout

    • A: 这个问题 99% 与 Ollama 有关。请检查:
      1. Ollama 是否正在运行? 检查系统托盘区的羊驼图标。
      2. 防火墙是否阻止了连接? 尝试暂时关闭防火墙测试一下。
      3. OLLAMA_API_URL 配置是否正确?如果你在本机运行,http://localhost:11434 通常是正确的。
  • Q: 终端报错 XML 文件未找到!

    • A: 请检查 CONFIG 中的 XML_FILE_PATH 是否填写正确。文件名、扩展名(.xml)都需要完全匹配。同时,请确认该文件确实和你的脚本在同一个目录下,或者你提供了正确的绝对/相对路径。
  • Q: 我可以换一个 Ollama 模型吗?

    • A: 当然可以!比如你想用一个更轻量的模型 qwen2:7b。首先,在终端运行 ollama pull qwen2:7b 下载它。然后,在脚本的 CONFIG 中,将 OLLAMA_MODEL 的值修改为 "qwen2:7b" 即可。注意,不同模型的效果和遵循指令的能力会有差异。

恭喜你!至此,你应该已经成功完成了博客的迁移。

再见,Typecho!你好,Astro!
https://blog.wlens.top/posts/再见typecho你好astro/
作者
Lao Wang
发布于
2025-06-21
许可协议
CC BY-NC-SA 4.0