地震とRSS

RSSを使うスクリプトを作ることを今年の目標に考えていましたが,やっとそのネタを思いつきました.地震情報 - goo天気で流れている地震情報を取得し,それをmixiに投稿します.やっていることは非常に単純で,http://weather.goo.ne.jp/earthquake/index.rdfをLWP::Simpleで取得.それをXML::XPathを使って必要な情報を抽出.そして,WWW::Mixiを使って日記として投稿します.スクリプトは以下のようになります.

#!/bin/perl
use XML::XPath;
use XML::XPath::XMLParser;
use WWW::Mixi;
use Storable qw(retrieve store);
use LWP::Simple;
use Encode qw(encode decode);
use strict;

my ($xml, $xp, $nodeset, $oldref, $file, %table);
my ($mixi, $login, $pass);
my ($res, %diary, %items, $text);

$file  = 'store';
$login = 'user@example.com';
$pass  = 'PASSWORD';

if (-r $file){
	$oldref = retrieve($file);
}else{
	%$oldref = ();
}

$xml = get("http://weather.goo.ne.jp/earthquake/index.rdf");
$xml = decode("euc-jp", $xml);
$xml =~ s/EUC-JP/UTF-8/m;

$xp = XML::XPath->new(xml => $xml);

$nodeset = $xp->find('/rss/channel/item');

for my $node ($nodeset->get_nodelist){
	my ($item, $url, $key, $title, $str);
	$item  = $node->find('./link');
	$url   = XML::XPath::Node::Text::string_value(($item->get_nodelist)[0]);
	$key   = $1 if ($url =~ /(\d+)/);
	$title = $node->find('./title');
	$str   = XML::XPath::Node::Text::string_value(($title->get_nodelist)[0]);
	$str   =~ s/^([^]]*)\]//;
	$str   =~ s/-.*$//;
	$table{$key} = $str;
}

for my $old (keys %$oldref){
	delete $table{$old} if (defined ($table{$old}));
}

exit if (scalar keys %table == 0);

$text = join "\n", map{$table{$_}}(sort keys %table);
$text = encode("euc-jp", $text);

$mixi = WWW::Mixi->new($login);
$res  = $mixi->login($pass);
$res->is_success || die $res->status_line;

%diary = (
	'diary_title' => 'Recent earth quake (Auto post)',
	'diary_body'  => $text,
);

%items = $mixi->get_add_diary_preview(%diary);
$diary{'post_key'} = $items{'post_key'};
%items = $mixi->get_add_diary_confirm(%diary);

store \%table, $file if (%items);

いくつかコツが必要だったので,その部分を解説しておきます.

if (-r $file){
	$oldref = retrieve($file);
}else{
	%$oldref = ();
}
...
store \%table, $file if (%items);

storableを使っていることに深い意味はありません.このスクリプトはcronを使って動かすことを考えたのでロックファイルを気にする必要がありません.そのためDBは使いませんでした.最後の"if (%items)"というのは,新規データがなければファイルの更新をしないことを意味しています.

$xml = get("http://weather.goo.ne.jp/earthquake/index.rdf");
$xml = decode("euc-jp", $xml);
$xml =~ s/EUC-JP/UTF-8/m;

元のrdfファイルの文字コードeuc-jpでした.perlでこれを処理するためには,encodingを"x-euc-jp-unicode"と指定してやる必要がありますが,XML::XPathXML::ParserではなくXML::Parser::Liteを継承しているため,"ProtocolEncoding"を使って文字コードを指定することができません.そのため,元のrdfファイルをutf8に変換し,さらに,文字コード指定をEUC-JPからUTF-8に変換することで対処しました.

$xp = XML::XPath->new(xml => $xml);
$nodeset = $xp->find('/rss/channel/item');
for my $node ($nodeset->get_nodelist){
	my ($item, $url, $key, $title, $str);
	$item  = $node->find('./link');
	$url   = XML::XPath::Node::Text::string_value(($item->get_nodelist)[0]);
	$key   = $1 if ($url =~ /(\d+)/);
	$title = $node->find('./title');
	$str   = XML::XPath::Node::Text::string_value(($title->get_nodelist)[0]);
	$str   =~ s/^([^]]*)\]//;
	$str   =~ s/-.*$//;
	$table{$key} = $str;
}

必要なデータは,"/rss/channel/item"配下の"link"と"title"のエントリにあります.この部分を抽出しています.リンクは日時データなので,これをキーにしたハッシュを作っています.これを利用することにより,前回取得したデータを再び出力することを防ぐことができます.それが,

for my $old (keys %$oldref){
	delete $table{$old} if (defined ($table{$old}));
}

この部分です.mixiで使っている文字コードEUCなので,書き込むときには文字コード変換が必要になります.

$text = encode("euc-jp", $text);

後は作成したデータを日記として書き込んでやればOKです.本来なら,コミュニティのどこかのトピックに書き込んだ方がいいのでしょうが,とりあえずこのスクリプトで問題がないかチェックするためにこのままでしばらく動かしてみます.