NHK番組表APIを使ってオリジナル番組表を作って見ました。

## 動機
* ネタ探しをしていて、[Qiita.com:個人でも使える！おすすめAPI一覧](https://qiita.com/mikan3rd/items/ba4737023f08bb2ca161)の投稿に出会う。
* コメント欄に「[NHK 番組表 API](http://api-portal.nhk.or.jp/ja)もいいよ」とあったので覗いて見たら簡単そうだった。
* REST APIから取得(JSON形式)して、加工・整形してHTML作成まではいい練習と思い。
* **NHK番組APIを使ってオリジナルの番組表を作って見ました。**(見てくれは残念ですが、、、)

## API keyの取得の流れ
1. [NHK番組API](http://api-portal.nhk.or.jp/ja)で、アカウント登録を行います。
2. メール認証を経て、アプリケーション名：（好きな名称）、URL：（未登録）を登録します。
3. 登録が完了すると`API キー`が発行されます。**これが取得に必要です。**

## APIの概要
* 公開中のAPIは以下の４種類になります。

| No. | 名称 | 説明 |
|:-:|:-:|:-:|
|1   |Program List API   |  地域、サービス、日付を指定 |
|2   |Program Genre API   | 地域、ジャンル、日付を指定  |
|3   |Program Info API   |  番組IDを指定 |
|4   |Now On Air API   |  地域、サービスを指定 |

* 今回利用する`Program List API`は以下の構成になっています。

```http
https://api.nhk.or.jp/v2/pg/list/{area}/{service}/{date}.json?key={apikey}
```
| パラメータ | 説明 | 値 |
|:-:|:-:|:-:|
|area   | 地域ID(3byte)  |130:東京,他46地域   |
|service   |サービスID(2byte)   |g1：NHK総合,e1:Eテレ,s1:BS,s3:BSP   |
|date   |日付(YYYY-MM-DD形式)   | 例:2020-04-07  |
|apikey   |APIキー(32byte)   |  アプリ登録で取得した`APIキー` |

* レスポンスフォーマットは、`json`で、**利用回数制限は300回/日**となっています。
* 以下の例は、area:130(東京)、service:g1(NHK総合)、date:2020-04-07で取得した一部です。

```json
{
  "list": {
    "g1": [
      {
        "id": "2020040704706",
        "event_id": "04706",
        "start_time": "2020-04-07T04:02:00+09:00",
        "end_time": "2020-04-07T04:30:00+09:00",
        "area": {
          "id": "130",
          "name": "東京"
        },
        "service": {
          "id": "g1",
          "name": "ＮＨＫ総合１",
          "logo_s": {
            "url": "//www.nhk.or.jp/common/img/media/gtv-100x50.png",
            "width": "100",
            "height": "50"
          },
          "logo_m": {
            "url": "//www.nhk.or.jp/common/img/media/gtv-200x100.png",
            "width": "200",
            "height": "100"
          },
          "logo_l": {
            "url": "//www.nhk.or.jp/common/img/media/gtv-200x200.png",
            "width": "200",
            "height": "200"
          }
        },
        "title": "ダーウィンが来た！「波乱のライオン学園に潜入！百獣の王を養成！！」",
        "subtitle": "成長まっただ中のライオンの子どもたちが、群れの中で先生役の大人から狩りの技や子育て術を学ぶ。不真面目な生徒は退学処分に！？学園ドラマ顔負けの波乱の日々に密着！",
        "content": "新年度１本目は、ライオンの「学校」をご紹介。成長真っ最中のライオンの子どもたちが、群れの中で先生役の大人から狩りや子育て、ライバル撃退法まで、生きるためのあらゆるスベを学ぶ。でもライバル・ハイエナに全く歯が立たなかったり、狩りでは姿が丸見えで獲物にあっさり逃げられたり、実践形式の授業にみんなタジタジ。さらに不真面目な生徒はまさかの退学処分に！？学園ドラマ顔負けの波乱の授業に潜入！歌：ＭＩＳＩＡ",
        "act": "【語り】和久田麻由子，龍田直樹，豊嶋真千子，山田孝之，水瀬いのり",
        "genres": [
          "0802",
          "1000"
        ]
      },
{以下、省略}
```

## データ取得
* 取得したAPIキーで、`Program List API`を叩く。 
* 取得サービスは、g1:総合１、e1:Eテレ、s1:BS1、s3:BSプレミアムの４局です。
* pandasの`json_normalize()`を使って、json形式をデータフレームに変換します。

```python
import pandas as pd
import json
import requests
import datetime

# 取得したAPIキーをセット
apikey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

# 130:東京
area = '130'
# g1:NHK総合１,e1:Eテレ,s1:BS,s3:BSP
service = ['g1','e1','s1','s3']
# 日時
date = datetime.date.today()

all_results = pd.DataFrame(index=None, columns=[])
for i in range(len(service)):
    url = 'https://api.nhk.or.jp/v2/pg/list/{0}/{1}/{2}.json?key={3}'.format(area,service[i],date,apikey)
    request_get = requests.get(url)
    if request_get.status_code != 200:
        print('NHK番組表APIのデータが取得出来ません。')
        break
    result = pd.json_normalize(request_get.json(), ['list',[service[i]]])
    all_results = pd.concat([all_results, result])

all_results = all_results[~all_results['title'].str.contains('放送休止')]
```

取得したデータ概要：

```
<class 'pandas.core.frame.DataFrame'>
Int64Index: 235 entries, 0 to 37
Data columns (total 22 columns):
 #   Column                 Non-Null Count  Dtype 
---  ------                 --------------  ----- 
 0   id                     235 non-null    object
 1   event_id               235 non-null    object
 2   start_time             235 non-null    object
 3   end_time               235 non-null    object
 4   title                  235 non-null    object
 5   subtitle               235 non-null    object
 6   content                235 non-null    object
 7   act                    235 non-null    object
 8   genres                 235 non-null    object
 9   area.id                235 non-null    object
 10  area.name              235 non-null    object
 11  service.id             235 non-null    object
 12  service.name           235 non-null    object
 13  service.logo_s.url     235 non-null    object
 14  service.logo_s.width   235 non-null    object
 15  service.logo_s.height  235 non-null    object
 16  service.logo_m.url     235 non-null    object
 17  service.logo_m.width   235 non-null    object
 18  service.logo_m.height  235 non-null    object
 19  service.logo_l.url     235 non-null    object
 20  service.logo_l.width   235 non-null    object
 21  service.logo_l.height  235 non-null    object
dtypes: object(22)
memory usage: 42.2+ KB
```

## データ加工
* 日付形式に置き換え、放送時間の算出、ページ内リンクのためのHTMLコード列の追加

```python
# 日付形式に変換、放送時間の計算
all_results['start_time'] = pd.to_datetime(all_results['start_time'], format='%Y/%m/%d %H:%M')
all_results['end_time'] = pd.to_datetime(all_results['end_time'], format='%Y/%m/%d %H:%M')
all_results['airtime'] = all_results['end_time'] - all_results['start_time']
all_results['link'] = all_results['id']
# ページ内リンク情報の作成
func = lambda x: x.replace(x, f'<div id="{x}">番組詳細</div>')
all_results['link'] = all_results['link'].apply(func)

# 月・日。時間・分を取得する。さらに番組表の時間行名を追加する
tmp = pd.concat([all_results['start_time'].dt.month ,all_results['start_time'].dt.day,
                 all_results['start_time'].dt.hour, all_results['start_time'].dt.minute,
                 (all_results['start_time'].dt.day.astype(str)+all_results['start_time'].dt.strftime('%H'))], axis=1)
tmp.columns = ['month', 'day', 'hour', 'minute','time_bins']

# 取得データと新規の列を結合する
all_results = pd.concat([all_results, tmp], axis=1)

# 番組表ための集約
data = all_results.iloc[:,[23,0,1,2,3,4,5,6,7,8,9,10,11,12,22,24,25,26,27,28]]
```

## データ整形(その１)
* 番組表の必要データが揃えられたので、最終形のテーブルを作成してHTML形式保存します。

```python
# 最終の番組表のデータフレーム作成
tv_index = data.time_bins.sort_values().unique()
tv_table = pd.DataFrame(index=tv_index, columns=['H', 'g1', 'e1', 's1', 's3']).fillna('')

# コンテンツを結合して時間帯に挿入する。
cell = ''
for s in range(len(service)):
    for i in range(len(tv_table.index)):
        tmp = data[(data['time_bins'] == tv_table.index[i]) & (data['service.id'] == service[s])]

        for c in range(len(tmp)):
            cell += '[' + str(tmp['minute'].iloc[c]) + '~]'
            cell += tmp['title'].iloc[c]
            cell += f'<a href="#{tmp["id"].iloc[c]}">▼</a><br>'
        tv_table[service[s]].iloc[i] = cell
        cell = ''

# 時間帯追加
for h in range(len(tv_index)):
    tv_table['H'].iloc[h] = int(tv_index[h][-2:])
tv_table.columns = ['H', 'NHK総合', 'Eテレ', 'BS', 'BSP']
```

## データ整形(その２)
* 番組タイトルから、ページ内リンクとして番組詳細情報を提供するためテーブル作成します。

```python
detail_table = data.stack().reset_index()
detail_table.drop(columns='level_0', inplace=True)
detail_table.columns = ['種別', '値']
detail_table.set_index('種別', drop=True, inplace=True)
```
## データ整形(その3)
* pandasの`to_html`を使って、htmlファイルを作成します。

* htmlファイルの定義情報

```python
html_template = '''
<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <title>私製番組表</title>　<!-- #1 -->
  </head>
  <body>
    <h2>&nbsp;NHK番組表APIを使ってオリジナル番組表を作って見ました。{date}版</h2>　<!-- #2 -->

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
    <style type="text/css"> th {{ text-align: center; background-color: #f5f5f5}}</style>
    <div class="container-fluid"> <!-- #3 -->
        {table}
    </div>
    <div class="container-fluid"> <!-- #4 -->
        {table1}
    </div>
    <div>情報提供:ＮＨＫ</div> <!-- #5 -->
</body>
</html>
```
定義情報に追加したの以下の内容になります。
1. title:ページタイトル
2. h2:見出し + {date}
3. ４局の番組表table {table} -> table
4. 番組詳細table {table1} -> table1
5. クレジット表示

* htmlファイル形式の保存

```python
table = tv_table.to_html(classes=['table', 'table-bordered', 'table-hover'],escape=False)
table1 = detail_table.to_html(classes=['table', 'table-bordered', 'table-hover'],escape=False)
html = html_template.format(table=table, table1=table1, date=date)

with open('g1_table_' + str(date) + '.html', 'w') as f:
    f.write(html)
```
▼をクリックすると、ページ内リンクで番組詳細データにスクロールします。
![スクリーンショット 2020-04-07 16.08.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126042/80f4eac1-3387-076d-eb6a-6350172939d1.png)
![スクリーンショット 2020-04-07 16.08.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126042/42552302-0c07-194f-b7e6-92a8b446f471.png)


## 参考サイト
* [pandasのDataFrameを元に、画像入りの表をつくる](https://qiita.com/nshinya/items/a46ef0002284d2f77789)
* 上記のサイト情報から bootstrapのバージョンを最新化しました。

## まとめ
* `pandas.read_json`と`pandas.to_html`がどこまで使えるか確認したかったので、練習としては最適だった。
* `pandas.read_json`は軽い入り口的な状態で終わったので、また材料を探そうと思う。
* `pandas.to_html`は、そもそもの使い方が理解できたのが収穫でした。
* ただ`Bootstrap`を導入しても自分の**Web力**が低いので見てくれは**残念な結果**となった。
* ニュースが気になる状況なので、朝一に実行しておけば、本家サイトの番組表に飛ばずに確認ができます。
* [NHK番組表API 利用上の注意](http://api-portal.nhk.or.jp/ja/ServiceGuide#Guideline)を確認の上、用法・用量を守ってご利用ください。