Kentaro Kuribayashi's blog

Software Engineering, Management, Books, and Daily Journal.

del.icio.us からはてなブックマークへデータを移行する

はてな各サービスの機能変更、お知らせなど - はてなブックマーク日記 - タグ機能の追加について」にてアナウンスされている通り、待望のタグ付け機能が追加され、また、なんか最近 del.icio.us が不安定なことがままあったりしたので、移行を考えてみたりしてます。そのためには、まずは del.icio.us のデータをはてなブックマークへ移行させることが必要。まぁ誰かすでに作っているとは思ったけど、どうせ数十分でできるだろうから探すよりさくっとスクリプトを書く方が早いだろう……と思いきや、以下に述べる通りなんかあれこれあって、途中で放棄したりしつつ、けっこう時間かかっちゃいましたよ……。

まず、del.icio.us API 用には Net::Delicious というモジュールがあるので、それ使えばそっこーぶっこ抜けるんだろうなってんで使ってみるも、なにこれ、データ取れないよ…? んー、使い方間違えてるのかな、追うのめどいな……っつーわけで投げた。まぁ、とりあえず全ぶっくま取得できればいいので、LWP::UserAgent でざくっと取ってくることに。

んでもって、取ってきたデータを、正規表現でさくっとやってもよさそうなものを XML::Simple でざっくりパース。とことんまで手を抜くのがモットーです!

そこから Atom の entry を作って、XML::Atom::Client を使ってはてなブックマーク AtomAPI にデータをひたすら投げつければ終了……のはずが、どうも utf8 文字列周りでエラる……。ソース見ても別に問題なさげな感じなのになぁ……とゆわけで、さらに追うのを諦め、汚いやり口で回避。そんなこんなでできたのが以下のスクリプト。

その後リリースされた XML::Atom-0.12 で、この問題は解消されています。

#/usr/bin/env perl

use strict;
use warnings;

use Encode;
use XML::Simple;
use XML::Atom::Client;
use XML::Atom::Entry;
use XML::Atom::Link;
use LWP::UserAgent;

my $del_user    = "del.icio.us のユーザ名";
my $del_pass    = "del.icio.us のパスワード";
my $hatena_user = "はてなのユーザ名";
my $hatena_pass = "はてなのパスワード";

my $del_api    = 'http://del.icio.us/api/posts/all';
my $hatena_api = 'http://b.hatena.ne.jp/atom/post';

my $del = LWP::UserAgent->new;
   $del->credentials(
       'del.icio.us:80',
       'del.icio.us API',
       $del_user,
       $del_pass,
   );

my $res   = $del->get($del_api);
my $posts = XML::Simple::XMLin($res->content, ForceArray => 1);

my $hatena = XML::Atom::Client->new;
   $hatena->username($hatena_user);
   $hatena->password($hatena_pass);

for my $post (reverse @{$posts->{post}}) {
    my $link = XML::Atom::Link->new;
       $link->href($post->{href});
       $link->rel('related');
       $link->type('text/html');

    my $tag = '';
       $tag = '[' . join('][', split(' ', $post->{tag})) . '] ' if $post->{tag};
    my $summary = $post->{extended} ? $tag . $post->{extended} : $tag;

    my $entry = XML::Atom::Entry->new;
       $entry->title('dummy');
       $entry->add_link($link);
       $entry->summary($summary);

    $hatena->createEntry2($hatena_api, $entry);
    print "posted: $post->{href}\n";
}

BEGIN {
    *XML::Atom::Client::createEntry2 = sub {
        my $client = shift;
        my($uri, $entry) = @_;
        return $client->error("Must pass a PostURI before posting")
            unless $uri;
        my $req = HTTP::Request->new(POST => $uri);
        $req->content_type('application/x.atom+xml');
        my $xml = $entry->as_xml;
           $xml = Encode::encode('utf8', $xml);
        $req->content_length(length $xml);
        $req->content($xml);
        my $res = $client->make_request($req);
        return $client->error("Error on POST $uri: " . $res->status_line)
            unless $res->code == 201;
        $res->header('Location') || 1;
    };
}

馬鹿みたいなことしてるなぁ。とはいえ一応目的は達成できるから、まぁいいや。ちなみに、はてなブックマークの仕様上、createEntry で title を登録することはできません。

PostURI による投稿時はブックマークのタイトルを編集することはできません。タイトルはlink要素に指定したURLから自動取得され設定されます。これは、はてなブックマークにおける各エントリーのタイトルが、全ユーザー共通であることを考慮し、タイトルを変更する場合はすでに設定されているタイトルを一度確認した上で編集を行っていただきたい故の仕様です。

はてなダイアリー - はてなブックマークAtomAPIとは

というわけで、del.icio.us に登録した時に title を実際のページの title から変更して登録していても、このスクリプトではそれが反映されないことになります(一度登録して、それをさらに編集するというプロセスを経ることによって、可能ではある)。仕様では、その辺りはちゃんと目視で確認してやれよ、つーことなので、こゆスクリプトで勝手に変更しちゃうのはマズいでしょう。

んで、データを移した結果が以下の通り。

最近、単に clip するだけでなく、なんでもいいからとりあえずコメントを書くようにしてるのですが、なんかはてなブックマークdel.icio.us に比べてちとばかしコメントの制限がキツイみたい……。ぶった切られまくりですよ><。

おまけ。実験の過程で必要だったので、はてなブックマークに登録したデータを全部一挙に削除しちゃうスクリプトも書きました。そっちもついでに貼っつけておきます。

#/usr/bin/env perl

use strict;
use warnings;

use XML::Atom::Client;

my $hatena_user = "はてなのユーザ名";
my $hatena_pass = "はてなのパスワード";
my $hatena_api  = 'http://b.hatena.ne.jp/atom/feed';

my $hatena = XML::Atom::Client->new;
   $hatena->username($hatena_user);
   $hatena->password($hatena_pass);

while (my $feed = $hatena->getFeed($hatena_api)) {
    for my $entry ($feed->entries) {
        for my $link ($entry->link) {
            $hatena->deleteEntry($link->href)
                if $link->rel eq 'service.edit';
        }
    }
}