Walrus,Digit. | 一覧 | 検索 | 更新履歴(RSS) | 新規作成
はてなブックマークに追加 はてなブックマークを表示 編集 | 編集(管理者用) | 差分

Perlメモ/RSSの解析

編集

サイトの更新情報、RSSをパースする簡易的な方法です。

このサブルーチンはWalWikiWikiAntennaで使用しているものと、ほぼ同じものです。 エラーハンドリングもxmlnsの確認もencodingによる文字コードの判定もしていませんが、WalWikiWikiAntennaで使用実績は積んでいて、それなりに有効であるという点は 納得してもらえるかな、と思います。 かなり簡易というかいい加減な手法なのは承知ですが、intersiteメーリングリストなどでRSSの良し悪しを論じるにも、まずはRSSを手軽に使えなきゃというのが前提だと思うので、私のできる範囲での情報公開から。

余談ですけど、私自身はニュース系サイトだけでもasahi.comASCII24BiztechCNetJapanCNetITTrendHotWiredNewsPCWebInternetWatchLinux24NetSecurityNikkeiITNikkeiNetZDNetケータイWatchを取得(ただし多くはbulknews.net経由)していますが、特に問題は起きていません。

RSSの解析

編集

引数は、取得したRSSを読み込むためのファイルハンドルです。

RSSはeucコードに変換されていることを前提にしています。

sub parse_rss {
  my ($rss, $num) = @_;
  my @items = ();
  return unless ($rss);
  $num = 0 unless ($num =~ /^\d+$/);
  foreach my $item ($rss =~ /<item\b.*?>.*?<\/item>/gis) {
    my $parsed = {};
    foreach my $tag qw(title link description dc:date) {
      if ($item =~ /<$tag\b.*?>(.*?)<\/$tag>/is) {
        $parsed->{$tag} = &sanitize($1);
      }
    }
    $parsed->{'time'} = &date_to_time($parsed->{'dc:date'});
    push(@items, $parsed);
    last if ($num and @items >= $num);
  }
  return @items;
}

sub sanitize {
  my $str = shift;
  # remove tags and unescape
  my $re_tag_    = q{[^"'<>]*(?:"[^"]*"[^"'<>]*|'[^']*'[^"'<>]*)*(?:>|(?=<)|$(?!\n))}; #'};
  my $re_comment = '<!(?:--[^-]*-(?:[^-]+-)*?-(?:[^>-]*(?:-[^>-]+)*?)??)*(?:>|$(?!\n)|--.*$)';
  my $re_tag     = qq{$re_comment|<$re_tag_};
  $str =~ s/$re_tag//g;
  # resanitize
  my %unescaped = ('&lt;' => '<', '&gt;' => '>', '&quot;' => '"', '&apos;' => "'", '&copy;' => '(c)', '&amp;' => '&');
  my %escaped = ('<' => '&lt;', '>' => '&gt;', '"' => '&quot;', '&apos;' => "'", '&' => '&amp;');
  $str =~ s/&(lt|gt|quot|apos|copy|amp);/$unescaped{$1}/gio;
  $str =~ s/([<>"'&])/$escaped{$1}/go;
  return $str;
}

sub date_to_time {
  my $date = shift;
  if ($date =~ /^(\d{4})(?:-(\d{2})(?:-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d))?)?(Z|([+-]\d{2}):(\d{2}))?)?)?)?$/) {
    my ($year, $month, $day, $hour, $min, $sec) = ($1, ($2 ? $2 : 1), ($3 ? $3 : 1), $4, $5);
    my $offset = (abs($8) * 60 + $9) * ($8 >= 0 ? 60 : -60) if ($7);
    my $time   = ($7) ? &Time::Local::timegm($sec, $min, $hour, $day, $month - 1, $year) - $offset
                      : &Time::Local::timelocal($sec, $min, $hour, $day, $month - 1, $year) - $offset;
    return $time;
  }
  return undef;
}

返り値はハッシュリファレンスの配列で、個々のハッシュは、例えば次のような内容になっています。 項目は、元のrssの含んでいる情報によって変わりますが、通常はabout、title、linkぐらいは持っています。

{
  'about' => itemの関連するURL,
  'title' => itemの表題,
  'link' => itemのリンクしているURL(aboutと同じケースが多い),
  'description' => itemの記述(本文またはその一部であることが多い),
  'dc:date' => itemの作成(とは限らない)日付([[Perlメモ/W3C形式の日時の解析]]で説明している形式)
  'time' => dc:dateを1970年1月1日からの秒数に直した値(localtime等の変換にかけられます)
}

RSSからHTMLのリストに変換。

例えば上のサブルーチンを使用して、RSSからHTMLのリストに変換ということを簡単に行うことができます。 複数のRSSをパースして@itemsに保持、$item->{'dc:date'}で降順ソートして、複数のサイトの更新情報を時系列で一覧表示、といったこともできるでしょう。

my $fh;
open($fh, "http://bulknews.net/rss/rdf.cgi?NikkeiNet");
my @items = &parse_rss($fh);
close $fh;
if (@items) {
  print "<ul>\n";
  foreach (@items) {
    print qq(\t<li>$_->{'dc:date'} <a href="$_->{'link'}">$_->{'title'}</a> - $_->{'description'}</li>\n);
  }
  print "</ul>\n";
}

日付はRSSから取得された際の形式そのままで出力されます。 おそらく、Perlメモ/W3C形式の日時の解析の「YukiWiki/WalWikiの日付形式に変換。」に示したスクリプトを使うなどして、書式を変換、統一した方が見やすくなるでしょう。

コメント

[[#rcomment]]