Skip to main content
  1. Posts/
  2. 2026/

Migrating to Hugo (from Blogger)

·186 words·1 min·
Maciej Kalisiak
Author
Maciej Kalisiak

Ooof, that was rough. Blogger underwent many migrations since I used it last, and whatever migration tooling people have come up with in the intervening years, all are now broken, unsurprisingly. After much Gemini poking, back-and-forth, we arrived at this, which “works” (reads atom.feed):

# blogger_to_hugo.py
import feedparser
from markdownify import markdownify as md
import os
import re
from datetime import datetime

# CONFIG
ATOM_FILE = "feed.atom" # Point this to your file
OUTPUT_DIR = "content/posts"

os.makedirs(OUTPUT_DIR, exist_ok=True)
feed = feedparser.parse(ATOM_FILE)

def slugify(text):
    return re.sub(r'[\W_]+', '-', text.lower()).strip('-')

for entry in feed.entries:
    # Filter for actual blog posts (Blogger feeds include CSS and settings entries)
    if not hasattr(entry, 'content') or 'post' not in entry.id:
        continue

    title = entry.title
    slug = slugify(title) or f"post-{entry.id.split('-')[-1]}"
    date = entry.published # Standard ISO format usually
    content_html = entry.content[0].value

    # Convert HTML to Markdown
    markdown_body = md(content_html, heading_style="ATX")

    with open(f"{OUTPUT_DIR}/{slug}.md", "w") as f:
        f.write("---\n")
        f.write(f"title: \"{title}\"\n")
        f.write(f"date: {date}\n")
        f.write("draft: false\n")
        f.write("---\n\n")
        f.write(markdown_body)

print(f"Success: Processed {len(feed.entries)} entries into {OUTPUT_DIR}/")

Invocation:

uv run \
  --python 3.10 \
  --with feedparser \
  --with markdownify \
  python blogger_to_hugo.py

(you might still need to pip install some dependencies…)