Googleカレンダーに阪神とプロ野球の日程をインポートするためのスクレイピング・2020年版

最終更新時間:2020年07月05日 13時32分11秒

calendar.png

 先月(2020/06/19)とうとうプロ野球が開幕した。阪神ファンとしては、2020/07/05現在、早くもシーズン終わったんじゃねえかとも思える状況だが、それはそれとして、昨年以下のようなスクリプトを紹介していた。

 早速今年の日程を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。