博客从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

节气里的智慧

节气里的智慧

又到了一年的农历七月。 天气不再像盛夏那样燥热,夜晚多了一丝凉意。四季轮回,寒来暑往,在不知不觉间,年景又走过了一大半。 小时候,我总觉得夏天的热是没有尽头的。那时候别说空调了,连电扇都都没有,白天热得人心浮气躁。于是,我常忍不住问母亲: “啥时候能凉快啊?” 母亲总是笑着说:“快了,快了,到七月就凉快了。” 接着她就会念叨几句顺口溜似的谚语: * “交了七月节,夜凉白天热。” * “交了八月节,就晌午头一会热。” * “交了九月节,一热也不热。” * “交了十月节,下雨就下雪。” 这些话没有气象数据,没有科学仪器,却能准确地描摹出气候的变迁。小时候不懂,只觉得母亲念叨的很有趣。长大后才明白,那是黄河中下游地区世世代代人们的经验总结,是生活里最实在的智慧。 很多时候,这些古老的谚语比天气预报还灵验。它们伴随着农耕社会的节奏而来,帮助人们安排农事,也抚慰人们对季节更替的期待。 其实,不只是“交七月节,夜凉白天热”,在中国广袤的土地上,每个地方都有属于自己的气候歌谣:南方人看梅雨,北方人盼秋凉,西北人识得风沙,

By laoliu
折腾杀毒软件的一点感想

折腾杀毒软件的一点感想

这几天,我一直在折腾各种杀毒软件。事情的起因,是一个熟人单位里发生的一起网络诈骗。单位里一位员工因为点击了不明链接,不小心中毒,黑客随即接管了他的钉钉账号。接下来就是老套路:建群、拉人、冒充发工资补贴,让大家输入银行卡号。可别小看这招,受害的人不少,听说损失最大的一位,损失相当的严重。 这件事让我再次意识到,网络安全并不是一个遥远的概念,而是真实发生在身边的风险。尤其最近“银狐木马”在网上泛滥,更让我觉得杀毒软件的重要性不容忽视。 为什么折腾杀毒软件? 很多人都说现在病毒少了,而且Windows自带的杀毒软件也足够了,说实话,现在很多新型木马的第一步,就是要绕过 Windows Defender,所以单纯依赖 WD,并不能让人放心。这几天,我几乎把主流的杀毒软件都试了一遍: * 腾讯 IOA 基础版:资源占用少,系统流畅;智能弹窗过滤很安静;没有广告;还能后台一键部署到家里的几台电脑;关键是免费(能免费部署500台),而且是腾讯重点发展的项目,检测能力还行,其出身让人感觉不如360等专业的杀毒软件让人心安。 * 360

By laoliu
开箱|媳妇的华为 Watch Fit 4

开箱|媳妇的华为 Watch Fit 4

媳妇前段时间去南京学习,回来跟我说起一件事:她的同学戴的手表居然能直接接打电话。她说起的时候眼神里有点心动。她平常其实不太喜欢戴这些东西,更结婚没多久的时候,说想要一块手表,也就戴了一段时间,就收藏了起来了。前几年给她买了一只荣耀手环 7,她戴过一阵子,后来也不戴了。 之所以现在又提出来,大概是和她最近工作有关,最近她们科新开了中医护理门诊,有不少患者是冲着她的刮痧来的,所以有时候满手都是刮痧油的情况下,有电话进来的时候,再取手机确实也不太方便。 于是我在京东上搜了一下。没想到就发现了华为 Watch Fit 4 ——白色的表带,颜值简洁大方,价格也不算离谱,不到 800 块钱(如果有国补的情况下,还能便宜不少)。几乎一下就戳中了她的需求,于是毫不犹豫下单。 开箱过程 快递很快,盒子不大,正面是手表的渲染图。拆开之后,里面的东西也很简单: * 手表本体 * 白色硅胶表带(已经装好) * 充电线 * 使用说明书 说实话,这条 充电线还挺让我惊艳的。磁吸式的圆形接口,

By laoliu
粮票里的父爱

粮票里的父爱

前几天在博客群里闲聊,话题莫名其妙地扯到了粮票。我随口说了一句:“当年我爸把全国的粮票攒着不舍得用,说是为了给我和我哥上大学的时候用。”阿均半开玩笑地说:“整理一下这个故事吧,也挺有意思的。”我心里一动,其实不久前还听我爸抱怨呢:“当年给你们攒的粮票,其实也没怎么用上。” 说到粮票,很多年轻人可能都没见过。上世纪八九十年代,粮食和一些生活用品都是按票供应的。手里有粮票才能买粮,没有就只能干着急。那时候的粮票分两种:地方粮票和全国性粮票。地方粮票只能在本地用,而全国性的粮票可以全国通用,更珍贵。 我爸攒的,就是全国粮票。他考虑到我和我哥上大学,不一定会留在甘肃,怕我们去外地没粮票买粮,饿肚子。他省吃俭用,把这些全国粮票默默积攒起来,为我们未来的温饱留着。想象他每天回家,把一张张小票叠好放进抽屉里,那画面真是朴素又让人心安。 求学的道路上,其实父母一直在鼓励我们。父亲看到单位新分来的大学生待遇非常好,意识到知识和学历的重要性;同时,我们家从祖辈开始都没有什么学问,所以望子成龙的期望特别重。放假的时候,除非是集中性的农活,比如收种庄稼、施肥这些有时效的农活,平常的象除草这些活儿都不让我

By laoliu