玩式草子─ソフトウェアとたわむれる日々

第57回 「らじる★らじる」をもう一度(その2)

この記事を読むのに必要な時間:およそ 7 分

前回NHKのNow On Air情報(以下NOA情報)のURLにアクセスすれば,⁠らじる★らじる」で放送している番組のタイトルや曲目情報等がJSON形式で入手できることを紹介しました。

入手できる情報は番組によって異なるものの,たいていの音楽番組では放送した曲目一覧を提供しているので,それらをファイル名と紐づけて記録しておけば,聞きたい番組や曲を探す時に便利でしょう。そう考えて,NOA情報から得た番組情報をデータベース化するスクリプトを書いてみました。

番組情報データベースの設計

かってエア・チェックを趣味にしていた人間としては,前回紹介した専門誌の番組表のように,出演者や曲目を簡単に一覧できることが理想です。

そのため番組情報をテキストベースで集積していこうかとも考えたものの,多数の曲目リストを収めなければならない音楽番組と,せいぜい出演者名しか不要な語学番組を1つの表に収めるのも見通しが悪そうです。

そこで項目数を固定にした番組名のリスト任意数の曲目を登録できる内容リストは別の表として整理することにしました。表(テーブル)形式のデータを操作するなら,SQLで利用できるデータベースの形にしてしまうのが簡単そうです。

前回の最後に,NOAから得られる番組情報の一覧を示しました。これらのうち,indexやhashtag,linkやrebroad等の項目は特に保存しておく必要はなさそうです。また,subtitleやcontentとして登録されている情報はfreeの一部を取り出しているだけなので,freeを記録するようにすれば不要でしょう。そのあたりを考慮して,番組名リストと内容リストは以下のような形式にしました。

番組名リスト

番組IDファイル名日付チャンネルタイトル出演者

内容リスト

番組ID曲名 1
番組ID曲名 2
番組ID曲名 3

前者の番組名リストは1つの番組を1行に記録し,各番組には一意のID番号を振ります。一方,後者の内容リストは,free行に記録された曲目等の情報が,番組名リストのIDに紐づけられて任意の数だけ並んでいく,というシンプルな構造です。

だいたいのアイデアがまとまったので,これらの操作を使いなれたPythonとSQLite3で書いてみることにしました。

DB登録用スクリプト

まずデータベースを作成(初期化)する部分を作ります。SQLite3の場合,データベースは1つのファイルに保存されるので,使用するファイル名を引数として受けとることにしました。その中に含まれる番組名リストはtitlesというテーブル名,内容リストはcontentsという名前にしました。

def init_db(dbname):
    conn = sqlite3.connect(dbname)
    conn.isolation_level = None
    cursor = conn.cursor()
    cursor.execute('''create table titles
       (filename text, date text, ch text, title text, act text)''')
    cursor.execute('''create table contents
       (id int, list text)''')
    return conn

create tableしている部分を見ると気づくように,番組名リスト(titlesテーブル)の定義の中に先に示した「番組ID」はありません。これはSQLite3の場合,各行には自動的にROWIDと呼ばれるID番号が振られるので,このROWIDを番組IDに流用すればいいだろう,と判断したためです。

次に,作成したテーブルにデータを登録する部分を作りました。テーブルはtitlesとcontentsに分かれているので,登録用の関数も2つに分けています。Pythonの場合,複数のデータをひとかたまりにしたタプルと呼ばれるデータ構造が利用できるので,登録すべきデータはタプル(t)で渡し,書き込み先のデータベースはカーソルオブジェクト(cursor)で渡しています。タプル内のデータは,スクリプト中に"?"で指定したプレースホルダーに展開されるため,テーブルの構造に従ったタプルはこの関数を呼び出す側で用意することにして,登録用関数では最低限の処理に留めています。

def insert_title(cursor, t):
    try:
        cursor.execute('insert into titles values(?, ?, ?, ?, ?)', t)
    except sqlite3.Error, e:
        print "An error occurred:", e.args[0]

contentsテーブルにデータを登録する関数もほぼ同じですが,登録すべき項目は番組IDと曲目だけなので,登録するデータは2つだけです。

def insert_contents(cursor, t):
     try:
         cursor.execute('insert into contents values(?, ?)', t)
     except sqlite3.Error, e:
         print "An error occurred:", e.args[0]

次に,前回紹介した操作を元に,JSON形式でNOA情報を取り込む部分を作りました。urllib2.urlopen()は指定したURLを開くための関数で,インターネット上のURLをファイルと同じように操作できるオブジェクトを返します。このオブジェクトにread()メソッドを適用し,必要なデータを読み出します。

前回紹介したように,NOAサイトから読み出したデータには冒頭と末尾に余計なデータが付いているので,それらをはぎとって正式なJSON形式にしてからjson.loads()で読みこみ,Pythonの辞書型データとして返します。

def get_json_data():
    req = urllib2.urlopen('http://www2.nhk.or.jp/hensei/api/noa.cgi?c=3&wide=1&mode=jsonp')
    response = req.read()
    data = json.loads(response.lstrip('nowonair(').rstrip(');'))
    return data

NOA情報にはラジオ第一,第二,FMの3チャンネル分の番組情報が含まれているので,必要なデータを取り出す際にはどのチャンネルのデータを使うかを決める必要があります。また,データベースに登録するファイル名も外部から与える必要があるので,このスクリプトでは引数としてチャンネルファイル名を受けとることにしました。

def main():
    channel = sys.argv[1]
    title = sys.argv[2]

    if channel == 'fm':
        ch = '001netfm0'
    elif channel == 'r1':
        ch = '001netr10'
    elif channel == 'r2':
        ch = '001netr20'
    else:
        print("{} is not a valid channel(fm, r1, r2)".format(channel))
        sys.exit(1)

最初の引数で指定するchannelはfm,r1, r2の3択で,それぞれをNOA情報のチャンネル名に変換しています。また,必要なのは現在放送中の番組情報のみなので,末尾のインデックスは0に揃えました。

データベースと接続する部分はこんな感じにしてみました。データベースファイルは~/MP3/radiru_titles.sql3とし,このファイルが無ければ前述の初期化処理でデータベースを作成し,ファイルがあればデータベースとして接続します。

    dbname = os.path.expanduser("~/MP3/radiru_titles.sql3")
    if os.access(dbname, os.R_OK) == False:
        connection = init_db(dbname)
        cursor = connection.cursor()
    else:
        connection = sqlite3.connect(dbname)
        cursor = connection.cursor()

あとは実際にNOA情報を読み込み,指定したチャンネルの情報を取り出して,データベースに記録する作業です。

まずは get_json_data() を使ってNOA情報を取り込み,指定したチャンネル(ch)の情報から,⁠番組タイトル(title)⁠出演者(act)⁠録音日時(date)⁠の情報を取り出します。

    data = get_json_data()
    title = check_char(data[ch]['title'])
    act = check_char(data[ch]['act'])
    date = data[ch]['starttime']

前回も見たように,JSON形式で使用する文字コードはUTF-8で,SQLite3も保存するテキストデータの文字コードもUTF-8なので,JSON形式から切り出したデータはそのまま(文字コード変換なしに)記録することができます。

紹介は省きましたが,上記で使っているcheck_char()関数は, UTF-8からEUC-JPやSHIFT-JISに直接変換できないローマ数字や丸括弧数字, 全角チルダ等を修正するための処理です。

一方,コマンドラインから引数として渡されるファイル名はロケールで指定された文字コードになるため,SQLite3に記録するためには文字コードを変換する必要があります。

書き込むべきデータが揃えば,それらをinsert_title()に渡してデータベースに書き込んだ後,書き込んだ行のROWIDを取り出しておきます。

    filename = title.decode('euc-jp')
    insert_title(cursor, (filename, date, ch, title, act))
    connection.commit()
    rowid = cursor.lastrowid

次に,番組情報のうちfree行の部分を取り出して,改行記号が2つ並ぶ(=1行空け)ごとに分割して,それぞれをtitlesテーブルのROWIDと紐づけて,contentsテーブルに書きこんで行きます。

    txt = check_char(data[ch]['free'])
    cols = txt.split('\\n\\n')
    for i in cols:
        tmp = i.replace('\\n', '\n')
        insert_contents(cursor, (rowid, tmp))
        connection.commit()

著者プロフィール

こじまみつひろ

Plamo Linuxとりまとめ役。もともとは人類学的にハッカー文化を研究しようとしていたものの,いつの間にかミイラ取りがミイラになってOSSの世界にどっぷりと漬かってしまいました。最近は田舎に隠棲して半農半自営な生活をしながらソフトウェアと戯れています。

URLhttp://www.linet.gr.jp/~kojima/Plamo/index.html

コメント

コメントの記入