🖊

個人ブログをZennに移行しました

2025/01/12に公開

はじめに

2021/06~2024/12の3年半ほど、ゆるーく個人のテックブログを運営していましたが、
年末年始に 個人ブログ(Wordpress)をたたみZennに記事を移行しました

テックブログに対して考えがどう変わっていったのかをポエム的に残してみたいと思います

そもそもなんで個人ブログを始めたの??

エンジニアになったからには、アウトプットとしてテックブログを書くことは決めてました

3つの理由からWordpressでブログを書くことに

  • 以前からWordpressを使って記事を書くことがあった。
  • SEOやデザイン、Wordpressの勉強ができれば。
  • 広告やアフィリエイトなどでお小遣い稼ぎにもなればなと。

個人ブログを運営してみて

維持費がつらくなりました
年間13,000円ぐらいかかっていて、それに見合った使い方ができていないかなと。

ブログを立ち上げた当初は10,000円ぐらいなら稼げるのでは??
と甘い考えをしていました。。。

具体的には
自分の場合は主にGoogle広告で、月5000PVで750-1,000円ぐらい。
維持費的にトントンか、ちょいマイナスぐらいですが、自分で管理していることを考えると。。。
定期的に記事を書かないとPV数が落ちてくるのでそれが大変。。。

テックブログのマネタイズが難しいのは

  • 読者がエンジニアになるのでネットリテラシー高め
  • 仕事のPCで見られる確立が高いので広告とかアフィリエイトを踏みにくい(気がする)
    • 平日の方がPVが高かった
  • アフィリエイトで還元率が高いものがない
    • 技術書とかは還元率低い
    • 転職やプログラミングスクールの還元率は高いが関連付けにくく、競合も強い
      みたいなのが個人的な感覚です

たとえ、マイナスになっていても、SEOなど他の勉強もできるのであればまあよしだったんですが、
そこまで手が回らず、デザインなども初期のまま放置状態

なので、このままだとただお金がかかるだけなのでZennに移行しようと決心。

個人ブログとZennのメリデメ比較

  • 個人ブログ
    • メリット
      • UIなど自分好みにできる
      • デザインやSEOのべんきょうにもなる
      • 広告やアフィリエイトなどマネタイズができる
    • デメリット
      • レンタルサーバやドメインなどの維持費がかかる
      • 自分好みにできる分、やることが多い
  • Zenn
    • メリット
      • 管理がいらない
        • 維持費もかからない
      • SEOが強く、ページを見てもらいやすい
        • 他の人からコメントもらえたりする
    • デメリット
      • アフィリエイトなどマネタイズがむずかしい
        • Zennなら有料記事などが作れる
      • デザインには凝れない

Qiitaではなく、Zennにした理由

移行先としてQiitaかZennのどちらかを検討しました

正直、差はないので悩みましたが、画像ファイルの管理しやすさ からZennにしました
(あとは、スクラップや本も使ってみたかったりします

とはいえ、転職系の記事など気が向いた時にはQiita(@hisui_hisui)を使ってます
(AdventCalenderにも参加してみたい

さいごに

個人ブログ(Wordpress)からZennに移行した考えを書いてみました

ありきたりですが、どちらがいいというわけではなく、自分に合ったものを選べばいいと思います

(余談) 記事の移行方法

Wordpressの記事をZennに移行した方法を公開します

Zennの記事はGitHubで管理しているので
Wordpress->Markdownへ変換してGitHubにあげることでZennに移行します

Wordpressの記事をMarkdownへ

↓の記事の通りに記事と画像を変換しました
https://www.webcreatorbox.com/blog/wordpress-to-md

Zennに投稿できるように手を加える

このままだと

  • ヘッダがZennに適してない
    • 絵文字は
  • 画像ファイルのパスが異なる
    • Wordpressにアップロードした画像ファイルの名前に日本語があれば英語に変換する必要もある
  • 無駄な空白行がある

ChatGPTに要件投げてPythonスクリプトを作ってもらいました

スクリプト

必要なライブラリ

requirements.txt
anyio==4.7.0
asyncio==3.4.3
beautifulsoup4==4.12.3
certifi==2024.12.14
exceptiongroup==1.2.2
googletrans==4.0.2
h11==0.14.0
h2==4.1.0
hpack==4.0.0
httpcore==1.0.7
httpx==0.28.1
hyperframe==6.0.1
idna==3.10
PyYAML==6.0.2
sniffio==1.3.1
soupsieve==2.6
tenacity==9.0.0
typing_extensions==4.12.2

スクリプト実行後、記事と画像が入っているフォルダを指定するだけ。

import asyncio
import os
import random
import re

import httpx
import yaml
from tenacity import retry, stop_after_attempt, wait_fixed

# 絵文字のリスト(ランダムに選択される)
EMOJIS = ["🚀", "🔥", "🌟", "💻", "📚", "⚙️", "🐍", "🎉"]

# ランダムな絵文字を選ぶ関数
def get_random_emoji():
    return random.choice(EMOJIS)

# 非同期でファイル名を翻訳する関数
@retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
async def translate_filename(filename):
    name, ext = os.path.splitext(filename)
    url = "https://translate.googleapis.com/translate_a/single"
    params = {
        "client": "gtx",
        "sl": "ja",
        "tl": "en",
        "dt": "t",
        "q": name
    }
    async with httpx.AsyncClient(timeout=15) as client:
        try:
            response = await client.get(url, params=params)
            response.raise_for_status()
            result = response.json()
            translated_name = result[0][0][0]
            return re.sub(r"\s+", "_", translated_name) + ext
        except (httpx.HTTPError, httpx.ConnectTimeout):
            print(f"Translation failed for: {filename}. Using original name.")
            return filename

# すべてのMarkdownファイルの画像参照を更新する関数
def update_image_references(folder_path, rename_map):
    for root, _, files in os.walk(folder_path):
        for file_name in files:
            if file_name.endswith(".md"):
                md_file = os.path.join(root, file_name)
                with open(md_file, "r", encoding="utf-8") as file:
                    content = file.read()

                for old_name, new_name in rename_map.items():
                    content = re.sub(rf'\b{re.escape(old_name)}\b', new_name, content)

                with open(md_file, "w", encoding="utf-8") as file:
                    file.write(content)

# 画像ファイルの名前を一括翻訳して変更する関数
def rename_images(folder_path):
    rename_map = {}

    async def process_images():
        for root, _, files in os.walk(folder_path):
            if "images" in root:
                for file_name in files:
                    old_name = file_name
                    full_old_path = os.path.join(root, file_name)
                    new_name = await translate_filename(file_name)
                    full_new_path = os.path.join(root, new_name)
                    os.rename(full_old_path, full_new_path)
                    rename_map[old_name] = new_name

    asyncio.run(process_images())
    return rename_map

# ヘッダ部分を変換する関数
def convert_markdown_header(input_file):
    with open(input_file, "r", encoding="utf-8") as file:
        lines = file.readlines()

    # ヘッダ部分を抽出
    header_lines = []
    content_lines = []
    in_header = False
    for line in lines:
        if line.strip() == "---":
            in_header = not in_header
            continue
        if in_header:
            header_lines.append(line)
        else:
            content_lines.append(line)

    # YAMLとして読み込み
    original_header = yaml.safe_load("\n".join(header_lines)) if header_lines else {}

    # 新しいヘッダを構築
    new_header = {
        "title": original_header.get("title", ""),
        "emoji": get_random_emoji(),
        "type": "tech",  # 技術記事に統一
        "topics": original_header.get("categories", []),
        "published": True,
    }

    # 本文の画像パスを置換
    content_lines = [line.replace("images/", "/images/") for line in content_lines]

    # 空白のみの行を削除(改行のみの行は保持)
    content_lines = [line for line in content_lines if line.strip() != "" or line == "\n"]

    # 新しいMarkdownを生成
    new_content = "---\n"
    new_content += yaml.dump(new_header, allow_unicode=True)
    new_content += "---\n"
    new_content += "".join(content_lines)

    # ファイルに書き込み
    with open(input_file, "w", encoding="utf-8") as file:
        file.write(new_content)

# 指定フォルダ内のすべてのMDファイルを処理
def process_markdown_files_in_folder(folder_path):
    # 画像ファイルを一括翻訳して名前変更
    rename_map = rename_images(folder_path)

    # Markdownファイル内の画像参照を更新
    update_image_references(folder_path, rename_map)

    # Markdownファイルのヘッダを変換
    for root, _, files in os.walk(folder_path):
        for file_name in files:
            if file_name.endswith(".md"):
                md_file = os.path.join(root, file_name)
                convert_markdown_header(md_file)

# メイン処理
if __name__ == "__main__":
    folder_path = input("Enter the folder path containing Markdown files: ")
    process_markdown_files_in_folder(folder_path)

このスクリプトでおおよそ変換できますが、
念のため一つ一つ記事を確認してZennにアップロードしました
(なんか画像名が上手く変換できてなかったり、Markdownの記法にあってなかったりとかありました)

Discussion