ottijp blog

はてなブログからGatsby.jsで作った静的サイトへの記事の移行

ブログを独自ドメインで運用したかったのと,自分の資産として管理したかったので, はてなブログからGatsby.js + Netlifyで作る静的サイトへお引越ししました. (資産というほどの記事は書いていないですが・・・.)

その際,はてなブログからはMovableType形式(html)でしかエクスポートできないようだったので, gatsby-transformer-remark用のMarkdownに変換してディレクトリに格納するスクリプトを書いて移行しました.

ディレクトリ構成

移行後のGatsbyのディレクトリ構成はこんな感じです.

content/blog
├── 2017
│   ├── 03
│   │   └── 02
│   │       └── hello-blog
│   │           └── index.md
│   ├── 10
│   │   ├── 21
│   │   │   └── ios-reject-conf2017-day2
│   │   │       └── index.md

記事ファイル

移行後の記事ファイルはこんな感じです.

content/blog/2017/10/21/ios-reject-conf2017-day2/index.md
---
title: iOSDC 2017 Reject Conference day2 に参加しました
date: 2017-10-21 13:41:26
tags:
  - iOS
  - Apple
  - Swift
  - iOSDC
---

今年のiOSDC2017に初めて参加してとても勉強になったので,ぜひReject
Conferenceも参加しようと思ってたんですが,気づいたらすでにday1は終わっており・・・.

スクリプト

ワンタイムで実行する自分用のスクリプトなので,そのままは使えないかもしれませんが,参考まで.

やっていること

  • 記事ごとのファイル分割
  • タイトル,日時,タグ(カテゴリ)の取得と,frontmatter(gatsby-transformer-remarkが処理する記事のメタデータ)へのセット
  • ステータスがPublishの記事をベースネームに相当するディレクトリへ出力
  • pandocでhtmlをMarkdownに変換
  • はてなブックマーク用のリンク削除

やっていないこと

  • 抜粋文の移行
  • 途中で改行されているはてなブックマーク用のリンク削除

はてなブログで[App Store](http://d.hatena.ne.jp/keyword/App%20Store)のように空白を含むリンクを書いていた箇所について,AppStoreの間に改行が含まれたhtmlで出力されてしまうようでした. sedで変換が難しかったので,変換結果の確認がてら,変換後のMarkdownを手直ししていきましたが,記事数が多いと大変だと思うので,その場合はスクリプトで改行対応したほうが良いと思います.

実行環境

  • Mac: 10.14
  • pandoc: 2.4
  • はてなブログをエクスポートした日: 2019年9月25日

実行方法

$ ./migration.sh ottijp.hatenablog.com.export.txt output

はてなブログからエクスポートしたファイルと,変換結果の出力先を引数に渡します.

スクリプト

こんな感じです. ファイルの分割処理は自前でやってしまいましたが,csplitコマンドとか使えばよかったかもしれません.

migration.sh
#!/bin/bash

output_dir=$2

init() {
  title=
  date=
  basename=
  status=
  tags=()
  body=()
  excerpt=()
  inBody=false
  inExceprt=false
  inComment=false
}

parseHeader() {
  # タイトルの特定
  temp=$(echo "$1" | grep "^TITLE: " | sed 's/TITLE: \(.*\)/\1/')
  if [ -n "$temp" ]; then
    title=$temp
    return
  fi
  # 日時の特定
  temp=$(echo "$1" | grep "^DATE: " | sed 's/DATE: \(.*\)/\1/')
  if [ -n "$temp" ]; then
    # YYYY-MM-DD HH:mm:ss形式に変換
    date=`echo $temp | sed -E 's|([0-9]{2})/([0-9]{2})/([0-9]{4})(.*)|\3-\1-\2\4|'`
    return
  fi
  # ベースネームの特定
  temp=$(echo "$1" | grep "^BASENAME: " | sed 's/BASENAME: \(.*\)/\1/')
  if [ -n "$temp" ]; then
    basename="$temp"
    return
  fi
  # ステータス
  temp=$(echo "$1" | grep "^STATUS: " | sed 's/STATUS: \(.*\)/\1/')
  if [ -n "$temp" ]; then
    status="$temp"
    return
  fi
  # タグ(カテゴリ)
  temp=$(echo "$1" | grep "^CATEGORY: " | sed 's/CATEGORY: \(.*\)/\1/')
  if [ -n "$temp" ]; then
    tags+=("$temp")
    return
  fi
}

exportFile() {
  [ $status != 'Publish' ] && return

  local dirpath="$output_dir/$basename"
  local fpath="$dirpath/index.md"

  mkdir -p "$dirpath"

  # frontmatterヘッダを出力
  cat <<-EOS > "$fpath"
---
title: $title
date: $date
tags:
`for e in ${tags[@]}; do echo "  - ${e}"; let i++; done`
---

EOS

  # markdownに変換し,はてなキーワードのリンクを消して出力
  i=0
  for e in ${body[@]}; do
    echo "${e}"
    let i++
  done | pandoc -f html -t gfm | sed -E 's|<a href="https?://d.hatena.ne.jp/keyword/[^>]+>([^<]+)<\/a>|\1|g' | sed -E 's|\[([^]]*)\]\(https?://d.hatena.ne.jp/keyword/[^)]*\)|\1|g' >> "$fpath"
}

init
cat $1 | while read line
do
  if $inBody; then
    if [ "$line" = "-----" ]; then
      inBody=false
    else
      # body出力
      body+=($line)
    fi
  elif $inExceprt; then
    if [ "$line" = "-----" ]; then
      inExceprt=false
    else
      # exceprt出力
      excerpt+=($line)
    fi
  elif $inComment; then
    if [ "$line" = "-----" ]; then
      inComment=false
    fi
  else
    if [ "$line" = "--------" ]; then
      # 1つの記事が終了
      echo title: $title
      echo date: $date
      echo basename: $basename
      echo status: $status
      echo tags: `echo "${tags[@]}" | tr ' ' ','`
      echo 本文: ${#body[@]}echo 抜粋: ${#excerpt[@]}echo -----------------------------

      exportFile
      init
    elif [ "$line" = "BODY:" ]; then
      # 本文の開始
      inBody=true
    elif [ "$line" = "EXCERPT:" ]; then
      # excerptの開始
      inExceprt=true
    elif [ "$line" = "COMMENT:" ]; then
      # commentの開始
      inComment=true
    else
      # ヘッダのパージング
      parseHeader "$line"
    fi
  fi
done

ottijp
都内でアプリケーションエンジニアをしています
© 2024, ottijp