ぐるなびからレストランデータを取得・表示する

PythonからWeb APIを呼び出す練習として、ぐるなびAPI等を利用したサンプルを作成してみます。
なお、Python3で確認しています。

※PythonもWeb APIも、まだまだ勉強中です．．．

# Web API
使用したWeb APIは以下の2つ。
- ぐるなびAPI：https://api.gnavi.co.jp/api/
- Geocoding API：https://www.geocoding.jp/api/

ぐるなびAPIを使用するためには、アカウントの発行が必要です。
Geocodingは自由に利用できます。

# コード
## モジュールの使用
特別なモジュールは使用していません。

```python
import sys
import urllib
import xml.etree.ElementTree as ET
import json
```

Web APIにアクセスするために「urllib」を使用します。
また、Geocoding APIの戻りはXMLなため「xml.etree.ElementTree」を使用します。
ぐるなびAPIからはJSONでデータを取得するため「json」を使用します。

## パラメータ
今回は以下の2つのパラメータを指定することにします。
- 場所
- フリーワード

「場所」から緯度経度を取得するためにGeocoding APIを使用します。
「フリーワード」はぐるなびAPIで使用します。

```python
# 探したいパラメータ
place = '東京駅'
freeword = "インド料理"
```

ここでは、東京駅周辺のインド料理屋を探すことにします。

## Geocoding APIの使用
Geocoding APIはGETでアクセスします。

```python
# 位置から緯度経度を取得
def get_latlong():
    url = "https://www.geocoding.jp/api/"
    params =  urllib.parse.urlencode({
        'q': place
    })
    try:
        response = urllib.request.urlopen(url + '?' + params)
        XmlData = response.read()
        root = ET.fromstring(XmlData)
        return root.findtext('.//lat'), root.findtext('.//lng')
    except:
        raise Exception('Geocoding APIへのアクセスに失敗しました')
```

緯度は「lat」に、経度は「lng」に設定されてくるため、そこから取得します。
（それぞれ1つだけしか設定されていないはず）

## ぐるなびAPIの使用
ぐるなびAPIもGETでアクセスします。
今回指定しているパラメータは少ないですが、もっと細かく設定することも可能です。

```python
# ぐるなびからレストランデータを取得
def gnavi_api(latitude, longitude):
    key = 'your-accesskey-id'
    url = "https://api.gnavi.co.jp/RestSearchAPI/v3/"
    search_range = '1' # 半径300mを検索
    params = urllib.parse.urlencode({
        'keyid': key,
        'latitude': latitude,
        'longitude': longitude,
        'range': search_range,
        'freeword': freeword
        # 最大10件
    })
    try:
        response = urllib.request.urlopen(url + '?' + params)
        return response.read()
    except:
        raise Exception('ぐるなびAPIへのアクセスに失敗しました')
```

<font color="FF0000">※アクセスキーは、アカウント発行時に送られてきたものを使用してください</font>

今回は検索範囲を半径300mとし、取得件数はデフォルト（10件）としました。
取得したJSONデータの解析は、このあとの関数で行います。

## JSONデータの解析
ぐるなびAPIから取得したJSONデータを解析して、その情報を表示します。

```python
# レストランデータを解析して表示
def parse_json(data):
    parsed_data = json.loads(data)
    if 'error' in parsed_data:
        if 'message' in parsed_data:
            raise Exception('{0}'.format(parsed_data['message']))
        else:
            raise Exception('データ取得に失敗しました')

    # ヒット数を確認
    total_hit_count = parsed_data.get('total_hit_count', 0)
    if total_hit_count < 1:
        raise Exception('指定した内容ではヒットしませんでした')
    print('{0}件ヒットしました。'.format(total_hit_count))
    print('---')

    # レストランデータを表示（最大10件）
    for (count, rest) in enumerate(parsed_data.get('rest')):
        access = rest.get('access', {})
        url = rest.get('url', '')
        name = rest.get('name', '')
        access_line = access.get('line', '')
        access_station = access.get('station', '')
        access_walk = '{0}分'.format(access.get('walk', ''))
        categories = rest.get('code', {}).get('category_name_s', []) 
        result_list = [url, name, access_line, access_station, access_walk] + categories
        result = ', '.join(result_list)
        print(result)
    print('---')
    print('{0}件出力しました'.format(count+1))
```

ここでは以下の情報を表示しています。
- URL
- 路線
- 最寄り駅
- 距離（徒歩時間）
- カテゴリー

カテゴリーだけは複数の可能性があるので、最後に追加しています。

## 処理の実行
ここまでに用意した関数を呼び出します。

```python
# 処理を開始
try:
    latitude, longitude = get_latlong()
    res = gnavi_api(latitude, longitude)
    parse_json(res)
except Exception as e:
    tb = sys.exc_info()[2]
    print("message:{0}".format(e.with_traceback(tb)))
```

まず緯度経度を取得し、それをぐるなびAPIに渡してレストランデータを取得し、解析して表示するといった流れになります。

ちなみに、実行結果はこんな感じ。

```text
1件ヒットしました。
---
https://r.gnavi.co.jp/9xzzav9z0000/?ak=VTJZSHreuzXYpQ4oPijWc75KCbgoEPj5MARQQJzcCMQ%3D, スーリヤ 東京八重洲店, ＪＲ, 東京駅, 1分, インドカレー, カレー その他
---
1件出力しました
```

# 感想
簡単にPythonからWeb APIを呼び出せるんだと感動しています。
それ以上に、JSONの解析が簡単すぎて驚いています。
（C言語信者はいつも苦労しています）

これをベースに、各クラウドベンダー提供のAI系のWeb APIを利用したサンプルも書いていければなと思っています。

# 参考
https://qiita.com/himanoa/items/dad0a85864e0b088b10c
https://qiita.com/mikan3rd/items/ba4737023f08bb2ca161