WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

MyWeb から Yahoo! ブックマークに移行しました

先日の記事でお伝えしたとおり、これまで今週の話題でご紹介したネタを整理するのに使っていたソーシャルブックマーク・サービス Yahoo! MyWeb が 3 月で終了することになってしまいました。また、 MyWeb の前に使っていた Google Notebook も開発が終了していつ閉じられてもおかしくない状態です(トホホ)。

このままではせっかく 3 年かけて積み上げてきたデータが、水泡ならぬデータセンターの廃熱に帰してしまうので、日曜日に丸一日かけて Yahoo! Japan の Yahoo! ブックマークに全データを移しました。結果的にレスポンスが向上し、 UI も日本語になったので良かったなと思っています。やはり米 Yahoo! と違って、日本の Yahoo! は安心感がありますね(笑)。ついでに今週の話題のインデックスページに主なカテゴリのリンクも加えました。使える時間も限られているので見た目はイマイチですが、少しは使いやすくなったのではないでしょうか。

本日は、前述のデータ移行の方法と、それに使った Ruby スクリプトなどをご紹介します。あまりいないかもしれませんが、 MyWebGoogle Notebook から Yahoo! ブックマークに移行しようとしている方の参考になればと思います。

MyWeb からの以降

まずは MyWeb からの移行です。 MyWeb のエクスポート機能はダウンロードしても空の HTML が出てくるだけなので(ォィ)、 MyWeb と共通のバックエンドを利用している米 Yahoo! の Yahoo! Bookmarks からエクスポートしました。 XML 形式ならタグの情報も正しくエクスポートできます。

そして、エクスポートした XML を以下のスクリプトで Delicious API と同じ形式に変換し、 Yahoo! ブックマークのインポート機能でインポートすれば完了です。

#! /usr/bin/ruby
# -*- coding: utf-8 -*-
 
KCODE='UTF8'
 
require 'rexml/document'
require 'time'
 
exported_data = IO.read('myweb.xml')
 
exported_data.gsub!(/<URL>([^<]*)<\/URL>/u) do
  url = $1.gsub(/&(\w+)(?!;)/u, '&amp;\1').gsub(/&\z/u,'')
  "<URL>#{url}</URL>"
end
 
entities = {
  "alpha"  => 'α',
  "beta"   => 'β',
  "Sigma"  => 'Σ',
  "omega"  => 'ω',
  "acute"  => '´',
  "rarr"   => '→',
  "phi"    => 'φ',
  "times"  => '×',
  "euml"   => 'ë',
  "hellip" => '…',
  "hArr"   => '⇔',
  "forall" => '∀'
}
 
exported_data.gsub!(/&(\w+);/u) do |match|
  entity = $1
  entities[entity] || match
end
 
bookmarks = []
doc = REXML::Document.new(exported_data)
doc.elements.each('MYWEBBOOKMARKS/FOLDER/ITEMS/BOOKMARK')  do |entry|
  bookmarks << {
    :title   => entry.elements['TITLE'].text.strip,
    :url     => entry.elements['URL'].text.strip,
    :date    => Time.at(entry.elements['ADD_DATE'].text.strip.to_i).getutc,
    :comment => entry.elements['NOTE'].text,
    :tags    => entry.elements.to_a('TAGS/TAG').map{|e| e.text.strip }
  }
end
bookmarks.sort!{|a, b| (a[:date] <=> b[:date]) * -1 }
 
block      = 0
BLOCK_SIZE = 500
while bookmarks.size > block * BLOCK_SIZE
  doc = REXML::Document.new('<posts />')
  bookmarks[block*BLOCK_SIZE, BLOCK_SIZE].each do |bookmark|
    e = REXML::Element.new('post')
    e.attributes['description'] = bookmark[:title]
    e.attributes['href']        = bookmark[:url]
    e.attributes['time']        = bookmark[:date].xmlschema
    e.attributes['extended']    = bookmark[:comment]
    e.attributes['tag']         = bookmark[:tags].join(' ')
    doc.root.elements << e
  end
 
  File.open('myweb_output%02d.xml' % block, 'w') do |file|
    doc.write(file, 2)
  end
 
  block += 1
end

カレントディレクトリの "myweb.xml" からエクスポートデータを読み込み、変換結果を 500 件ごとに "myweb_output??.xml" というファイル名で出力します。手抜きですいません。

XML の形式を変換しているだけにしてはスクリプトが長いですが、以下の処理をしています。

  1. URL の <, >, &, " が実体参照になっていなくて REXML がエラーを吐くので、実体参照に変換。
  2. コメント中の一部の全角記号が HTML4 の実体参照に変換されていたので、それを通常の文字に戻している。でも、インポートしたらけっきょく実体参照化されてしまったので、あまり意味ないかも。
  3. 件数が多すぎると Yahoo! ブックマークへのインポートがエラーで止まってしまうので、 500 件ごとに分割。

2, 3 はまだしも、 1 みたいなバグが残っているのはちょっとカッコわるいですよね。エクスポートした XML を一回でも検証すればわかるはずなんですが・・・。ちなみに、日本の Yahoo! ブックマークではきちんと修正(CDATA になっている)されてました。さすがです。

Google Notebook からの移行

次は Google Notebook からの移行です。最初は API を使ってデータをエクスポートしようとしたのですが、どうも 100 件までしかデータが取得できないみたいです。仕方ないので、次善の策として Google Notebook のページの下のほうにある「エクスポート」のリンクでノートブックごとにエクスポートしました。こちらは Atom フィードの形式で全件を取得できます。

あとは、それらの XML をカレントディレクトリの "notebooks" の下に保存し、 MyWeb と同様にスクリプトで Delicious API 形式に変換しました。

#! /usr/bin/ruby
# -*- coding: utf-8 -*-
 
KCODE='UTF8'
 
require 'rexml/document'
require 'time'
 
bookmarks = []
 
Dir.glob('notebooks/*.xml') do |fname|
  xml = IO.read(fname).gsub(/&(\w+);/u) do |match|
    entity = $1
    entities[entity] || match
  end
  doc = REXML::Document.new(xml)
  notebook_title = doc.elements['feed/title'].text
  notes = []
  doc.elements.each('feed/entry') do |entry|
    if entry.elements['link']
      bookmark = {
        :title   => entry.elements['title'].text.strip,
        :date    => Time.xmlschema(entry.elements['updated'].text.strip),
        :comment => entry.elements['content'].text.strip,
        :tags    => ['gn', notebook_title]
      }
      bookmark[:comment] = bookmark[:comment].gsub(/<br>/u, "\n").gsub(/<[^>]*>/u, '').strip
      entry.elements.each('link') do |link|
        bookmark[:url] = link.attributes['href'] if link.attributes['rel'] == 'related'
      end
      entry.elements.each('category') do |category|
        if category.attributes['scheme'] == 'http://schemas.google.com/notebook/gdata/2007/section'
          bookmark[:tags] << category.attributes['label']
        end
      end
      bookmark[:tags].map!{|t| t.gsub(/\s/u, '_') }
      notes << bookmark if bookmark[:url] && !bookmark[:url].empty?
    end
  end
  bookmarks += notes
  $stdout << "#{notebook_title} : #{notes.size}\n"
end
bookmarks.sort!{|a, b| (a[:date] <=> b[:date]) * -1 }
 
doc = REXML::Document.new('<posts />')
bookmarks.each do |bookmark|
  e = REXML::Element.new('post')
  e.attributes['description'] = bookmark[:title]
  e.attributes['href']        = bookmark[:url]
  e.attributes['time']        = bookmark[:date].xmlschema
  e.attributes['extended']    = bookmark[:comment]
  e.attributes['tag']         = bookmark[:tags].join(' ')
  doc.root.elements << e
end
 
File.open('notebook_output.xml', 'w') do |file|
  doc.write(file, 2)
end

変換結果は "notebook_output.xml" という名前に出力されます。こちらは件数が少なかったので、分割はしていません。また、ノートブック名とセクション名をタグに変換しています。

Web サービスを使い始める際は、エクスポート機能をチェック!

というわけで、なんとか全データを Yahoo! ブックマークに移行できました。やれやれです。

今回のことで痛感したのが、エクスポート機能の重要性です。 Web 上のサービスは運営側の都合でいつシャットダウンされるかわかりませんし、サービスが終了したら最後、自分が数年かけて蓄えてきたデータがすべて失われてしまいます。そんなハメに陥らないよう、 Web サービスを使い始める際には、必要なデータがきちんとエクスポートできることを確認しておくことが大事ですね。とくに Atom などの標準的なフォーマットでエクスポートできれば、他のサービスに移行するのも簡単です。

逆に言えば、自分でサービスを作る際にもエクスポート機能は必須ということですね。作る側の心理として他のサービスへの移行に障害を設けてしまいがちですが、それではユーザーに安心してサービスを利用してもらうことができないということ。できるだけ利便性の高いエクスポート機能を用意しておきたいものです。

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles