Excite ブログから Seesaa ブログへの移行:その2
次に、 Excite ブログのドキュメント構造を調べる。ドキュメント構造なんて言うと小難しいことをやりそうだけれど、単に HTML を、目で、パースしてみたにすぎない。この HTML から、各要素を抜き出して、 Seesaa ブログの(エクスポートしたデータの)書式に合わせて整えればよいのだけれど、 Excite ブログのそれは、ちょっと酷い内容。あまり長く眺めていると、気絶しそう。
一見、たくさんのスタイルの指定がしてあるので、データもそれなりに括られているのかと思われど、要素としては意識されておらず、 HTML デザインの延長として(だけに)スタイルを作っているように思われる。でも、ほかに手がかりもないのでそれを頼りにするしかない。 DIV タグと、その要素 class の値。でもそれらを取り出せば、記事の構成要素を抽出して行けそう。ということで、取り出してみる。
たとえば、記事のタイトルは、要素 class の値が "POST_TTL" の div タグで囲まれている中の、更に a タグ(要素 name を持つ)に挟まれている。またたとえば、筆者名、記事カテゴリや投稿日時などが、同様に "POST_TAIL" の中にある。そんな具合で、いちおう、すべてのデータ項目に目星はついた。
さて、そこまで見通しがついたらば、実践してみる。こうした問題にはそれこそ Perl の出番。とはいうものの Perl は久しぶりに扱うから、ちょっとカンが鈍っているようだ。
ページをまるごと、正規表現でごっそり、とも思ったけれどもそれはあまりにも酷な作業だし、そもそも発想がだらしない。そこで、 HTML をパースするには "HTML::Parser" かと、その使い方を調べていたら、 "HTML::TreeBuilder" が面白そうだった。マニュアルを見てみると... よくわからなかった。英語が不案内というのは言うまでもないけれど、ドキュメント自体が分りにくい。ような気がした。しかもメソッドが多いために、そのドキュメントは超大作だ。調べるのが途端に厭になった。──ということで、なにはともあれ書いてみることにする。がしがし突貫していけば、何かを掘り出すだろうと期待して。
"HTML::TreeBuilder" は、たとえばこんなふうに:
# sample.html を解析する
my $tree = HTML::TreeBuilder->new;
$tree->parse_file("sample.html");
# ページタイトルを取り出す
print $tree->find("title")->as_text;
# すべての div タグ(の構成ブロック)を取り出す
for my $attr ( $tree->tag("div") ){
print $attr->as_HTML;
}
# 要素 class に値 post をもつものを取り出す
for my $attr ( $tree->look_down("class", "post") ){
print $attr->as_HTML;
}
# リンクをぜんぶ
for my $attr ( $tree->look_down("href", qr{http://} ) ){
print $attr->as_text;
}
なんとなくそれっぽく使えた。なるほど、 look_down というメソッドがとても強力だ。これは便利。
Excite ブログの生成する HTML の気が利かないために、さすがに欲しいところだけを一発で取り出す事はできないけれど、これを使えば(ある程度大雑把に)抽出する事ができる。その先の細かい所は、正規表現でマッチさせていけばいいだろう。
ということで、プロトタイプとして、次のようなルーチンができた。
sub parse_html {
my $file = shift;
my $tree = HTML::TreeBuilder->new;
$tree->parse_file( $file );
# AUTHOR, CATEGORY, DATE を含むブロックを取り出す
for my $attr ( $tree->look_down("class", "POST_TAIL") ){
print $attr->as_HTML("¥t"), "¥n¥n";
}
# TITLE を含むブロックを取り出す
for my $attr ( $tree->look_down("class", "POST_TTL") ){
print $attr->as_HTML("¥t"), "¥n¥n";
}
# BODY, EXTENDED_BODY, EXCERPT を含むブロックを取り出す
for my $attr ( $tree->look_down("class", "POST_BODY") ){
print $attr->as_HTML("¥t"), "¥n¥n";
}
# COMMENTS/PINGS の情報を含むブロックを取り出す
for my $attr ( $tree->look_down("class", "COMMENT_TAIL") ){
print $attr->as_HTML("¥t"), "¥n";
}
# COMMENTS/PINGS の本文を含むブロックを取り出す
for my $attr ( $tree->look_down("class", "COMMENT_BODY") ){
print $attr->as_HTML("¥t"), "¥n";
}
# 次のページの URL を取り出す
for my $attr ( $tree->look_down("ALIGN", "LEFT") ){
if( my $nextpage = $attr->look_down("href", qr(^http://)) ){
print $nextpage->attr('href'), "¥n";
}
}
}
コメントとトラックバックの部分が、ちょっと面倒。なぜなら、どちらも class の値が "COMMENT_..." と表現されているから、コメントなのか、トラックバックなのかを内容から見分けないといけない。また、これらは複数あることがあるので、 "COMMENT_TAIL" に含まれる内容と、 "COMMENT_BODY" に含まれる内容とのペア組も意識しないといけない。
ただ、それは面倒なだけで、無理ではない。たとえば、トラックバックの場合は "Tracked from" でコメントの場合は "Commented by" というどちらかのテキストがあるかで判断に使える。汎用性が犠牲になるけれど、そもそも Excite ブログが汎用的なものでは、ないのだから、固有のテキストとのマッチングでも、この際なんでもいい。
あと、それから、実用するときには、すべての記事を取得したいのだけれども、すべての記事を取得するには、次のページへのリンクを辿ることにする。ひとつひとつ単純に、また確実にページを得て行くには都合がいいと思われる。そのポインタが、ページの下の方にあるので、これを利用する。ただ、一意にそこを指定できれば苦労もないのだけれども、その周辺のタグには特別な名前を持っているものがなかった。従って、上の例のように、段階的にトライしてみた。「次のページ」というテキストをマッチさせてもよいけれど、結局 href の値を取り出すのにまた HTML をパースしないといけないから、既にパースされた中から探せればそれに越したことはない。まあどっちでもいいか。これでなんとなく取れた、ということに過ぎないのだから、偉そうなことは言えない。
(つづく)