博客从Hugo迁移到Ghost

博客从Hugo迁移到Ghost
Photo by Andrew Neel / Unsplash

我的博客从2023年开始从WordPress迁移到Hugo,使用Hugo有很多的优点,比如说省钱,只需把博客部署在GitHub上,就能剩下一笔VPS的开支。但是它也有很多的缺点,最让我感觉到不方便的地方在于,我不能随时随地的写博客。

最早的时候,我在我店里的电脑上部署了Hugo,我写博客就只能到店里之后才能进行,当然也可以在别的地方写好,然后在店里的电脑上进行上传,但是这个过程是不连续的。有时候我们做事情讲究一鼓作气,很多时候,因为不连续的原因,写博客的兴致也少了很多。

后来,我们Hugo系统部署在我的VPS上。这样我就可以随时随地的写博客了,但是还有一个问题就是,写博客需要打开终端,连上VPS,步骤太多。因此,我不止一次的想要从Hugo迁移会WordPress。但是从网上搜索的教程全都是从WordPress迁移到Hugo的。大佬们好像是不屑于再回归WordPress。

最近了解到了ghost这个系统,我用docker安装上之后体验了一下,感觉不错。没有WordPress那么臃肿,有后台可以直接写文章。于是就想把博客迁移到Ghost,网上搜索相关教程当然也是无果的。博友建议我寻求AI的帮助。

疫情期间,我也自学过一点点python。于是我就想把Hugo的md文件,转化成ghost导入的json文件。借助ChatGPT,终于把代码给搞了出来。代码如下:

转换代码


import os
import json
import re
import yaml
import markdown
from datetime import datetime

def parse_markdown(filepath):
    with open(filepath, 'r', encoding='utf-8') as f:
        text = f.read()

    match = re.match(r'^---\n(.*?)\n---\n(.*)', text, re.DOTALL)
    if not match:
        print(f"⚠️ 跳过文件:{filepath},没有找到 front matter")
        return None

    front_matter_text, content_part = match.groups()
    try:
        front_matter = yaml.safe_load(front_matter_text)
    except Exception as e:
        print(f"⚠️ YAML 解析错误:{filepath} -> {e}")
        return None

    title = front_matter.get('title')
    raw_date = front_matter.get('date')
    tags = front_matter.get('tags', [])

    if not title or not raw_date or not content_part.strip():
        print(f"⚠️ 数据不完整,跳过文件:{filepath} -> title: {title}, date: {raw_date}, content_length: {len(content_part.strip())}")
        return None

    try:
        if isinstance(raw_date, datetime):
            dt = raw_date
        elif isinstance(raw_date, str):
            try:
                dt = datetime.fromisoformat(raw_date.replace("Z", "+00:00"))
            except ValueError:
                try:
                    dt = datetime.strptime(raw_date, "%Y-%m-%d %H:%M:%S")
                except ValueError:
                    dt = datetime.strptime(raw_date, "%Y-%m-%dT%H:%M:%S%z")
        else:
            print(f"⚠️ 日期格式无法识别,跳过:{filepath} -> date: {raw_date}")
            return None
    except Exception as e:
        print(f"⚠️ 日期解析失败:{filepath} -> {e}")
        return None

    date_str = dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')

    html_content = markdown.markdown(content_part.strip(), extensions=['extra', 'codehilite', 'tables'])

    return {
        'title': title,
        'slug': os.path.splitext(os.path.basename(filepath))[0],
        'html': html_content,
        'created_at': date_str,
        'updated_at': date_str,
        'tags': tags
    }


def walk_md_files(base_dir):
    for root, _, files in os.walk(base_dir):
        for file in files:
            if file.endswith('.md'):
                yield os.path.join(root, file)

def main():
    base_dir = './'
    print(f"开始遍历目录:{base_dir}")
    posts = []
    tags_set = set()

    for filepath in walk_md_files(base_dir):
        print(f"📄 正在处理文件:{filepath}")
        post = parse_markdown(filepath)
        if post:
            post_tags = []
            for tag_name in post['tags']:
                tag_str = str(tag_name).strip()
                if tag_str:
                    tags_set.add(tag_str)
                    post_tags.append(tag_str)

            post['tags'] = post_tags
            posts.append(post)

    ghost_data = {
        "meta": {
            "exported_on": int(datetime.now().timestamp() * 1000),
            "version": "5.0.0"
        },
        "data": {
            "posts": [],
            "tags": [],
            "posts_tags": [],
            "users": [
                {
                    "id": 1,
                    "name": "admin",
                    "slug": "admin",
                    "email": "admin@example.com"
                }
            ]
        }
    }

    for i, post in enumerate(posts, start=1):
        ghost_data["data"]["posts"].append({
            "id": i,
            "title": post["title"],
            "slug": post["slug"],
            "html": post["html"],
            "status": "published",
            "created_at": post["created_at"],
            "updated_at": post["updated_at"],
            "published_at": post["created_at"],
            "author_id": 1
        })

    tag_id_map = {tag: idx + 1 for idx, tag in enumerate(tags_set)}
    for tag, tag_id in tag_id_map.items():
        ghost_data["data"]["tags"].append({
            "id": tag_id,
            "name": tag,
            "slug": tag.lower().replace(" ", "-")
        })

    for post_id, post in enumerate(posts, start=1):
        for tag in post["tags"]:
            ghost_data["data"]["posts_tags"].append({
                "post_id": post_id,
                "tag_id": tag_id_map[tag]
            })

    with open("ghost_import.json", "w", encoding="utf-8") as f:
        json.dump(ghost_data, f, ensure_ascii=False, indent=2)
    print("✅ 已生成 ghost_import.json 文件")

if __name__ == "__main__":
    main()

使用方法:

把这个代码保存成Hugotoghost.py,把这个文件放在content文件夹里。然后输入命令:

python hugotoghost.py

会生成一个ghost_import.json的文件,在ghost的后台把这个文件上传即可。

Read more

站在新高考的起点:对两年后女儿的期许

站在新高考的起点:对两年后女儿的期许

2025年的高考今天落幕,这是“3+1+2”模式全面实施的第一年。从改革到首考,从认知到实践,这不仅是对学生能力的测量,更是一种教育方向的宣示。 作为家长,我对这次高考有三点深刻感受: 一、语文卷:不再“刷题”,更考“立志与思辨” 今年全国卷一作文聚焦“民族魂”,从老舍、艾青、穆旦的文字中引导学生理解爱国情怀的不同方式 。考题不仅强调情感共鸣,也要求结构逻辑清晰,表达真诚、语言自然流畅 。 这道作文开放而不空洞:任何有生活、有思考的学生,都能找到表达路径;但能够精准逻辑、有独立见解,并融入素材,便是真高分。 这使我明白,真正的语文能力,不在于套路,而在于“你是谁”——有温度、有思想、有立场。 二、历史题:文言与史料,强调跨学科理解 今年历史全国卷大量使用文言史料,并引导学生依据不同角度进行对比、评析与判断

By 老刘
荔枝路远,道在人心——从《易经》看李善德的一生

荔枝路远,道在人心——从《易经》看李善德的一生

马伯庸的小说《长安的荔枝》及其改编剧集,看似讲述一场跨越千里的荔枝运输奇旅,实则以小人物的遭遇折射盛唐末期政治、社会、文化的真实图景。主人公李善德,这位从九品小吏,在完成“运荔枝”这一看似荒诞实则关乎权力的任务中,演绎出一部小人物在大时代中如何“知命”、“尽性”、“归真”的命运史。 本文尝试以《易经》的视角,借其卦象与哲思,还原李善德的命运轨迹与精神转化。 一、《屯卦》:困中求进,命运起始 卦辞:“屯,元亨,利贞。勿用有攸往。利建侯。” “屯”象征万物初生,动荡未定。李善德身为地方文吏,无权无势,却在极为不利的局势中被委以“千里送荔枝”之命——这是危也是机。他组建了一支临时小队,历经风雨山川、盗贼瘴气,表面是传荔枝,实则是在夹缝中求生存。这正是“屯”卦中的“利建侯”

By 老刘
蒙卦:从困中启,向光而行

蒙卦:从困中启,向光而行

如果你在街头做一个采访,问:“《易经》是什么?” 十有八九会有人回答:“算卦的书呗。” 这个答案,其实对,也不全对。 《易经》确实起源于卜筮之术,但它远远超越了“算卦”本身。它是一种看待变化的哲学,是中华文化思维的源代码,是一部人生操作手册。在我们日常生活中,家庭、工作、人际、选择,每个节点的应对,都可以在《易经》中找到线索。 昨天读南怀瑾先生对蒙卦的讲解,颇有感触。他说:蒙卦,其实就是屯卦翻转过来的样子。 * 屯卦(第三卦):☵上坎下震☳ —— 水雷屯。 * 蒙卦(第四卦):☶上艮下坎☵ —— 山水蒙。 我们平常说“启蒙”,正是从这个卦象来的。 屯是磨难,蒙是顿悟 屯,是开始阶段的艰难,万事起头难。 蒙,是经过困顿后,开始有“

By 老刘
屯:起步的困难,是值得尊重的时刻

屯:起步的困难,是值得尊重的时刻

昨天继续读《易经杂说》,读到第三卦——屯卦。卦辞写道: 屯,元亨,利贞。勿用有攸往,利建侯。 南怀瑾先生解释,“屯”这个字,一横开天地,一下面的“屮”,像刚要钻出泥土的芽,而“屯”字本身,有艰涩、积聚的意思。万物初生,正是最混沌、最艰难的时候。 卦象是水雷屯:下震上坎。震为雷,坎为水,意思是:雷藏于水下,动而未发。就像大海中滚动的雷电,一切都在发生,却还没有方向。一方面是能量的积蓄,另一方面却是外在环境的未明。天地未分,局势不定,正是人生中最难走的阶段之一。 我想起了去年女儿刚升上高中的时候。有次接她回家,在车上她闷闷不乐地说:“数学太难了,题目都看不懂。”我问她哪题不会,她摇头:“都不会。” 她的眉头紧锁,跟小时候做拼图时卡在中间的样子很像。

By 老刘