Firefox の places.sqlite からブックマークをとりだす

新規インストールしたマシンに旧マシンの Firefox のブックマークを移行しようと思いました。

で,どうせ bookmarks.html でしょと思ってみてみたら,どうも内容が乏しい。

調べてみたら Firefox 3 では places.sqlite というファイルにブックマークはじめアクセスした URL を記録しているみたいですね。

さいわいプロファイルフォルダをまるごととってあったので places.sqlite を上書きしようかと思ったんですが,そうすると新マシンでとったブックマークがなくなってしまうのでちょっと困ります。

なので places.sqlite の構造をみてみました。

% sqlite3 places.sqlite
SQLite version 3.5.9
Enter ".help" for instructions

sqlite> .tables
moz_anno_attributes  moz_favicons         moz_keywords       
moz_annos            moz_historyvisits    moz_places         
moz_bookmarks        moz_inputhistory   
moz_bookmarks_roots  moz_items_annos    

moz_bookmarks というテーブルがあやしい。

sqlite> .headers on

sqlite> SELECT * FROM moz_bookmarks;

id|type|fk|parent|position|title|keyword_id|folder_type|dateAdded|lastModified
1|2||0|0||||1209528568635986|1219119864784935
2|2||1|0|ブックマークメニュー|||1209528568636177|1228180489548243
3|2||1|1|ブックマークツールバー|||1209528568636276|1225854151400802
4|2||1|2|タグ|||1209528568636372|1209528568636992
5|2||1|3|未整理のブックマーク|||1209528568636468|1224748978301927

...... snip snip snip ......

60|1|44|3|2|Google|0||1209530922956848|1212462725554602

...... snip snip snip ......

ブックマークのタイトルはとれてますが,URL がみあたりません。

moz_bookmarks_roots テーブルをみてみると,

sqlite> SELECT * FROM moz_bookmarks_roots;

root_name|folder_id
places|1
menu|2
toolbar|3
tags|4
unfiled|5

こちらではなかったみたい。これは名前のとおり,ブックマークツリーのルートみたいですね。

では,general な名前の moz_places をみてみると,

sqlite> SELECT * FROM moz_places LIMIT 5;

id|url|title|rev_host|visit_count|hidden|typed|favicon_id|frecency
1|place:queryType=0&sort=8&maxResults=10|queryType=0&sort=8
  &maxResults=10||0|1|0||0
2|place:folder=BOOKMARKS_MENU&folder=UNFILED_BOOKMARKS
  &folder=TOOLBAR&queryType=1&sort=12
  &excludeItemIfParentHasAnnotation=livemark%2FfeedURI&maxResults=10
  &excludeQueries=1|folder=BOOKMARKS_MENU&folder=UNFILED_BOOKMARKS
  &folder=TOOLBAR&queryType=1&sort=12
  &excludeItemIfParentHasAnnotation=livemark%2FfeedURI&maxResults=10
  &excludeQueries=1||0|1|0||0
3|place:type=6&sort=14&maxResults=10|type=6&sort=14&maxResults=10||0|1|0||0
4|https://en-us.add-ons.mozilla.com/en-US/firefox/bookmarks/
 |/en-US/firefox/bookmarks/|moc.allizom.sno-dda.su-ne.|0|0|0|1|140
5|http://en-us.www.mozilla.com/en-US/firefox/central/
 |/en-US/firefox/central/|moc.allizom.www.su-ne.|0|0|0|2|140

こっちに URL が格納されているみたい。こちらにも title がありますが,こちらはもともとの title なのかな。

で,当て推量してると moz_bookmarksfk フィールドと moz_placesid フィールドが対応しているみたい((.schema コマンドでスキーマをみてみましたけど,トリガ等で明示的にリレーションが設定されてはなかったです。))。

なので,結合して表示してみましょう。

sqlite> SELECT moz_places.url, moz_bookmarks.title
   ...> FROM moz_bookmarks, moz_places
   ...> WHERE moz_bookmarks.fk IS NOT NULL
   ...>   AND moz_bookmarks.fk = moz_places.id
   ...>   AND moz_places.url NOT LIKE 'place:%'
   ...> ORDER BY moz_bookmarks.id;

url|title
https://en-us.add-ons.mozilla.com/en-US/firefox/bookmarks/|Get Bookmark Add-ons
http://en-us.www.mozilla.com/en-US/firefox/central/|Getting Started
http://en-us.www.mozilla.com/en-US/firefox/help/|Help and Tutorials
http://en-us.www.mozilla.com/en-US/firefox/customize/|Customize Firefox
http://en-us.www.mozilla.com/en-US/firefox/community/|Get Involved
http://en-us.www.mozilla.com/en-US/firefox/about/|About Us
http://www.google.co.jp/|Google

おー。いい感じです。


じゃあこっからエクスポートするスクリプトを書いてみる。

#!/usr/bin/perl

use strict;
use warnings;
use DBI;

my $file = shift;
if (! defined $file) {
    die "database places.sqlite not specified";
}

my $dbh = DBI->connect("dbi:SQLite:dbname=${file}")
    or die $DBI::errstr;

my $sth = $dbh->prepare(<<'END_SQL')
    SELECT moz_places.url, moz_bookmarks.title
      FROM moz_bookmarks, moz_places
     WHERE moz_bookmarks.fk IS NOT NULL
       AND moz_bookmarks.fk = moz_places.id
       AND moz_places.url NOT LIKE ?
     ORDER BY moz_bookmarks.id;
END_SQL
    or die $dbh->errstr;

$sth->execute('place:%')
    or die $dbh->errstr;

my @items;
while (my $rec = $sth->fetchrow_hashref()) {
    #use YAML;
    #print Dump($rec);

    my ($url, $title) = map { $rec->{$_} } qw( url title );

    for ($url) {
        s'"'&quot;'go;
    }
    for ($title) {
        s'&'&amp;'go;
        s'<'&lt;'go;   s'>'&gt'go;
    }

    push @items, <<"END_ITEM";
    <DT><A HREF="${url}">${title}</A>
END_ITEM
}

if (@items) {
    my $items = join q{}, @items;

    print <<"END_HTML";
<!DOCTYPE NETSCAPE-Bookmark-file-1>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>

<DL><p>
${items}
</DL><p>
END_HTML
}

% perl bookmark.pl $(BOOKMARKPROFILE)/places.sqlite

実行すると,

<!DOCTYPE NETSCAPE-Bookmark-file-1>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>

<DL><p>
    <DT><A HREF="https://en-us.add-ons.mozilla.com/en-US/firefox/bookmarks/">Get
 Bookmark Add-ons</A>
    <!-- snip snip snip -->
</DL><p>

うまくできました。

あとはこのファイルから不要な項目を削って「ブックマークの管理」から「HTML からインポート」すればいいはず。


ほんとうはブックマークフォルダの階層構造もとれるんでしょうけど,面倒だしそこまでもとめていない*1のでこれでいいやと思いました。


あと,favicon もとろうと思えばとれますが,bookmarks.html の場合,内部にアイコンがうめこまれているのに対し,places.sqlite の場合,moz_favicons テーブルからアイコンの URL を取得してブラウザキャッシュのアイコンファイルを利用しているのではないかなー。

*1:おおかたのブックマークは Gmarks にまかせているので,メインのブックマークはそんなにないのです。