読者です 読者をやめる 読者になる 読者になる

yamamoWorks

.NET技術を中心に気まぐれに更新していきます

Bloggerからはてなブログに移行しました

お知らせ

Bloggerが使いにくかったので意を決して(?)はてなブログに移行しました。 その際のデータ移行手順をメモしておきます。

1. Bloggerの記事とコメントのデータをエクスポート
Blogger管理画面メニュー[設定]-[その他]を開き「コンテンツをバックアップ」ボタンを押してファイルをダウンロード。

2. データをWordPress形式に変換
はてなブログはMovableType形式かWordPress形式のデータしかインポートできないのでBloggerからエクスポートしたデータをgoogle-blog-converters-appengineを使用して変換します。
※MovableType形式ではコメントが取り込めなかったのでWordPress形式にしました
※このツールの実行にはPython 2.5.xが必要です。(2.7.xではエラーが発生します)
 
上記サイトからgoogle-blog-converters-r89.tar.gzをダウンロードして解凍し、コマンドプロンプトで解凍したフォルダ内のbinフォルダに移動してblogger2wordpress.batを実行。

R:\google-blog-converters-r89\bin\blogger2wordpress.bat {Bloggerからエクスポートしたファイル} > R:\wp.xml

3. 画像ファイルの移動
上記でWordPress形式に変換したファイルは既にはてなブログにインポートできる状態なのですが、記事で参照していた画像ファイルの実体はBloggerに残ったままなので、はてなフォトライフに画像ファイルを一括アップロードし参照パスを書き換えるプログラムを作成しました。
※下に超適当に作ったコードを晒しておきます

4. はてなブログにインポート
あとは変換したデータをはてなブログの管理画面よりインポートして完了。

 


記事で参照されているBlogger上の画像ファイルをはてなフォトライフにアップロードしパスを書き換えるサンプル

class Program
{
    static SHA1 sha1 = new SHA1CryptoServiceProvider();

    static HttpClient bloggerClient = new HttpClient();
    static HttpClient hatenaClient = new HttpClient();

    static void Main(string[] args)
    {
        File.WriteAllLines(
            "{書き換え後の保存パス}",
            File.ReadLines("{WordPress形式のデータファイルパス}").Select(s => Regex.Replace(s, @"http://[^/]+\.blogspot\.com/[^""]+", MatchEvaluator)));
    }

    static string MatchEvaluator(Match match)
    {
        try
        {
            var filename = match.Value.Split('/').Last();
            var data = Convert.ToBase64String(bloggerClient.GetByteArrayAsync(match.Value).Result);

            var xml = string.Format(
                "<entry xmlns=\"http://purl.org/atom/ns#\">" +
                "<title>{0}</title>" +
                "<dc:subject>Hatena Blog</dc:subject>" +
                "<content mode=\"base64\" type=\"{1}\">{2}</content>" +
                "</entry>"
                , filename, "image/" + Path.GetExtension(filename).Trim('.').ToLower(), data);

            hatenaClient.DefaultRequestHeaders.Clear();
            hatenaClient.DefaultRequestHeaders.Add("X-WSSE", CreateWSSEHeader());
            var res = hatenaClient.PostAsync("http://f.hatena.ne.jp/atom/post", new StringContent(xml, Encoding.UTF8)).Result;

            var text = res.Content.ReadAsStringAsync().Result;

            XNamespace hatena = "http://www.hatena.ne.jp/info/xmlns#";
            var url = XDocument.Parse(text).Descendants(hatena + "imageurl").First().Value;

            return url;
        }
        catch (Exception ex)
        {
            Console.WriteLine(match.Value);
            Console.WriteLine(ex.Message);
        }

        return match.Value;
    }

    static string CreateWSSEHeader()
    {
        var apiKey = "{はてなフォトライフのAPI Key}";
        var username = "{はてなフォトライフのユーザ名}";
        var nonce = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(DateTime.Now.ToString()));
        var created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");

        var list = new List<byte>();
        list.AddRange(nonce);
        list.AddRange(Encoding.UTF8.GetBytes(created));
        list.AddRange(Encoding.UTF8.GetBytes(apiKey));

        var passwordDigest = Convert.ToBase64String(sha1.ComputeHash(list.ToArray()));
        var enc_nonce = Convert.ToBase64String(nonce);

        return string.Format("UsernameToken Username=\"{0}\", PasswordDigest=\"{1}\", Nonce=\"{2}\", Created=\"{3}\"", username, passwordDigest, enc_nonce, created);
    }
}