博客从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": "[email protected]"
                }
            ]
        }
    }

    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

风动,幡动还是心动

风动,幡动还是心动

读南怀瑾先生的《禅宗和道家》,读到“风动、幡动、心动”这个典故(佛家称为公案)时,我的思绪回到了过去。 记得第一次读到这个公案,大概是在初中的政治课本上。故事是这样的: 唐代仪凤元年(676年),慧能大师在广州法性寺(今光孝寺)听印宗法师讲《涅槃经》。一阵风吹来,寺中的幡旗随风摇曳,引发了两位僧人的争论。一位僧人说:“是风在动。”另一位坚持:“是幡在动。”两人争执不下,慧能大师便上前说道:“不是风动,也不是幡动,是仁者的心在动。” 当时读到这里,我心想古人真傻,这看似很有哲理,实则是对自然现象缺乏了解,根本就是一种空气流动罢了,和风、幡、心都毫无关系。甚至到了大学,我和信奉佛教的父母聊起这个公案时,依旧是以嘲弄的口吻,当时还试图用这个典故让他们“回头”。 随着年龄增长,读书和见识也多了,才发现当初的小丑恰恰是我自己。 这哪是古人没有见识,分明是我的认知高度不够,没有达到那个层次,

By laoliu
金钱心理学:财富、人性和幸福的永恒真相

金钱心理学:财富、人性和幸福的永恒真相

我曾以为,理财是一个很复杂的东西,股票、基金、保险等等,充满了复杂的公式和博弈。所以,对理财这方面一直抱着敬而远之的态度,只会挑选一些风险小的项目。也曾在股市上投入了一些,但每次都赶不上节奏,所以慢慢的这方面的心思也就淡了。 前几天偶尔在微信读书上看到了摩根·豪泽尔的《金钱心理学:财富、人性和幸福的永恒真相》这本书,才发现原来理财也可以是这么简单的一件事情,但简单的事情,往往也是最不容易做到的事情。 我们很多人都会大精力花在寻找下一个“暴富”的机会上,希望能一击即中,实现财务自由,其实这是很不现实的东西。这本书没有教我们如何赚钱,而是教我们如何守财。 这听起来似乎现在主流的消费观向左,但我认为却触及了财富的本质,特别是当下这个大环境中,守财才是更重要的。书中虽然用了大量的例子,但给我印象最深,也是颠覆我原来概念的一个例子是:地球的冰河期并非由某个特别寒冷的冬天造成,而是因为无数个凉爽的夏天。那些看似不够热的夏天,导致上一年的冰雪没有完全融化,日积月累,最终形成了巨大的冰川。我一直以为,冰河期的形成,是突变的,是哗啦一下子,地球被冻上了。 作者用这个例子来说明,真正的财富积累,靠

By laoliu
曼联再次输球

曼联再次输球

不出所料,曼联又输了,输给了同城死敌曼城,这结果一点都不让人感到意外,所以也就没有了什么不好的情绪。如今的曼联,用“每况愈下”这个词来形容,简直再贴切不过了。 每年夏天,曼联都像个豪气的冤大头,挥舞着钞票在转会市场上一掷千金。买回来的球员的表现确都不如预期。今年,他们终于改变了买人的策略,不再只盯着那些潜力新人,而是把钱砸向了联赛中的“即战力”。即战力来是来了,但是花费了2亿英镑买的即战力,也不会进球了。 机会是有,但球就是不进。据统计,开赛以来曼联的射门次数达到了68次,英超各队第一;但是进球才进了4个,还有两个乌龙球。 然而反观那些曾经在曼联被嫌弃、被放弃的球员,在别的球队却像是被施了魔法,个个大放异彩。你看看这份名单: * 拉什福德:本场比赛一个助攻,曾经的“曼联太子”在巴塞罗那找到春天。 * 霍伊伦:打进了一个球,那个在曼联被诟病“不会跑位”的前锋,跑位变得无比机敏,射门也准了。 * 加纳乔:送出助攻,天赋在别队彻底兑现。 * 麦克托米奈:

By laoliu
解决腾讯 EdgeOne 域名解析与 SSL 证书申请失败问题

解决腾讯 EdgeOne 域名解析与 SSL 证书申请失败问题

前几个月,我关注到腾讯新推出的 EdgeOne 服务。这款产品无需备案和实名,而且提供免费额度,对于个人网站来说非常友好。刚发布时兑换码一码难求,我并没有太多关注。后来,腾讯推出了一个活动,只要在 X(原 Twitter)上转发推文,就能获得两个兑换码。我成功领取并兑换了一个,并将其用于我的“镜缘轩”网站。 我的“镜缘轩”网站部署在 CloudCone 的 VPS 上,访问速度一直不尽如人意。接入 EdgeOne 后,网站速度有了显著提升,这让我决定将我的博客也套上这个服务。 域名解析遇到的第一个问题:CNAME 扁平化 在将博客主域名iliu.org 通过 CNAME 解析到 EdgeOne 提供的地址后,我发现 EdgeOne 无法识别解析成功。奇怪的是,“镜缘轩”网站的解析过程却非常顺利。

By laoliu