give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

妻のパン屋のブログを自作ツールでエキサイトブログからTumblrに移行した話

はじめに

ご存知の方はご存知かと思いますが、僕の妻は兵庫県西脇市で「Coupé Baguette(クープバゲット)」という小さなパン屋さんを営んでいます。
妻は今までエキサイトブログで店のブログを書いていたのですが、「もっとシンプルでオシャレなデザインのブログに引っ越したい」ということで、先日Tumblrに移行しました。


妻は新しいブログサービスでブログが書ければそれでOKだったのですが、僕としては店のブログが新旧2箇所に分散するのが嫌だったので古い記事もTumblrに移行させました。
今回はこのブログ移行に関する話を書いてみます。


新ブログのURL: http://blog.coupe-baguette.com/

f:id:JunichiIto:20140922090152p:plain:w500


ブログデザインの新旧比較

最初にブログデザインの比較を載せておきます。


エキサイトブログを使っていたころのデザインはこんな感じです。

f:id:JunichiIto:20140922090134p:plain:w500


Tumblrではこんなデザインになりました。

f:id:JunichiIto:20140922090152p:plain:w500


スマホページも雰囲気が変わっています。
こちらがエキサイトブログ。

f:id:JunichiIto:20140922090311p:plain:w250


こちらがTumblrです。

f:id:JunichiIto:20140922090407p:plain:w250


自作したお引っ越しツールのデモ

このエントリの後半で紹介する「お引っ越しツール」のデモ動画です。
Tumblr APIやSelenium Webdriverを使ってTumblrやエキサイトブログを自動更新しています。
再生時間は6分45秒です。


新しいブログサービスの選定

それではここから本エントリの本編です。


ブログサービスを変えるにあたって、「もっとシンプルでオシャレなデザインのブログがほしい」という妻の意向はあったものの、次に使いたいブログサービスが決まっていたわけではありませんでした。


妻と僕が新しいブログサービスに求めることを列挙するとこんな感じになります。



  • シンプルでオシャレなデザインであること(そういうテーマがたくさんあること)


  • テンプレートHTML、CSS、JavaScriptを自由にいじれること
  • スマートフォン対応していること
  • 運用が楽なこと
  • SEOに強いこと(できれば)

 

WordPressは今回見送り

とりあえず、「シンプルでオシャレなテーマがたくさん揃ってそうなブログサービス」ということでぱっと思いついたのがWordPressです。
が、確かにオシャレなテーマはたくさんあるものの、周りの人に聞いてみると、「WordPressって意外とかゆいところに手が届かない」みたいな話をちょくちょく聞きました。


僕自身も調べてみましたが、ホスティング関連の費用が思った以上にかかるみたいだし、やりたいことを実現するのにたくさんプラグインを導入しなければいけない印象があったので、WordPressは今回見送ることにしました。


ノーマークだったけど、意外とイケてたTumblrに決定

「じゃあ、どこがいいかな~?はてなBlogもちょっと違うしな~。」とか思っていたところ、ソニックガーデン副社長の藤原さんにオススメされたのがTumblrです。
「Tumblrならデザイン性とカスタマイズ性が両立されてるよ」という話を聞いて調べてみたところ、確かにテンプレートもCSSもJSも好きにいじれてなかなかいい感じでした。


Tumblrは今まで実際に使ったことがなく、なんとなく「Twitter以上ブログ未満なwebサービスかな?」と思っていました。
しかし調べてみると、Tumblrをブログとして使っている人や企業は結構多いみたいです。


しかも、Tumblrは気持ち悪いぐらいに全部が無料!
これといった有料プランの設定も見当たらず、独自ドメインの設定ですら無料でできてしまいます。(はてなBlogは有料なのに!)


「Tumblrっていったいどうやって収益上げてるんだ?」という不可解さはありますが、とりあえず今はそんな話は置いといて、ありがたくTumblrを利用させてもらうことにしました。


続いて過去記事のお引っ越し

というわけで、独自ドメインの設定を終え、妻好みのテーマを選び、テンプレートHTMLに軽くカスタマイズを入れて、無事に新ブログでの初投稿が完了しました。
ここまではそれほど苦労することなく進みました。


さて、次にやることは旧ブログで書いていた昔の記事のお引っ越しです。
せっかく独自ドメインも設定したんだから、店のブログ記事は全部独自ドメイン以下に置いておきたいものです。
新旧の記事でURL(ドメイン)が変わっちゃうのもイヤですし。


というわけで、妻からリクエストされたわけでもないのに独断で昔の記事をTumblrへ移行させることにしました。


お引っ越しで実現したいこと

ブログのお引っ越しで実現したいのは以下の2点です。

  1. 写真と本文をエキサイトブログからTumblrにコピーする
  2. エキサイトブログ上の本文は削除して、「この記事はこちらに移動しました (URL)」メッセージを表示する


2については、欲を言えばエキサイトブログからTumblrへ301リダイレクト(恒久的な転送)を設定するのがベストなんですが、エキサイトブログにそんな高度な機能はありません。


また、記事をコピーしただけで終わらせてしまうと、Google先生にコピーサイトと見なされてペナルティを食らうらしいです。


なので、今回は「この記事はこちらに移動しました」メッセージをエキサイトブログ上に表示する、という方法を取ることにしました。


便利なAPIが用意されているとTumblrと、APIや移行ツールが全く存在しないエキサイトブログ

当然ですが、ワンクリックでエキサイトブログからTumblrへお引っ越しできるような便利ツールはTumblrにはありません。
なので、お引っ越しするためにはある程度自分の手を動かさなければなりません。


一番原始的な方法は全部手作業で移行させる、という方法ですが、エキサイトブログで書いていた記事は170本、ブログ中で使った画像は500点もあるので、全部手作業でやるのはさすがにうんざりです。


幸いなことに僕の本業はプログラマなので、「プログラムを組んで自動化させる」という「魔法」が使えます。


が、ブログにも自動化しやすいブログと自動化しにくいブログがあります。
ポイントはAPIの有無です。
ブログを操作できるAPIが用意されていれば自動化は比較的容易ですが、APIがなければ自動化に苦労します。


この点についてもTumblrは優秀で、ブログを操作するためのAPIがしっかり用意されています。


一方、エキサイトブログには予想通りというかなんというか、APIなんていう便利なものは用意されていません。
加えて過去記事のエクスポート機能も提供されていません。
RSSは一応用意されていますが、本文の長さや表示できる件数に制限があるため、過去記事を全件吸い上げるような用途には使えません。


というわけで、エキサイトブログの制約に対しては以下のように対応することにしました。

  • 過去記事の取得についてはWebスクレイピングで対応する
  • 「移動しました」の更新はSelenium Webdriverを使ってブラウザ操作を自動化する


API経由での操作に比べると面倒ですが、他に方法がないので仕方ありません。


お引っ越しツールの処理フロー

さて、それでは早速プログラムを書いていきましょう・・・と言いたいところですが、設計もせずにやみくもにプログラムを書いていくことはできません。


今回作るお引っ越しツールはだいたいこんな処理フローで作っていこうと考えました。

  1. Webスクレイピングでエキサイトブログ上の過去記事をデータベースに保存する
  2. 本文のテキストをパースして本文中で使われている画像のURLを抽出する
  3. ブログで使用している画像をTumblrにアップロードし、画像の新しいURLを取得する
  4. ブログ記事ごとに以下の処理を実行する
    • 過去記事の本文をTumblrに投稿する。その際、本文中の画像URLはTumblr上の画像URLに置き換える
    • エキサイトブログの編集画面を開いて「移動しました」メッセージと移転先のURLを更新する


ステップ4はTumblrへの投稿とエキサイトブログへの更新を同時に行うことで、コンテンツがネット上で重複しないように配慮しています。


お引っ越しツールの実装はRuby on Railsで

お引っ越しツールはRuby on Railsを使うことにしました。


もちろん、「データの引っ越し」という観点で見れば特にUIは必要ないのですが、データベースに保存したデータをブラウザ上で確認できたりするとちょっと便利です。
ただし、このwebアプリケーションを動作させるのはローカルの開発環境だけです。


あとは単純にRailsが使い慣れているから、という理由も大きいです。
ActiveRecordやActiveSupportの便利機能に慣れきっちゃってるので、とりあえずRailsで作っちゃうのが速いんですよね~。


というわけで、僕はRailsでこのツールを実装することにしました。


お引っ越しツールで活躍したgemたち

今回作ったお引っ越しツールでお世話になったgemは以下の通りです。

Nokogiri

Webスクレイピングや本文の置換等でHTMLをパースする必要があったので、定番のNokogiriを使いました。
とはいえ、僕はそれほどNokogiriやXPathに精通しているわけではないので、非効率な方法でデータを操作している可能性も高いです。

Tutorials - Nokogiri 鋸


Tumblr Ruby Gem(tumblr_client)

Tumblr APIを利用するためのgemです。
APIをさらにgemでラップしてくれると、Rubyから簡単にAPIを操作できて本当に便利です。

GitHub - tumblr/tumblr_client: A Ruby Wrapper for the Tumblr v2 API


Selenium Webdriver

前述の通りエキサイトブログにはAPIがないので、ブラウザ操作を自動化する必要があります。
そこで利用したのがSelenium Webdriverです。
Selenium Webdriverを使うとローカルマシンにインストールされたFirefoxを自動実行することができます。


Seleniumの代わりにPhantomJSのようなヘッドレスブラウザを使うこともできるかもしれませんが、今回は実際に動きを目で見て確認できた方がデバッグやトラブルシューティングが速くなるのではないかと考え、Seleniumを使うことにしました。

selenium-webdriver | RubyGems.org | your community gem host


お引っ越しツールのソースコード

お引っ越しツールのソースコードはGitHubに置いてあります。

GitHub - JunichiIto/excite-to-tumblr-for-cb


このツールのソースコードを全部載せようとするとかなりの量になってしまうので、面白そうなところだけをピックアップしてみます。

WebスクレイピングでエキサイトブログのHTMLをパースする部分

以下のread_postメソッドは、エキサイトブログの記事IDを受け取って記事のHTMLをパースし、BlogPostモデルと一つ前の記事のURLを一緒に返すメソッドです。

require 'open-uri'
require 'nokogiri'
class ExciteBlogClient
  # (省略)

  def read_post(excite_id)
    url = "http://#{Settings.excite.account_name}.exblog.jp/#{excite_id}/"
    doc = read_doc_from_url(url)
    node = doc.xpath('//div[@id="post"]').first

    title = node.xpath('//h2[@class="subj"]').text
    content_html = read_content_html(doc)
    old_page_url = doc.xpath('//a[@class="older_page"]').first.try(:[], 'href')
    old_page_id = old_page_url[/\d{8}/] if old_page_url.present?

    tail_node = doc.xpath('//div[@class="posttail"]').first
    posted_at = tail_node.text[/\d+-\d+-\d+ \d+:\d+/]

    tag_nodes = tail_node.search('a').select do |node|
      node['href'] =~ /\/i\d+\//
    end
    tag_list = tag_nodes.map(&:text).join(',')

    [BlogPost.new(title: title, posted_at: posted_at, excite_id: excite_id, content: content_html, tag_list: tag_list), old_page_id]
  end

  # (省略)

  def read_doc_from_url(url)
    logger.info "[INFO] Reading #{url}"

    charset = nil
    html = open(url) do |f|
      charset = f.charset
      f.read
    end
    Nokogiri::HTML.parse(html, nil, charset).tap do
      sleep SLEEP_SEC
    end
  end

  # (省略)
end


ただし、何が原因なのかよくわかりませんが、エキサイトブログの記事はどうもHTMLの構造がおかしいらしく、記事によってうまく全文が取れたり、一部だけしか取れなかったりするときがありました。
なので、以下のようなメソッドを用意して、むりやり(?)本文を丸ごと抽出できるようにしています。

  def read_content_html(doc)
    start_comment = doc.at("//comment()[contains(.,'interest_match_relevant_zone_start')]")
    return start_comment.parent.inner_html if start_comment.parent.inner_html =~ /interest_match_relevant_zone_end/

    # タグ構造がおかしい場合に対処する
    html_in_start_comment = ''
    if start_comment.parent.text.strip.present?
      html_in_start_comment = start_comment.parent.inner_html
    end
    content = Nokogiri::XML::NodeSet.new(doc)
    contained_node = start_comment.parent.next_sibling
    loop do
      break if contained_node.comment? && contained_node.text.strip == 'interest_match_relevant_zone_end'
      content << contained_node
      contained_node = contained_node.next_sibling
    end
    html_in_start_comment + content.to_html
  end

 

Tumblr APIでテキストを投稿する部分

本当は他のメソッドでいろいろやってるんですが、Tumblrへの投稿を実行している箇所だけを抜き出すとこんな感じです。
APIとgemがあれば投稿の処理自体はラクラクです。

class BlogPost < ActiveRecord::Base
  # (省略)

  def post_to_tumblr(excite_blog_writer)
    # (省略)

    result = tumblr_client.create_post :text, BLOG_NAME, tags: tag_list, date: date_param_for_tumblr, title: title, body: content_for_tumblr
    self.tumblr_id = result['id']

    # (省略)
  end

  # (省略)

  def tumblr_client
    @tumblr_client ||= Tumblr::Client.new
  end
end

 

Selenium Webdriverでエキサイトブログの記事を更新する部分

ブログ用にGitHub上のコードを少し改変していますが、Selenium Webdriverでブラウザを自動操作するコードはこんな感じになります。


過去にRSpec + CapybaraでSeleniumを使ったことはありましたが、Selenium単体で使ったのはこれが初めてだったので、使い方を調べるのにちょっと時間がかかりました。

class ExciteBlogWriter
  attr_reader :browser, :wait

  def initialize
    @browser = Selenium::WebDriver.for :firefox
    @wait = Selenium::WebDriver::Wait.new(:timeout => 15)
  end

  # (省略)

  def edit_content(excite_id, content)
    browser.get edit_url(excite_id)
    sleep 4 # 日付のselect boxが選択されるのを待つ

    input = wait.until {
      element = browser.find_element(:name, "content")
      element if element.displayed?
    }
    old_content = input.text
    input.clear
    input.send_keys content

    form = wait.until {
      element = browser.find_element(:name, "updateform")
      element if element.displayed?
    }

    form.find_element(:name, "submit_button").click
    sleep 4

    old_content
  end

  # (省略)

  def edit_url(excite_id)
    "http://www.exblog.jp/myblog/entry/edit/?eid=#{Settings.excite.eid}&srl=#{excite_id}"
  end

  # (省略)
end


こうした予備知識を仕入れた上で冒頭のデモ動画を見てみると、また新しい発見があるかもしれません。



お引っ越しツールの開発については他にもいろいろと苦労した点や、予期せぬ問題に遭遇してむりやり回避したところがありますが、全部書くと長くなってしまうので今回はここまでにしておきます。

まとめ

というわけで、今回は妻のパン屋のブログサービスをエキサイトブログからTumblrに移行したときの話をあれこれと書いてみました。


まあ、後半のお引っ越しツールの開発はぶっちゃけ自己満足な部分もあります。(苦笑)
「なんとか自動化できそうだ。よしやってみよう!」みたいなノリで開発に着手しました。


WebスクレイピングやTumblr API、Selenium Webdriverを使ったブラウザ操作の自動化等のサンプルコードは、こういったブログ移行ツール以外でも利用する機会があるかもしれません。
みなさんももし利用する機会があれば参考にしてみてください。


http://blog.coupe-baguette.com/

f:id:JunichiIto:20140922090152p:plain:w500


妻がやってるパン屋のWebサイトはこちら

兵庫県西脇市という田舎で毎週金曜日と土曜日だけ営業している小さなパン屋さんです。
ときどきオンライン販売もやっているので、遠方の方も良かったら利用してやってください。

Coupé Baguette(クープバゲット)

f:id:JunichiIto:20140922094506p:plain:w500


あわせて読みたい

Webサイトのデザインも僕が担当しています。
そろそろスマートフォン用のページも作りたいと考えている今日この頃。


リアルタイムな情報はFacebookページを使って発信しています。
Facebookページは今でも経営上欠かせないツールです。