日記/2020-7
<< | 2020-7 | >> | ||||
S | M | T | W | T | F | S |
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
2020-7-23
ベランダの花・続続続続
2020/06/07
2020/06/10
2020/06/12
2020/06/17
2020/06/21
2020/06/24
2020/07/23
2020-7-12
室内のCO2濃度が見たい・続続
「室内のCO2濃度が見たい」、「室内のCO2濃度が見たい・続」の続き。
室内のCO2濃度をLametric Timeで表示できるようになったが、自己満足ついでに値の推移をグラフで見たくなった。
昨日掲載したスクリプト(room.py)は、cronで5分おきに実行しているのだが、ログとして、室温、湿度、CO2濃度をroom.logへ出力している。
*/5 * * * * cd /home/pi/lametric/;./room.py >> /home/pi/lametric/room.log 2> /dev/null
room.logの内容は以下のような感じ。
date,temperature,humidity,co2 2020-07-11 13:50,27,68,513 2020-07-11 13:55,27,68,519 2020-07-11 14:00,27,68,507 2020-07-11 14:05,27,68,508 2020-07-11 14:10,27,68,505
これをグラフ化してみた。
#!/usr/bin/env python3 import pandas as pd import matplotlib.pyplot as plt import re import pprint df = pd.read_csv('room.log', index_col='date') period = int(-1 * (60 / 5) * 24 * 1) ltst = df[period:].interpolate() data1 = ltst.loc[:, 'temperature'] data2 = ltst.loc[:, 'humidity'] data3 = ltst.loc[:, 'co2'] xt = [] xl = [] idx = ltst.index.values.tolist() for i in idx: if '00:00' in i: xt.append(i) xl.append(re.search(r'(\d\d-\d\d) ', i).group()) elif '12:00' in i: xt.append(i) xl.append('') else: xt.append('') xl.append('') plt.style.use('seaborn-darkgrid') fig, [ax1, ax2, ax3] = plt.subplots(3, 1, sharex='col') fig.set_figwidth(12.8) fig.set_figheight(9.6) ax1.plot(data1, color='indianred') ax1.set_ylabel('temperature') ax2.plot(data2, color='royalblue') ax2.set_ylabel('humidity') ax3.plot(data3, color='seagreen') ax3.set_ylabel('co2') ax3.set(xticks=xt, xticklabels=xl) plt.tight_layout() plt.savefig('graph.png')
以下ができたグラフ(graph.png)。一日分のグラフで、9行目で変数periodの値を決めている、右辺の最後の数字が日数を表している。
12時過ぎのCO2濃度の急上昇は、昼飯でコンロに火を付けたためと思われる。換気したら値が一気に下がり、一緒に温度、湿度も急降下していることが見て取れる。
だからなに、って話ですが。
参考
2020-7-11
室内のCO2濃度が見たい・続
先日書いた「室内のCO2濃度が見たい」の続き。
2020/06/20にBanggood.comに発注したCO2濃度センサー「MH-Z19」だが、
昨日(2020/07/10)届いた。発注から到着まで20日間かかったことになる(納期は10日から30日となっていた)。
なお、届いたのは「MH-Z19B」だった。
早速、こちらのページを参考に、もともと近所のアメダスの情報やNature Remoの内蔵センサーの値をLametric Timeへ表示するために使っていたRaspberry Pi 3Bに接続してみた。金属製の台の上に置いてあるため、センサーの下にダンボールを貼り付けてある。
センサーの値も問題なく取得できたので、これをLametric Timeに表示してみた。
#!/usr/local/bin/python3 import requests import json import datetime import subprocess url = 'https://api.nature.global/1/devices' headers = { 'contentType': 'application/json', 'Authorization': 'Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' } res = requests.get(url, headers=headers) data = res.json() hum = str(data[0]['newest_events']['hu']['val']) temp = str(round(data[0]['newest_events']['te']['val'], 1)) mh = subprocess.check_output(['sudo', 'python3', '-m', 'mh_z19']).decode('utf-8') mh = json.loads(mh) co2 = str(mh['co2']) print(f"{datetime.datetime.today().strftime('%Y-%m-%d %H:%M')},{temp},{hum},{co2}") hum = hum + '%' temp = temp + '°C' co2 = co2 + 'ppm' disp = { 'frames': [ { 'index' : 0, 'text' : temp, 'icon' : '12464' }, { 'index' : 1, 'text' : hum, 'icon' : '12184' }, { 'index' : 2, 'text' : co2, 'icon' : '32936' } ] } disp = json.dumps(disp) headers = { 'X-Access-Token': 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy', 'Cache-Control': 'no-cache', 'Accept': 'application/json' } url = "https://developer.lametric.com/api/V1/dev/widget/update/com.lametric.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz/1" res = requests.post(url, disp, headers=headers)
上記のスクリプトをcronで5分おきに実行し、表示する情報を更新している。
表示は順に、時刻 -> 日付 -> 近所のアメダスの気温 -> 天気と降水量 -> 風速 -> 室温 -> 湿度 -> CO2濃度 となっている(アメダスの情報は別のスクリプトで更新している)。
まあ、自己満足以外の何物でもないが、満足満足。
参考
2020-7-5
Googleカレンダーに阪神とプロ野球の日程をインポートするためのスクレイピング・2020年版
先月(2020/06/19)とうとうプロ野球が開幕した。阪神ファンとしては、2020/07/05現在、早くもシーズン終わったんじゃねえかとも思える状況だが、それはそれとして、昨年以下のようなスクリプトを紹介していた。
- Googleカレンダーに阪神タイガースの試合日程をインポートするためスクレイピング
- Googleカレンダーにプロ野球の日程をインポートするためスクレイピング
- Googleカレンダーにプロ野球の日程をインポートするためスクレイピング・続
早速今年の日程をGoogleカレンダーにインポートしようとしてみたのだが、これが素直に動いてくれない。
以下に動かすまでにやったことを示す。
阪神タイガースの日程
まずはスクリプトの修正。
- オリックスが「bs」だったのが「b」に変わってたのに対応。
- 年を「2020」に、日にちを今年の変則日程に応じて変更した。
- いちいち「JERAセ・リーグ公式戦」と表示されるのがうるさいので消した。
#!/usr/bin/python3 #coding: utf-8 #scrapingtigers.py import re import datetime import urllib.request import requests import pprint from bs4 import BeautifulSoup data = {} year = '2020' team = { 't':'阪神', 's':'ヤクルト', 'd':'中日', 'h':'ソフトバンク', 'e':'楽天', 'f':'日本ハム', 'l':'西武', 'db':'DeNA', 'm':'ロッテ', 'b':'オリックス', 'g':'巨人', 'c':'広島', } head = "Subject, Start Date, Start Time, End Date, End Time, Description, Location" print(head) #month_days = {'03':'31', '04':'30', '05':'31', '06':'30', '07':'31', '08':'31', '09':'30'} month_days = {'06':'30', '07':'31', '08':'31', '09':'30', '10':'31', '11':'30'} for month in month_days.keys(): data.setdefault(month, {}) for day in range(int(month_days[month])): data[month].setdefault(day + 1, {}) data[month][day + 1].setdefault('date', year + '/' + month + '/' + ('0' + str(day + 1))[-2:]) for month in month_days.keys(): html = requests.get("https://m.hanshintigers.jp/game/schedule/" + year + "/" + month + ".html") soup = BeautifulSoup(html.text, features="lxml") day = 1 for tag in soup.select('li.box_right.gameinfo'): text = re.sub(' +', '', tag.text) info = text.split("\n") if len(info) > 3: if info[1] == '\xa0' or re.match('JERAセ・リーグ公式戦', info[1]): info[1] = '' data[month][day].setdefault('gameinfo', info[1]) data[month][day].setdefault('start', info[2]) data[month][day].setdefault('stadium', info[3]) if re.match('オールスターゲーム', info[2]): data[month][day]['gameinfo'] = info[2] data[month][day]['start'] = '18:00' text = str(tag.div) if text: m = re.match(r'^.*"nologo">(\w+)<.*$', text, flags=(re.MULTILINE|re.DOTALL)) if m: gameinfo = m.group(1) data[month][day].setdefault('gameinfo', gameinfo) m = re.match(r'^.*"logo_left (\w+)">.*$', text, flags=(re.MULTILINE|re.DOTALL)) if m: team1 = m.group(1) data[month][day].setdefault('team1', team[team1]) m = re.match(r'^.*"logo_right (\w+)">.*$', text, flags=(re.MULTILINE|re.DOTALL)) if m: team2 = m.group(1) data[month][day].setdefault('team2', team[team2]) day += 1 for month in month_days.keys(): for day in data[month].keys(): if data[month][day].get('start'): m = re.match(r'(\d+):(\d+)', data[month][day]['start']) if m: sthr = m.group(1) stmn = m.group(2) start = datetime.datetime(int(year), int(month), int(day), int(sthr), int(stmn), 0) delta = datetime.timedelta(hours=4) end = start + delta sttm = start.strftime("%H:%M:%S") entm = end.strftime("%H:%M:%S") summary = '' if data[month][day]['gameinfo']: summary = data[month][day]['gameinfo'] + " " if not re.match('オールスターゲーム', data[month][day]['gameinfo']): summary += data[month][day]['team1'] + "対" + data[month][day]['team2'] #head = "Subject, Start Date, Start Time, End Date, End Time, Description, Location" print(f"{summary}, {data[month][day]['date']}, {sttm}, {data[month][day]['date']}, {entm}, {summary}, {data[month][day]['stadium']}")
これぐらいの修正で動きそうなものだが(環境はWindow10のWSL(Ubuntu20.04))、
SSL routines:tls12_check_peer_sigalg:wrong signature type
みたいなエラーを吐いて止まってしまう。
原因は、Ubuntu20.04にデフォルトで入ってるOpenSSLのバージョンが古いため。1.1.1fが入ってるんだが、これを1.1.1gへ上げれば、無事動くようになる。バージョンアップはaptではダメで、ソースコードからコンパイルする必要があるようなので(少なくとも私はそうした)ググってきちんと調べてやってください。
後は出力結果をcsvファイルへ吐き出して、それをGoogleカレンダーへインポートすればOK。
プロ野球の日程
こちらも変なエラーに悩まされたが、環境のアップデートではなく、スクリプトの修正で事足りた。
- 年を「2020」に、日にちを今年の変則日程に応じて変更した。
- SSL認証でエラーが出ないよう、対応を追記。
#!/usr/bin/python3 #coding: utf-8 #scrapingnpb2.py import sys import re import datetime import pandas as pd import ssl ssl._create_default_https_context = ssl._create_unverified_context print("Subject, Start Date, Start Time, End Date, End Time, Description, Location") year = '2020' #months = ['03', '04', '05', '06', '07', '08', '09'] months = ['06', '07', '08', '09', '10', '11'] # 0, 1, 2, 3, 4, 5 #(0, '3/29(金)', 'DeNA - 中日', '横\u3000浜 18:30', nan, nan) for month in months: url = "http://npb.jp/games/" + year + "/schedule_" + month + "_detail.html" tb = pd.io.html.read_html(url) for row in tb[0].itertuples(name=None): card = '' md = re.sub(r'(.*)', '', row[1]) ymd = year + '/' + md sttm = '' entm = '' place = '' if row[2] == row[2]: card = re.sub(' - ', '対', row[2]) if row[3] == row[3]: place_time = row[3].split(' ') if len(place_time) > 1: (sthr, stmn) = place_time[1].split(':') (mon, day) = md.split('/') start = datetime.datetime(int(year), int(mon), int(day), int(sthr), int(stmn), 0) delta = datetime.timedelta(minutes=200) end = start + delta sttm = start.strftime("%H:%M:%S") entm = end.strftime("%H:%M:%S") place = re.sub(r'\s+', '', place_time[0]) else: sttm = '18:00:00' entm = '21:20:00' place = place_time[0] if len(sys.argv) > 1: m = re.search(sys.argv[1], card) if m: print(f"{card}, {ymd}, {sttm}, {ymd}, {entm}, {card}, {place}") elif card != '': print(f"{card}, {ymd}, {sttm}, {ymd}, {entm}, {card}, {place}")
当初は以下のようなエラーに悩まされた。
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1108)>
去年との違いは、NPBのサイトが「https」になっていたこと。
でも、pandasのサイトによると、以下のようにpandas.read_htmlは(というかlxmlは)httpsには対応していない。
pandas.read_html(io, match='.+', flavor=None, header=None, index_col=None, skiprows=None, attrs=None, parse_dates=False, thousands=',', encoding=None, decimal='.', converters=None, na_values=None, keep_default_na=True, displayed_only=True) Read HTML tables into a list of DataFrame objects. Parameters:io:str, path object or file-like object A URL, a file-like object, or a raw string containing HTML. Note that lxml only accepts the http, ftp and file url protocols. If you have a URL that starts with 'https' you might try removing the 's'.
なので、スクリプト上「http」にアクセスしているのはそれはそれで正しいはずなのだが、なぜかSSL認証エラーになってしまう(サイト側でhttpへのアクセスをhttpsへリダイレクトしてるためか?)。
で、しかたがないので、SSL認証エラーを無視するようにした次第。
後はこれまた出力結果をcsvファイルへ吐き出して、それをGoogleカレンダーへインポートすればOK。