メインコンテンツへスキップ

Codexを使ってnoteの記事データを全件移行した方法と気をつけたこと

·3918 文字·8 分
mota
著者
mota
AI開発、個人開発、ベトナムでの仕事と暮らしについて書いています。

noteからエクスポートした記事を、自分のHugoブログへ移行しました。

対象には、公開記事だけで338件ありました。 画像も数百点あります。 Markdownへ変換して保存すれば終わると思っていましたが、実際には記事の選別、連載の統合、文章のリライト、画像の移管、URLの維持、SEOの検証が必要でした。

単純な一括変換では、公開できる状態になりません。

そこで今回は、リポジトリを操作できるCodexに移行作業を任せました。 最終的には324件分の内容を287本のHugo記事へ整理し、591点の画像をCloudflare R2へ移しています。

noteから記事データを出力する
#

記事データは、noteのエクスポート機能から用意しました。 noteへログインして「自分の記事」を開き、画面右上の「エクスポート」から処理を開始します。 完了後に届くメールからダウンロードページを開き、データを保存します。

画面操作とメール受信までの流れは、note公式ヘルプの「エクスポート機能の使い方」で確認できます。

Codexへ渡した指示
#

最初の依頼では、エクスポートの保存場所と、完成してほしい状態をまとめて伝えました。

noteのエクスポートファイルを、ほぼすべて記事として整えて公開したい。 愚痴だけの記事やネガティブな記事は、その要素を薄くしてリライトする。 シリーズは、一つにまとめたほうが読みやすければ統合する。 画像はR2へアップロードし、記事へURLを入れてビルド、デプロイする。

リポジトリ側のAGENTS.mdには、記事の保存形式、front matter、タグの一覧、画像の置き場所、デプロイ手順を書いてあります。 Codexは作業前にこのファイルと関連ドキュメントを読み、ブログ固有のルールへ合わせました。

依頼文が完成形を示し、AGENTS.mdが実装方法を補う分担です。 記事を追加するたびに同じ注意を伝え直さずに済みました。

移行前に決めた公開方針
#

最初に決めたのは、変換方法ではなく公開の基準でした。

エクスポートに含まれる文章は、書いた時期も目的も異なります。 現在も役立つ解説がある一方で、短い告知、当時の感情だけを書いた文章、根拠を確認し直せない調査記事もありました。

すべてをそのまま公開すると、記事数は増えてもブログ全体は読みにくくなります。 Codexには次の基準を渡しました。

  • 公開記事は原則として移行する
  • 短すぎる告知や画像だけの記事は除外する
  • 根拠を確認できない調査記事は公開しない
  • 同じテーマの連載は、続けて読めるなら一つへ統合する
  • 愚痴や自己否定が中心の記事は、出来事と判断を残して書き直す
  • タグは既存の分類へ寄せ、種類を増やしすぎない

この基準がないまま変換を始めると、途中で「この文章も本当に公開するのか」という判断が何度も発生します。 大量移行では、その小さな迷いが作業全体を止めます。

まず全件を棚卸しした
#

Codexは、noteのXMLと画像をいきなり記事へ変換せず、先に件数と状態を調べました。

棚卸しの結果は次のとおりです。

  • 公開投稿:338件
  • note上の下書き:34件
  • 内容を移行した公開投稿:324件
  • 公開対象から外した投稿:14件
  • 作成したHugo記事:287件
  • 統合した記事:7件
  • R2へ移した画像:591点

公開投稿数とHugo記事数が一致しないのは、連載を統合したためです。

たとえば、生成AIとGoogle Apps Scriptを使った業務効率化の3回連載は、一つのチュートリアルへまとめました。 同じ概念について書いた4本の記事や、細かな機能更新を知らせる短文も、重複する導入と告知を除いて一つにしています。

記事単位で機械的に移すより、読者が何を続けて読みたいかで単位を決めたほうが自然でした。

Hugoの記事へ変換した流れ
#

変換処理では、一時的なRubyスクリプトを作り、XMLからタイトル、公開日、本文、画像参照、noteの記事IDを取り出しました。 出力先は、Blowfishテーマで扱いやすいpage bundle形式です。

content/posts/<pillar>/<slug>/index.md

各記事には、最低限次のfront matterを持たせました。

---
title: "記事タイトル"
date: 2026-07-26
summary: "検索結果や記事一覧に表示する要約"
tags: ["生成AI", "個人開発"]
categories: ["技術"]
draft: false
---

本文の変換では、見出しを##から始め、独自の箇条書きをMarkdownへ直し、壊れたリンクやコードブロックも点検しました。 画像の代替テキストには記事タイトルや画像の役割を入れ、すべてが「画像」になる状態を避けています。

変換スクリプトは初期配置には便利でした。 ただし、リライト後にもう一度実行すると、人が整えた文章を上書きする危険があります。 そのため、移行後はスクリプトを残さず削除しました。

感情の強い記事をどう書き直したか
#

古い文章には、その時にしか書けなかった温度があります。 温度まで消すと、どの記事も同じ調子になります。

しかし、当時の怒りや落ち込みをそのまま公開すると、出来事より強い言葉だけが残ることもあります。 Codexには、元の経験を維持しながら、次の順番で組み直すように伝えました。

  1. 実際に起きたこと
  2. 当時どう受け取ったか
  3. 何を迷ったか
  4. 現在はどう判断しているか

特定の個人を責める記述、性的な推測、集団への強い一般化は削除しました。 自己否定も、反省に必要な範囲を越えている部分は薄くしています。

これは文章を無難にする作業ではありません。 感情の勢いに隠れていた経験と判断を、読者が追える形へ戻す作業でした。

画像はR2へ移してから参照を書き換えた
#

エクスポート画像をGitリポジトリへ入れると、リポジトリとデプロイ成果物が急に大きくなります。 今回はCloudflare R2のmy-blog-imagesバケットへアップロードしました。

保存先は、記事IDごとに分けています。

posts/<note-id>/<filename>

本文からは、カスタムドメインのURLを参照します。

![画像の内容を表す説明](https://images.tnihei.com/posts/<note-id>/<filename>)

アップロードが成功したというログだけでは、移行完了とは判断しませんでした。 591点すべての公開URLへアクセスし、取得できることを確認しています。

元のエクスポートを削除したのは、この確認とHugoの本番ビルドが終わった後です。

タグは記事ごとではなく全体で決めた
#

Codexが各記事を個別に判断すると、似た意味のタグが増えていきます。 そこで移行記事のタグを次の5種類へ寄せ、1記事あたり最大2個にしました。

  • 生成AI
  • 個人開発
  • ベトナム
  • 資格およびキャリア
  • 雑記

カテゴリーも既存の「技術」「雑記」「お知らせ」から選んでいます。

タグは記事の説明文ではありません。 関連記事を探すための分類なので、表現の細かさより、複数の記事を同じ軸で束ねられることを優先しました。

SEOは全記事の共通テンプレートで処理した
#

287本へ同じ設定を手作業で足すと、修正漏れが起きます。 SEOはHugoの共通テンプレートへ集約しました。

各記事では、次の情報を生成しています。

  • description
  • canonical URL
  • Open GraphとTwitter Card
  • BlogPosting形式のJSON-LD
  • パンくずの構造化データ
  • sitemap
  • 全公開記事のタイトル、URL、要約を載せたllms.txt

移行後は生成HTMLを走査し、既存記事を含む312本すべてに必要なメタデータがあるか確認しました。 JSON-LDはタグの存在だけでなく、JSONとして解析できることも検証しています。

この検査では、要約へ入っていた長いURLが途中で切れ、不完全なパーセントエンコーディングになってHugoのビルドを止める問題も見つかりました。 本文から自動生成した要約は便利ですが、公開用のsummaryとしてそのまま使えるとは限りません。

最後に確認した項目
#

公開前には、変換件数だけでなく次の項目を確認しました。

  • front matterの必須項目に欠落がない
  • 1記事あたりのタグ数が上限内に収まっている
  • 内部リンクの参照先が存在する
  • R2の画像URLがすべて取得できる
  • 下書きと元データがpublic/へ混入していない
  • JSON-LDが正しいJSONとして読める
  • sitemapとllms.txtに全公開記事が載っている
  • hugo --minify --cleanDestinationDirが成功する

その後に変更をコミットし、mainブランチへpushしました。 このブログでは、pushを起点にCloudflare Workersの自動ビルドとデプロイが動きます。

Codexに任せてよかった部分
#

今回の移行では、同じ形式の変換、数百ファイルの作成、画像URLの置換、メタデータ検査のような反復作業をCodexへ任せられました。 途中でビルドが失敗したときも、生成結果と設定を見比べながら原因を絞り込めます。

一方で、何を残し、何をまとめ、どの表現を書き直すかは、ブログの方針によって答えが変わります。 そこまでを「全件移行」という一つの指示へ押し込むと、記事数だけがそろった読みにくいサイトになっていたはずです。

大量の記事移行で先に用意すべきものは、変換コードより公開基準でした。 基準をCodexと共有できたことで、過去の記事を捨てずに、現在のブログとして読める形へ移せました。

移行後は、Hugoを過去記事の保管場所で終わらせず、これから書く文章も集まる場所にすることにしました。 その理由は、Hugoで個人ブログにすべてのアウトプットを統合することにしたに書いています。

関連記事