kzfm’s trial and error

本腰いれてRubyやPHPを覚えていくブログ。

XMLをParseする (Ruby)

Radikoの番組表データを取得する

東京エリアの放送内容をXMLで取得し、色んなモジュールでパースしてみる。
APIの仕様はこちらのページ*1を参考にさせて頂いた。

APIと応答されるXML内容

地域別番組表API
http://radiko.jp/v2/api/program/today?area_id=[area_id]
エリアコードを指定してURLにアクセスすると、その地域の全放送局の放送内容がXMLで返される。

東京エリア(JP13)を指定すると以下のXMLが応答された。

<?xml version="1.0" encoding="UTF-8"?>
<radiko>
  <ttl>1800</ttl>
  <srvtime>1433606138</srvtime>
  <stations>
    <station id="TBS">
      <name>TBSラジオ</name>
      <scd>
        <progs>
          <date>20150606</date>
          <prog ft="20150606050000" to="20150606050500" ftl="0500" tol="0505" dur="300">
            <title>ニュース・天気予報</title>
            <sub_title />  <pfm></pfm>
            <desc />  <info>&lt;img src=&apos;http://www.tbs.co.jp/radio/todays954/photo/noimage.gif&apos;&gt;&lt;br /&gt;&lt;br /&gt;</info>
            <metas>
              <meta name="twitter" value="#radiko" />
              <meta name="facebook-fanpage" value="http://www.facebook.com/radiko.jp" />
              <meta name="twitter-hash" value="#radiko" />
            </metas>
            <url>http://www.tbs.co.jp/radio/</url>
          </prog>
          <prog ft="20150606050500" to="20150606060000" ftl="0505" tol="0600" dur="3300">
            <title>生島ヒロシのサタデー・一直線</title>
            <sub_title />  <pfm>生島ヒロシ/寺田理恵子ゲスト:舟木一夫/森山良子</pfm>
            <desc />  <info>&lt;img src=&apos;http://www.tbs.co.jp/radio/todays954/photo/sat-1.jpg&apos;&gt;&lt;br /&gt;&lt;br /&gt;先週に引き続き、生島ヒロシさんが憧れている舟木一夫さんが登場。&lt;br/&gt;「サタイチ週末大人クラブ」では、森山良子さんとの対談をお届け。&lt;br/&gt;&lt;br/&gt;メール:&lt;a href=&quot;mailto:sat-1@tbs.co.jp&quot;&gt;sat-1@tbs.co.jp&lt;/a&gt;&lt;br/&gt;</info>
            <metas>
              <meta name="twitter" value="#radiko" />
              <meta name="twitter-hash" value="#radiko" />
              <meta name="facebook-fanpage" value="http://www.facebook.com/radiko.jp" />
            </metas>
            <url>http://www.tbs.co.jp/radio/sat-1/</url>
          </prog>
        </progs>
      </scd>
    </station>
    </stations>
</radiko>

(抜粋。本当はstationノードもprogノードもたくさんある)


"REXML"を使ってパース

Ruby標準?のXMLパーサ。

  • XPathでノードを抽出できる
  • elements.each()でXPathにマッチするノードをイテレーション
  • elements[]で子ノードを取得
  • attributes[]で自身の属性を取得
require 'open-uri'
require 'kconv'
require 'rexml/document'

url  = 'http://radiko.jp/v2/api/program/today?area_id=JP13'
xml  = open( url ).read.toutf8

doc = REXML::Document.new(xml)

doc.elements.each('radiko/stations/station') do |station|
    printf "---%s (%s)---\n",
      station.attributes['id'],
      station.elements['name'].text

    station.elements.each('scd/progs/prog') do |prog|
        printf "%d-%d(%2d) : %s(%s)\n",
          prog.attributes['ft'],
          prog.attributes['to'],
          prog.attributes['dur'].to_i/60,
          prog.elements['title'].text,
          prog.elements['pfm'].text
    end
end

凄くstrictな感じ。見やすい。

"ActiveSupport::XMLConverter"を使ってパース

RubyOnRailsに付属してるライブラリ。

  • XMLを解析してハッシュに格納してくれる
  • 複数存在するノードはArrayで格納される
require 'open-uri'
require 'kconv'
require 'active_support/core_ext/hash/conversions'

url  = 'http://radiko.jp/v2/api/program/today?area_id=JP13'
xml  = open( url ).read.toutf8
hash = Hash.from_xml(xml)

hash['radiko']['stations']['station'].each do |station|
  printf "---%s (%s)---\n",
     station['id'],
     station['name']

  station['scd']['progs']['prog'].each do |prog|
    printf "%d-%d(%2d) : %s(%s)\n",
      prog['ft'],
      prog['to'],
      prog['dur'].to_i/60,
      prog['title'], prog['pfm']
  end
end

特定ノードの「属性値」と、そのノードの「子ノード」の区別が無くなる。まあこれは大した問題じゃない。

問題なのは、対象ノードが「1つの場合」と「複数の場合」とで格納内容が変わってしまう点。例えば上記に貼った「抜粋版」のXMLだと、stationノードは1つだけなので配列になっておらず、このコードでは動作しない。

こういった点を考慮するとコードが膨らむので、「要素数が変化する外部サービスのXML」を処理する場合は使わないほうが良さそう。"REXML"のeachは対象ノードが1つの場合も期待通りに動作する。