pythonでWebAPIを使ってみる

# 1.はじめに

pythonの学習を進める中でWebアクセスやスクレイピングなんてものにもチュートリアル程度に触れたのですが、  
如何せん、しばらくは業務で使う機会もなさそうなので備忘録として記事を残すことにしました。  

# 2.アプリを作成する

アプリの作成に沿って使用したライブラリの使用例を挙げていきます。  
今日の天気を気象庁のWebAPIから取得して表示してくれるアプリを作成します。  

# 3.WebAPIにアクセス(urllib)

まずは気象庁のサイトから本日の天気の情報を取得します。  
今回はpythonに標準機能として用意されているurllibというライブラリを使用してWebAPIにアクセスを試みます。  
下記のコードを実行します。  

* "urllib.request.urlopen(url)"で気象庁のWebApiにアクセス 
* "response.read()"で内容を読出

```python:
#!/usr/bin/env python

import urllib.request

url = "https://www.jma.go.jp/bosai/forecast/data/forecast/170000.json"

response = urllib.request.urlopen(url)
print(response.read())
```

実行結果として以下のレスポンスが得られました。  

```
b'[{"publishingOffice":"\xe9\x87\x91\xe6\xb2\xa2\xe5\x9c\xb0\xe6\x96\xb9\xe6\xb0\x97\xe8\xb1\xa1\xe5\x8f\xb0","reportDatetime":"2021-06-21T05:00:00+09:00","timeSeries":[{"timeDefines":["2021-06-21T05:00:00+09:00","2021-06-22T00:00:00+09:00"],"areas":[{"area":{"name":"\xe5\x8a\xa0\xe8\xb3\x80","code":"170010"},"weatherCodes":["100","212"],"weathers":["\xe6\x99\xb4\xe3\x82\x8c","\xe3\x81\x8f\xe3\x82\x82\xe3\x82\x8a\xe3\x80\x80\xe5\xa4\x95\xe6\x96\xb9\xe3\x80\x80\xe3\x81\x8b\xe3\x82\x89\xe3\x80\x80\xe5\xa4\x9c\xe3\x81\xae\xe3\x81\xaf\xe3\x81\x98\xe3\x82\x81\xe9\xa0\x83\xe3\x80\x80\xe9\x9b\xa8\xe3\x80\x80\xe6\x89\x80\xe3\x81\xab\xe3\x82\x88\xe3\x82\x8a\xe3\x80\x80\xe6\x98\xbc\xe9\x81\x8e\xe3\x81\x8e\xe3\x80\x80\xe3\x81\x8b\xe3\x82\x89\xe3\x80\x80\xe5\xa4\x9c\xe3\x81\xae\xe3\x81\xaf\xe3\x81\x98\xe3\x82\x81\xe9\xa0\x83\xe3\x80\x80\xe9\x9b\xb7\xe3\x80\x80\xe3\x82\x92\xe4\xbc\xb4\xe3\x81\x86"],"winds":["\xe6\x9d\xb1\xe3\x81\xae\xe9\xa2\xa8\xe3\x80\x80\xe6\x97\xa5\xe4\xb8\xad\xe3\x80\x80\xe5\x8c\x97\xe3\x81\xae\xe9\xa2\xa8","\xe8\xa5\xbf\xe3\x81\xae\xe9\xa2\xa8"],"waves":["\xef\xbc\x91\xef\xbc\x8e以下略...
```

# 4.データを成型する(BeautifulSoup4)

上記で気象庁の公開している情報の取得は出来ましたが、  
型がバイナリ型であったり、改行されていなかったりと正直データが見づらいです。
そこでBeautifulSoupというライブラリを使用してデータを成型します。

まず下記コマンドでインストールします。

```
pip install beautifulfulsoup4
```

インストールが完了したら下記のコードを実行します。
上記のコードからの変更点は

* "from bs4 import BeautifulSoup"でBeautifulSoupをインポート
* "soup = BeautifulSoup(response, 'html.parser')"でレスポンスを成型。  
"html.parser"は構文を解析するツールを指定しています。

```python:
#!/usr/bin/env python

import urllib.request
from bs4 import BeautifulSoup # New!!

url = "https://www.jma.go.jp/bosai/forecast/data/forecast/170000.json"

response = urllib.request.urlopen(url)
# print(response.read())

# New!! BeautifulSoupで変換
soup = BeautifulSoup(response, 'html.parser')
print(soup)
```

実行結果は以下となります。

```
[{"publishingOffice":"金沢地方気象台","reportDatetime":"2021-06-21T05:00:00+09:00","timeSeries":[{"timeDefines":["2021-06-21T05:00:00+09:00","2021-06-22T00:00:00+09:00"],"areas":[{"area":{"name":"加賀","code":"170010"},"weatherCodes":["100","212"],"weathers":["晴れ","くもり　夕方　から　夜のはじめ頃　雨　所により　昼過ぎ　から　夜のはじめ頃　雷　を伴う"],"winds":["東の風　日中　 
北の風","西の風"],"waves":["１．５メートル　後　１メートル　うねり　を伴う","１メートル"]},{"area":{"name":"能登","code":"170020"},"weatherCodes":["211","200"],"weathers":["くもり　昼前　から
　晴れ","くもり　所により　昼前　から　夕方　雨"],"winds":["南の風　日中　北東の風","南の風　日中　西の風"],"waves":["１．５メートル　後　１メートル　うねり　を伴う","１メートル"]}]},{"timeDefines":["2021-06-21T06:00:00+09:00","2021-06-21T12:00:00+09:00","2021-06-21T18:00:00+09:00","2021-06-22T00:00:00+09:00","2021-06-22T06:00:00+09:00","2021-06-22T12:00:00+09:00","2021-06-22T18:00:00+09:00"],"areas":[{"area":{"name":"加賀","code":"170010"},"pops":["0","0","0","10","10","50","50"]},{"area":{"name":"能登","code":"170020"},"pops":["0","0","0","10","10","20","20"]}]},{"timeDefines":["2021-06-21T09:00:00+09:00","2021-06-21T00:00:00+09:00","2021-06-22T00:00:00+09:00","2021-06-22T09:00:00+09:00"],"areas":[{"area":{"name":"金沢","code":"56227"},"temps":["30","30","20","26"]},{"area":{"name":"輪島","code":"56052"},"temps":["26","26","18","25"]}]}]},{"publishingOffice":"金沢地方気象台","reportDatetime":"2021-06-20T17:00:00+09:00","timeSeries":[{"timeDefines":["2021-06-21T00:00:00+09:00","2021-06-22T00:00:00+09:00","2021-06-23T00:00:00+09:00","2021-06-24T00:00:00+09:00","2021-06-25T00:00:00+09:00","2021-06-26T00:00:00+09:00","2021-06-27T00:00:00+09:00"],"areas":[{"area":{"name":"石川県","code":"170000"},"weatherCodes":["101","212","202","200","200","200","200"],"pops":["","50","50","40","30","40","40"],"reliabilities":["","","C","B","A","B","B"]}]},{"timeDefines":["2021-06-21T00:00:00+09:00","2021-06-22T00:00:00+09:00","2021-06-23T00:00:00+09:00","2021-06-24T00:00:00+09:00","2021-06-25T00:00:00+09:00","2021-06-26T00:00:00+09:00","2021-06-27T00:00:00+09:00"],"areas":[{"area":{"name":"金沢","code":"56227"},"tempsMin":["","20","19","19","20","20","20"],"tempsMinUpper":["","21","20","21","21","23","23"],"tempsMinLower":["","18","17","17","18","18","18"],"tempsMax":["","26","26","26","27","27","27"],"tempsMaxUpper":["","28","28","29","29","30","30"],"tempsMaxLower":["","25","24","24","24","23","23"]}]}],"tempAverage":{"areas":[{"area":{"name":"金沢","code":"56227"},"min":"19.7","max":"26.4"}]},"precipAverage":{"areas":[{"area":{"name":"金沢","code":"56227"},"min":"31.4","max":"70.1"}]}}]
```

# 5.更にデータを成型する(json)

だいぶ正体が見えてきましたが、
jsonクラスを使用して更に人間側に分かりやすい形式に変換します。

```python
#!/usr/bin/env python

import urllib.request
from bs4 import BeautifulSoup
import json # New!!

url = "https://www.jma.go.jp/bosai/forecast/data/forecast/170000.json"

response = urllib.request.urlopen(url)
# print(response.read())

soup = BeautifulSoup(response, 'html.parser')
# print(soup)

# New!!　見やすいJSON形式に変換
data = json.loads("{}".format(soup, 'utf-8'))
data = json.dumps(data, indent=2, ensure_ascii=False)
print(data)
```

実行結果は以下となります。

```
[
  {
    "publishingOffice": "金沢地方気象台",
    "reportDatetime": "2021-06-21T11:00:00+09:00",
    "timeSeries": [
      {
        "timeDefines": [
          "2021-06-21T11:00:00+09:00",
          "2021-06-22T00:00:00+09:00",
          "2021-06-23T00:00:00+09:00"
        ],
        "areas": [
          {
            "area": {
              "name": "加賀",
              "code": "170010"
            },
            "weatherCodes": [
              "100",
              "212",
              "202"
            ],
            "weathers": [
              "晴れ",
              "くもり　夕方　から　夜のはじめ頃　雨　所により　昼過ぎ　から　夜のはじめ頃　雷　を伴う",
              "くもり　一時　雨"
            ],
            "winds": [
              "北の風　後　東の風",
              "西の風",
              "南の風　後　北西の風"
            ],
            "waves": [
              "１．５メートル　後　１メートル　うねり　を伴う",
              "１メートル",
              "０．５メートル"
            ]
          },
          {
            "area": {
              "name": "能登",
              "code": "170020"
            },
            "weatherCodes": [
              "100",
              "200",
              "202"
            ],
            "weathers": [
              "晴れ",
              "くもり　所により　昼前　から　夕方　雨",
              "くもり　一時　雨"
            ],
            "winds": [
              "北東の風　後　南の風",
              "南の風　日中　西の風",
              "南の風　後　北の風"
            ],
            "waves": [
              "１．５メートル　後　１メートル　うねり　を伴う",
              "１メートル",
              "０．５メートル"
            ]
          }
        ]
      },
      {
        "timeDefines": [
          "2021-06-21T12:00:00+09:00",
          "2021-06-21T18:00:00+09:00",
          "2021-06-22T00:00:00+09:00",
          "2021-06-22T06:00:00+09:00",
          "2021-06-22T12:00:00+09:00",
          "2021-06-22T18:00:00+09:00"
        ],
        "areas": [
          {
            "area": {
              "name": "加賀",
              "code": "170010"
            },
            "pops": [
              "10",
              "0",
              "10",
              "10",
              "50",
              "50"
            ]
          },
          {
            "area": {
              "name": "能登",
              "code": "170020"
            },
            "pops": [
              "0",
              "0",
              "10",
              "10",
              "20",
              "20"
            ]
          }
        ]
      },
      {
        "timeDefines": [
          "2021-06-21T09:00:00+09:00",
          "2021-06-21T00:00:00+09:00",
          "2021-06-22T00:00:00+09:00",
          "2021-06-22T09:00:00+09:00"
        ],
        "areas": [
          {
            "area": {
              "name": "金沢",
              "code": "56227"
            },
            "temps": [
              "30",
              "30",
              "20",
              "26"
            ]
          },
          {
            "area": {
              "name": "輪島",
              "code": "56052"
            },
            "temps": [
              "26",
              "26",
              "18",
              "25"
            ]
          }
        ]
      }
    ]
  },
  {
    "publishingOffice": "金沢地方気象台",
    "reportDatetime": "2021-06-21T11:00:00+09:00",
    "timeSeries": [
      {
        "timeDefines": [
          "2021-06-22T00:00:00+09:00",
          "2021-06-23T00:00:00+09:00",
          "2021-06-24T00:00:00+09:00",
          "2021-06-25T00:00:00+09:00",
          "2021-06-26T00:00:00+09:00",
          "2021-06-27T00:00:00+09:00",
          "2021-06-28T00:00:00+09:00"
        ],
        "areas": [
          {
            "area": {
              "name": "石川県",
              "code": "170000"
            },
            "weatherCodes": [
              "212",
              "202",
              "201",
              "201",
              "200",
              "202",
              "202"
            ],
            "pops": [
              "",
              "50",
              "30",
              "30",
              "30",
              "50",
              "50"
            ],
            "reliabilities": [
              "",
              "",
              "A",
              "A",
              "A",
              "C",
              "C"
            ]
          }
        ]
      },
      {
        "timeDefines": [
          "2021-06-22T00:00:00+09:00",
          "2021-06-23T00:00:00+09:00",
          "2021-06-24T00:00:00+09:00",
          "2021-06-25T00:00:00+09:00",
          "2021-06-26T00:00:00+09:00",
          "2021-06-27T00:00:00+09:00",
          "2021-06-28T00:00:00+09:00"
        ],
        "areas": [
          {
            "area": {
              "name": "金沢",
              "code": "56227"
            },
            "tempsMin": [
              "",
              "19",
              "19",
              "19",
              "20",
              "21",
              "20"
            ],
            "tempsMinUpper": [
              "",
              "21",
              "20",
              "21",
              "22",
              "22",
              "23"
            ],
            "tempsMinLower": [
              "",
              "17",
              "17",
              "18",
              "18",
              "18",
              "18"
            ],
            "tempsMax": [
              "",
              "25",
              "26",
              "27",
              "28",
              "27",
              "25"
            ],
            "tempsMaxUpper": [
              "",
              "28",
              "27",
              "29",
              "30",
              "30",
              "29"
            ],
            "tempsMaxLower": [
              "",
              "23",
              "23",
              "24",
              "25",
              "24",
              "22"
            ]
          }
        ]
      }
    ],
    "tempAverage": {
      "areas": [
        {
          "area": {
            "name": "金沢",
            "code": "56227"
          },
          "min": "19.8",
          "max": "26.5"
        }
      ]
    },
    "precipAverage": {
      "areas": [
        {
          "area": {
            "name": "金沢",
            "code": "56227"
          },
          "min": "33.5",
          "max": "73.0"
        }
      ]
    }
  }
]
```

# 6.必要なデータを表示する

上記の取得値から"weathers"の最初のデータが当日の値であることが分かります。
JSONの構造が見えたので下記コードで天気を出力させます。

```python
#!/usr/bin/env python

import urllib.request
from bs4 import BeautifulSoup
import json

url = "https://www.jma.go.jp/bosai/forecast/data/forecast/170000.json"

response = urllib.request.urlopen(url)
# print(response.read())

soup = BeautifulSoup(response, 'html.parser')
# print(soup)

data = json.loads("{}".format(soup, 'utf-8'))
# data = json.dumps(data, indent=2, ensure_ascii=False)
# print(data)

# New!! 天気を表示
print("今日の天気は{}。".format(data[0]["timeSeries"][0]["areas"][0]["weathers"][0]))
```

実行結果は以下となります。

```
今日の天気は晴れ。
```

# 7.さいごに

今回使用した気象庁のWebAPI以外にも世の中には有料のものから無料のものまで様々な情報がWebAPIとして公開されているみたいです。
利用できるものは何でも利用して高機能なプログラムを素早く開発できると素敵だなと思いました。

# 8.参照

気象庁HP(https://www.jma.go.jp/jma/index.html)