{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## 天気予報\n", "\n", "奈良の天気予報のデータを表示してみよう。使いやすい予報データは以下のところから得られる。\n", "\n", "* [Weather Hacks](http://weather.livedoor.com/weather_hacks/webservice)\n", "* [OpenWeatherMap](http://openweathermap.org/api)\n", "\n", "ここではWeather Hacksのデータを使う。予報データは日本気象協会が作成しライブドアが配信している。" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from urllib.request import urlopen\n", "import json\n", "city = 290010 # 奈良\n", "url = \"http://weather.livedoor.com/forecast/webservice/json/v1?city=\" + str(city)\n", "response = urlopen(url)\n", "content = json.loads(response.read().decode(\"utf8\"))" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'copyright': {'image': {'height': 26,\n", " 'link': 'http://weather.livedoor.com/',\n", " 'title': 'livedoor 天気情報',\n", " 'url': 'http://weather.livedoor.com/img/cmn/livedoor.gif',\n", " 'width': 118},\n", " 'link': 'http://weather.livedoor.com/',\n", " 'provider': [{'link': 'http://tenki.jp/', 'name': '日本気象協会'}],\n", " 'title': '(C) LINE Corporation'},\n", " 'description': {'publicTime': '2017-08-20T10:32:00+0900',\n", " 'text': ' 近畿地方は、高気圧に覆われて、晴れています。\\n\\n 今日の奈良県は、高気圧に覆われておおむね晴れますが、強い日射の影響\\nで大気の状態が不安定となるため、昼過ぎから夜のはじめ頃にかけて雨や雷\\n雨の所があるでしょう。\\n 奈良県では、高温が予想され、熱中症の危険が特に高くなる見込みです。\\n暑さを避け、水分をこまめに補給するなど、十分な対策をとってください。\\n\\n 明日の奈良県は、高気圧に覆われておおむね晴れますが、強い日射の影響\\nで、大気の状態が不安定となるため、夕方からは雨や雷雨の所がある見込み\\nです。'},\n", " 'forecasts': [{'date': '2017-08-20',\n", " 'dateLabel': '今日',\n", " 'image': {'height': 31,\n", " 'title': '晴時々曇',\n", " 'url': 'http://weather.livedoor.com/img/icon/2.gif',\n", " 'width': 50},\n", " 'telop': '晴時々曇',\n", " 'temperature': {'max': {'celsius': '35', 'fahrenheit': '95.0'},\n", " 'min': None}},\n", " {'date': '2017-08-21',\n", " 'dateLabel': '明日',\n", " 'image': {'height': 31,\n", " 'title': '晴時々曇',\n", " 'url': 'http://weather.livedoor.com/img/icon/2.gif',\n", " 'width': 50},\n", " 'telop': '晴時々曇',\n", " 'temperature': {'max': {'celsius': '35', 'fahrenheit': '95.0'},\n", " 'min': {'celsius': '24', 'fahrenheit': '75.2'}}},\n", " {'date': '2017-08-22',\n", " 'dateLabel': '明後日',\n", " 'image': {'height': 31,\n", " 'title': '曇り',\n", " 'url': 'http://weather.livedoor.com/img/icon/8.gif',\n", " 'width': 50},\n", " 'telop': '曇り',\n", " 'temperature': {'max': None, 'min': None}}],\n", " 'link': 'http://weather.livedoor.com/area/forecast/290010',\n", " 'location': {'area': '近畿', 'city': '奈良', 'prefecture': '奈良県'},\n", " 'pinpointLocations': [{'link': 'http://weather.livedoor.com/area/forecast/2920100',\n", " 'name': '奈良市'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2920200',\n", " 'name': '大和高田市'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2920300',\n", " 'name': '大和郡山市'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2920400', 'name': '天理市'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2920500', 'name': '橿原市'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2920600', 'name': '桜井市'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2920701',\n", " 'name': '五條市北部'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2920800', 'name': '御所市'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2920900', 'name': '生駒市'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2921000', 'name': '香芝市'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2921100', 'name': '葛城市'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2921200', 'name': '宇陀市'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2932200', 'name': '山添村'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2934200', 'name': '平群町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2934300', 'name': '三郷町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2934400', 'name': '斑鳩町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2934500', 'name': '安堵町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2936100', 'name': '川西町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2936200', 'name': '三宅町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2936300',\n", " 'name': '田原本町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2940100', 'name': '高取町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2940200',\n", " 'name': '明日香村'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2942400', 'name': '上牧町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2942500', 'name': '王寺町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2942600', 'name': '広陵町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2942700', 'name': '河合町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2944100', 'name': '吉野町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2944200', 'name': '大淀町'},\n", " {'link': 'http://weather.livedoor.com/area/forecast/2944300',\n", " 'name': '下市町'}],\n", " 'publicTime': '2017-08-20T11:00:00+0900',\n", " 'title': '奈良県 奈良 の天気'}" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "content" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ここでは使っているのは,[JSON](http://www.json.org/json-ja.html)という形式。JSONは値に対して,それが何であるかというキーがつけられている。\n", "標準ライブラリの[urllib](https://docs.python.jp/3/library/urllib.html)を使ってデータを取得。\n", "[json](https://docs.python.jp/3/library/json.html)モジュールでPythonの[辞書](https://docs.python.jp/3/tutorial/datastructures.html#dictionaries)にデコード(変換,翻訳)している。\n", "データを印字してみよう。" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "奈良県 奈良 の天気\n" ] } ], "source": [ "print(content['title'])" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 近畿地方は、高気圧に覆われて、晴れています。\n", "\n", " 今日の奈良県は、高気圧に覆われておおむね晴れますが、強い日射の影響\n", "で大気の状態が不安定となるため、昼過ぎから夜のはじめ頃にかけて雨や雷\n", "雨の所があるでしょう。\n", " 奈良県では、高温が予想され、熱中症の危険が特に高くなる見込みです。\n", "暑さを避け、水分をこまめに補給するなど、十分な対策をとってください。\n", "\n", " 明日の奈良県は、高気圧に覆われておおむね晴れますが、強い日射の影響\n", "で、大気の状態が不安定となるため、夕方からは雨や雷雨の所がある見込み\n", "です。\n" ] } ], "source": [ "print(content['description']['text'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "予報は今日,明日など複数あるので,[`for`](https://docs.python.jp/3/tutorial/controlflow.html#for-statements)文で反復する。" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "今日\n", "明日\n", "明後日\n" ] } ], "source": [ "for forecast in content['forecasts']:\n", " print(forecast['dateLabel'])" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "今日2017-08-20晴時々曇\n", "明日2017-08-21晴時々曇\n", "明後日2017-08-22曇り\n" ] } ], "source": [ "for forecast in content['forecasts']: \n", " print(forecast['dateLabel'] + forecast['date'] + forecast['telop'])" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "今日2017-08-20晴時々曇\n", "None\n", "{'celsius': '35', 'fahrenheit': '95.0'}\n", "明日2017-08-21晴時々曇\n", "{'celsius': '24', 'fahrenheit': '75.2'}\n", "{'celsius': '35', 'fahrenheit': '95.0'}\n", "明後日2017-08-22曇り\n", "None\n", "None\n" ] } ], "source": [ "for forecast in content['forecasts']: \n", " print(forecast['dateLabel'] + forecast['date'] + forecast['telop'])\n", " print(forecast['temperature']['min'])\n", " print(forecast['temperature']['max'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "気温データは摂氏(celsius)と[華氏](https://ja.wikipedia.org/wiki/華氏)(fahrenheit)がある。" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "91.4" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def c2f(c):\n", " return 9/5 * c + 32\n", "c2f(33)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "摂氏だけ表示したいが…" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "今日2017-08-20晴時々曇\n" ] }, { "ename": "TypeError", "evalue": "'NoneType' object is not subscriptable", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m<ipython-input-9-d35fb1e416b7>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mforecast\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mcontent\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'forecasts'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mforecast\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'dateLabel'\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mforecast\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'date'\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mforecast\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'telop'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mforecast\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'temperature'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'min'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'celsius'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mforecast\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'temperature'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'max'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'celsius'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mTypeError\u001b[0m: 'NoneType' object is not subscriptable" ] } ], "source": [ "for forecast in content['forecasts']: \n", " print(forecast['dateLabel'] + forecast['date'] + forecast['telop'])\n", " print(forecast['temperature']['min']['celsius'])\n", " print(forecast['temperature']['max']['celsius'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "今日の最低気温が`None`なのでエラーが出でしまった。`None`であるときとないときを[`if`](https://docs.python.jp/3/tutorial/controlflow.html#if-statements)文で分けることにする。" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "今日2017-08-20晴時々曇\n", "--\n", "35\n", "明日2017-08-21晴時々曇\n", "24\n", "35\n", "明後日2017-08-22曇り\n", "--\n", "--\n" ] } ], "source": [ "for forecast in content['forecasts']: \n", " print(forecast['dateLabel'] + forecast['date'] + forecast['telop'])\n", " if forecast['temperature']['min'] != None:\n", " print(forecast['temperature']['min']['celsius'])\n", " else:\n", " print(\"--\")\n", " if forecast['temperature']['max'] != None:\n", " print(forecast['temperature']['max']['celsius'])\n", " else:\n", " print(\"--\" ) " ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "奈良県 奈良 の天気\n", " 近畿地方は、高気圧に覆われて、晴れています。\n", "\n", " 今日の奈良県は、高気圧に覆われておおむね晴れますが、強い日射の影響\n", "で大気の状態が不安定となるため、昼過ぎから夜のはじめ頃にかけて雨や雷\n", "雨の所があるでしょう。\n", " 奈良県では、高温が予想され、熱中症の危険が特に高くなる見込みです。\n", "暑さを避け、水分をこまめに補給するなど、十分な対策をとってください。\n", "\n", " 明日の奈良県は、高気圧に覆われておおむね晴れますが、強い日射の影響\n", "で、大気の状態が不安定となるため、夕方からは雨や雷雨の所がある見込み\n", "です。\n", "\n", "今日 (2017-08-20)晴時々曇 -- 35\n", "明日 (2017-08-21)晴時々曇 24 35\n", "明後日(2017-08-22)曇り -- --\n" ] } ], "source": [ "from urllib.request import urlopen\n", "import json\n", "\n", "def weather_forecast(city):\n", " url = \"http://weather.livedoor.com/forecast/webservice/json/v1?city=\" + str(city)\n", " response = urlopen(url)\n", " content = json.loads(response.read().decode(\"utf8\"))\n", " print(content['title'])\n", " print(content['description']['text'])\n", " print()\n", " for forecast in content['forecasts']: \n", " dateLabel = forecast['dateLabel']\n", " date = forecast['date']\n", " telop = forecast['telop']\n", " if forecast['temperature']['min'] != None:\n", " tmin = forecast['temperature']['min']['celsius']\n", " else:\n", " tmin = \"--\"\n", " if forecast['temperature']['max'] != None:\n", " tmax = forecast['temperature']['max']['celsius']\n", " else:\n", " tmax = \"--\" \n", " print(f\"{dateLabel: <3}({date}){telop: <7}{tmin:>3}{tmax:>3}\")\n", "\n", "city = 290010 # 奈良\n", "weather_forecast(city)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "それぞれの日の予報と最低最高気温を印字する際,文字を揃えるために[フォーマット済み文字列リテラル](https://docs.python.jp/3/whatsnew/3.6.html#whatsnew36-pep498)(f文字列)を使っている。\n", "f文字列はPythonバージョン3.6から使えるようになった新機能。\n", "[文字列のフォーマット](https://docs.python.jp/3/tutorial/inputoutput.html#fancier-output-formatting)にはいろいろあるが,直感的で分かりやすい。\n", "`{}`で囲んだ変数名の後の`:`以降にフォーマットを記す。\n", "次の例で最初の文字は,文字列の値が指定した桁数よりも短いときに埋めるために使う文字。\n", "既定は半角の空白なので,上の関数では全角の空白を指定した。\n", "`<`は左揃え,`^`は中揃え,`>`は右揃え,最後の数字は桁数。" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "____Kobe\n", "___Kyoto\n", "____Nara\n", "___Osaka\n", "Wakayama\n" ] } ], "source": [ "cities = [\"Kobe\", \"Kyoto\", \"Nara\", \"Osaka\", \"Wakayama\"]\n", "for city in cities:\n", " print(f\"{city:_>8}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 地点の辞書を作る\n", "\n", "[全国の地点定義表](http://weather.livedoor.com/forecast/rss/primary_area.xml)は[RSS](https://ja.wikipedia.org/wiki/RSS)で提供されている。RSSはXML形式の派生なので,[xml](https://docs.python.jp/3/library/xml.html)で[解析できる](http://diveintopython3-ja.rdy.jp/xml.html)。まずJSONと同様にurllibで読む。" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from urllib.request import urlopen\n", "url = \"http://weather.livedoor.com/forecast/rss/primary_area.xml\"\n", "response = urlopen(url)\n", "data = response.read()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "XMLはツリー構造をしているので,xmlモジュールを使ってルートを取得する。" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import xml.etree.ElementTree as et\n", "root = et.fromstring(data)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "<Element 'channel' at 0x000002942B533F48>" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "root[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`root`はリストになっていて,最初の要素がデータを含む`channel`である。その中から地点情報を含むノードを検索(`find`)する。各県の下に地点のデータがある。ここでは地点名(`title`)と地点番号(`id`)を辞書に格納する。" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": true }, "outputs": [], "source": [ "loc = {}\n", "for pref in root[0].find('{http://weather.livedoor.com/%5C/ns/rss/2.0}source'):\n", " for city in pref.findall(\"city\"):\n", " loc[city.attrib['title']] = int(city.attrib['id'])" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "260010" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "loc['京都']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "以上をモジュールにまとめる。`weather.py`として保存する。" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from urllib.request import urlopen\n", "import xml.etree.ElementTree as et\n", "import json\n", "\n", "def get_loc():\n", " url = \"http://weather.livedoor.com/forecast/rss/primary_area.xml\"\n", " response = urlopen(url)\n", " data = response.read()\n", " root = et.fromstring(data)\n", " loc = {}\n", " for pref in root[0].find('{http://weather.livedoor.com/%5C/ns/rss/2.0}source'):\n", " for city in pref.findall(\"city\"):\n", " loc[city.attrib['title']] = int(city.attrib['id'])\n", " return loc\n", "\n", "loc = get_loc()\n", "def forecast(city):\n", " url = \"http://weather.livedoor.com/forecast/webservice/json/v1?city=\" + str(city)\n", " response = urlopen(url)\n", " content = json.loads(response.read().decode(\"utf8\"))\n", " print(content['title'])\n", " print(content['description']['text'])\n", " print()\n", " for forecast in content['forecasts']: \n", " dateLabel = forecast['dateLabel']\n", " date = forecast['date']\n", " telop = forecast['telop']\n", " if forecast['temperature']['min'] != None:\n", " tmin = forecast['temperature']['min']['celsius']\n", " else:\n", " tmin = \"--\"\n", " if forecast['temperature']['max'] != None:\n", " tmax = forecast['temperature']['max']['celsius']\n", " else:\n", " tmax = \"--\" \n", " print(f\"{dateLabel: <3}({date}){telop: <7}{tmin:>3}{tmax:>3}\") " ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "京都府 京都 の天気\n", " 近畿地方は、高気圧に覆われて、晴れています。\n", "\n", " 今日の京都府は、高気圧に覆われておおむね晴れますが、強い日射の影響\n", "で大気の状態が不安定となるため、雷を伴い激しい雨の降る所がある見込み\n", "です。\n", " 京都府では高温が予想され、熱中症の危険が特に高くなる見込みです。暑\n", "さを避け、水分をこまめに補給するなど、十分な対策をとってください。\n", "\n", " 明日の京都府は、高気圧に覆われておおむね晴れますが、午後は強い日射\n", "の影響で大気の状態が不安定となるため、雨や雷雨の所があるでしょう。\n", "\n", "今日 (2017-08-20)晴時々曇 -- 35\n", "明日 (2017-08-21)晴時々曇 25 36\n", "明後日(2017-08-22)曇り -- --\n" ] } ], "source": [ "import weather\n", "loc = weather.loc\n", "weather.forecast(loc['京都'])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.2" } }, "nbformat": 4, "nbformat_minor": 2 }