ontvjapanでテレビ番組のリストを得る

テレビ番組表取得スクリプトが使えなくなった - http://rubikitch.com/に移転しましたYahoo!の番組表が使えなくなった件だが、あのサイトから情報を得るのは大変なので別のサイトを使ってみることにした。
一見どこのサイトも表形式での番組表しか提供していないようだ。広い新聞上ならば番組表の形式はそこそこ見やすいのだが、狭いPC画面では番組表の形式は見づらくてしょうがない。XMLかなんかで提供してくれれば、あとは自由に情報を得られるのだが、俺が使いたいのはないようだ。ライブドアRSS提供サービスは50番組しかリストしてくれないし。
番組表をXMLに落とすソフトウェアならば、xmltvがある。しかし、実際使ってみると文字化けしてしまう。原因を探るべくソースコードを読んでみたら、ontvjapanの番組表を解析しているのがわかった。さらに追ってみると、開始時刻と終了時刻も簡単に得られることがわかった。番組表のHTMLを見たところ、HTMLに欲しい情報がすべて埋め込まれていることがわかった。

軽くHpricotで解析してやるとこんな感じ。

require 'rubygems'
require 'hpricot'
$KCODE='e'
html = Hpricot <<EOH
      <span class="style_title"><a class=genreYellow href="/genre/detail.php3?tikicd=0002&hsid=200808030006039" target=_self title="02:35-04:25 スポーツ/メジャーリーグ">MAJOR LEAGUE BASEBALL 2008<IMG border=0 src="/images/mark/stereo.gif"><IMG border=0 src="/images/mark/hv.gif"></a></span><br>
      <span class="style_subtitle">「エンゼルス×ヤンキース」</span><br><br>
      <span class="style_corner">解説・片岡篤史 宮瀬茉祐子 実況・田淵裕章【中止のとき】タイガース×レイズ▽パイレーツ×カブス </span><br>
EOH
a = html.at("span.style_title/a")
# hsidに日付とチャンネルの情報が含まれている。
a[:href]   # => "/genre/detail.php3?tikicd=0002&hsid=200808030006039" # !> `&' interpreted as argument prefix
# 開始時刻、終了時刻、ジャンル
a[:title]  # => "02:35-04:25 スポーツ/メジャーリーグ"
# サブタイトル
html.at("span.style_subtitle").inner_text  # => "「エンゼルス×ヤンキース」"
# 備考
html.at("span.style_corner").inner_text    # => "解説・片岡篤史 宮瀬茉祐子 実況・田淵裕章【中止のとき】タイガース×レイズ▽パイレーツ×カブス "

hsidの値は日付(8桁)、チャンネルID(4桁)、連番(3桁)の形式になっているので、正規表現で取り出す。

しかし、実際のところHpricotを使うまでもなく、正規表現とString#scanで十分間に合う。→巨大な正規表現で解析するのは快感 - http://rubikitch.com/に移転しました
というか、Hpricotを使ったとしてもhsidや開始時刻、終了時刻、ジャンルの取り出しに正規表現を使う*1ので、HTMLを丸ごと正規表現マッチしてしまえばHTMLの解析だけでなく、細かい情報の取り出しもできる。まさに一石二鳥というわけだ。

そこでできあがったのが以下のスクリプトだ。

#!/usr/bin/env ruby
# -*- coding: euc-jp -*-
require 'open-uri'
require 'kconv'

# http://www.ontvjapan.com/program/gridNormal.php?frame=off&tikicd=0002&way=v&genre=all&hour_select=24&s_jikan=050000

URL = "http://www.ontvjapan.com/program/gridNormal.php?frame=off&tikicd=0002&way=v&genre=all&hour_select=24&s_jikan=050000"
def scan_html(html, &block)
  regexp = /
  <span\sclass="style_title"><a\s.+?href=".+?
   hsid=\d{8}  # yyyymmdd
   (\d{4})     # $1: channel id
   \d{3}       # program id
  ".+?
   title="
   (\d\d:\d\d) # $2: start
   -
   (\d\d:\d\d) # $3: end
   \s
   (.+?)       # $4: genre
   ">(.+?)<    # $5: title
  .+?\n
  (?:
  \s+
   <span\sclass="style_subtitle">(.*?)< # $6: subtitle
   .+?\n
  )?
  \s+
  <span\sclass="style_corner">(.*?)<    # $7: corner
  /xm

  html.gsub!(/<img.+?>/im, '')
  html.scan(regexp, &block)
end

CHANNEL_NO = {
  "0031" => 1,
  "0041" => 3,
  "0004" => 4,
  "0005" => 6,
  "0006" => 8,
  "0007" => 10,
  "0008" => 12,
}

def html2txt(html)
  txt = []
# * 05:00-06:00 1ch  NHKニュース おはよう日本 ニュース・気象情報・スポーツ 佐藤龍文,礒野佑子
  scan_html(html) do |channel, s, e, genre, title, subtitle, corner|
    txt << "* #{s}-#{e} #{CHANNEL_NO[channel]}ch  #{title}(#{subtitle})#{corner}(#{genre})\n"
  end
  txt
end

puts html2txt(open(URL).read)

実行するとこんな結果になる。270行ほど延々と続く。幸いなことに開始時間順に並んでいるのでソートする手間も省けた。あ、タグ除去するの忘れた、まいっかw
出力形式を変更したけりゃhtml2txtメソッドを適当に書き換えてくれ。

* 05:00-05:15 1ch  ニュース・気象情報()(ニュース・報道/総合)
* 05:00-06:00 3ch  こころの時代〜宗教・人生(「身体からの探求」)(ドキュメンタリー・教養/文化・宗教)
* 04:50-05:05 4ch  夜明けのマルシェ()(情報/テレビショッピング)
* 04:25-05:10 6ch  <IMG border=0 src="/images/mark/n.gif">バード()(ニュース・報道/総合)
* 04:50-05:20 8ch  通販DJ()(情報/テレビショッピング)
* 04:50-05:20 10ch  はい!テレビ朝日です()(情報/テレビ番組)

やはりテキスト形式に落とすのが俺にとっちゃ一番見やすいし使いやすい。あとでgrepかければ欲しい番組のみ手に入れられるし、「延長」とか「番組変更」でgrepかければ野球延長対策もできる!録画予約したい番組の前に番組変更の危険性がわかる。番組変更の可能性がある場合は30分余分に録画すりゃいい。録画予約は、↑の形式のテキストを解析(笑)してテレビ録画スクリプト*2をcrontabに登録するEmacs Lispを何年も前から使っている。
iEPG?それっておいしいの?なんでも録画予約の技術らしいけど、それって急な番組変更に対応できるの?
多くの番組表サイトはキーワードやジャンルで絞り込みができるのだが、番組変更対策がなされてないんじゃないの?だったらまったく意味ないし。

とにかくontvjapanというサイトを見つけられてラッキーだ。こんな解析しやすいサイトあるんだったら、前からそれ使えばよかったよ。

それにしても、なんで番組表をXMLで公開しているサイトってないんだろぅ…あれば幸せになれる人多いと思うのだが。

サイトの評価はMVCのうち、VとCで決められるようなものだが、俺にとっちゃw3mで見られりゃVもCもはっきり言ってどーでもいい。Mさえ取れ(復元でき)ればあとはこちらで本当に欲しい情報取り出すから。

追記[2008/10/18]

バグ修正。

*1:まあstr[m,n]でもできるけど。

*2:mencoderを使っている