シェルスクリプトマガジン

特集3 PythonとSeleniumを活用 自動操作とデータ分析(Vol.67記載)

著者:川嶋 宏彰

最近、プログラミング言語「Python」による自動化やデータ分析が注目されています。本特集では、Pythonと、Webブラウザを自動操作するためのライブラリ「Selenium WebDriver」を用いて、インターネットから取得できるオープンデータを例に、Webブラウザの自動操作方法およびデータ分析方法を分かりやすく紹介します。

シェルスクリプトマガジン Vol.67は以下のリンク先でご購入できます。

図4 非headlessモードのサンプルコード(sample.py)

import time
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://www.google.com/')
time.sleep(5)
search_box = driver.find_element_by_name('q')
search_box.send_keys('ChromeDriver')
search_box.submit()
time.sleep(5)
driver.quit()

図7 headlessモードのサンプルコード

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
driver = webdriver.Chrome(options=options)

driver.get('https://www.google.com/')
print(driver.title)

search_box = driver.find_element_by_name('q')
search_box.send_keys('ChromeDriver')
search_box.submit()
print(driver.title)

driver.save_screenshot('search_results.png')
driver.quit()

図24 Seleniumを用いた気温データの自動取得プログラム

import time
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select

# ダウンロード先フォルダの指定
dldir_path = Path('csv')  # csv という名前のフォルダとする
dldir_path.mkdir(exist_ok=True)  # なければ作成
download_dir = str(dldir_path.resolve())  # 絶対パスを取得
print("download_dir: " + download_dir)

options = webdriver.ChromeOptions()
options.add_experimental_option('prefs', {    # Chrome のオプションに
  'download.default_directory': download_dir  # 絶対パスで指定  
})

driver = webdriver.Chrome(options=options)

wait = WebDriverWait(driver, 10)  # 明示的待機用 (Timeout 10秒)

# 自動操作開始
driver.get('https://www.data.jma.go.jp/gmd/risk/obsdl/index.php')

# 「地点を選ぶ」
xpath = '//div[@class="prefecture" and text()="東京"]'
time.sleep(2)
driver.find_element_by_xpath(xpath).click()

xpath = '//div[@class="station" and contains(@title, "地点名:東京")]'
time.sleep(2)                                # (★)
driver.find_element_by_xpath(xpath).click()  # (★)

# 「項目を選ぶ」
driver.find_element_by_id('elementButton').click()

xpath = '//span[text()="月別値"]/preceding-sibling::input'
time.sleep(2)
driver.find_element_by_xpath(xpath).click()

css = '#日最高気温の平均'
time.sleep(2)
driver.find_element_by_css_selector(css).click()

# 「期間を選ぶ」
driver.find_element_by_id('periodButton').click()

time.sleep(2)
# <select>内の<option>要素を選択
Select(driver.find_element_by_name('iniy')).select_by_value('2010')
Select(driver.find_element_by_name('inim')).select_by_value('1')
time.sleep(2)  # いったん止めてみる
Select(driver.find_element_by_name('endy')).select_by_value('2019')
Select(driver.find_element_by_name('endm')).select_by_value('12')
time.sleep(2)

# 「CSVファイルをダウンロード」
driver.find_element_by_id('csvdl').click()
time.sleep(2)

driver.quit()

図27 e-StatのAPI機能を利用した家計調査データを取得するプログラム

import sys
import urllib
import urllib.request
import json
import calendar
import matplotlib.pyplot as plt
import japanize_matplotlib  # japanize-matplotlib を使う場合
import pandas as pd
from scipy import stats 

url = 'https://api.e-stat.go.jp/rest/3.0/app/json/getStatsData?'

app_id = '<e-Statマイページで取得したアプリケーションIDを挿入>'

cat01 = '010800150' # アイスクリーム・シャーベット
# cat01 = '010800130' # チョコレート
# cat01 = '011100030' # ビール

remove_month = 0  # 特定月を除く場合は1-12のいずれかを指定

# 指定する数字の桁数は決まっているので注意
keys = {
    'appId' : app_id,
    'lang' : 'J',
    'statsDataId' : '0003343671',  # 家計調査データ
    'metaGetFlg' : 'Y',
    'cntGetFlg' : 'N',
    'cdTab' : '01',  # 金額
    'cdTimeFrom' : '2010000101',  # 2010年1月から
    'cdTimeTo' : '2019001212',  # 2019年12月まで
    'cdArea' : '00000',  # 全国
    'cdCat01' : cat01,
    'cdCat02' : '03'  # 二人以上世帯
}

params = urllib.parse.urlencode(keys)
r_obj = urllib.request.urlopen(url + params)  # データを取得
r_str = r_obj.read()
res = json.loads(r_str)  # Pythonの辞書へ
stats_data = res['GET_STATS_DATA']['STATISTICAL_DATA']

class_obj = stats_data['CLASS_INF']['CLASS_OBJ']  # メタ情報

if 'DATA_INF' not in stats_data:  # ['DATA_INF']が入らないときのチェック用
    for co in class_obj:
        if 'CLASS' not in co:
            print("ERROR: Check params @id= {}, @name= {}" \
                .format(co['@id'], co['@name']))
    sys.exit(1)

values = stats_data['DATA_INF']['VALUE']  # 統計データの数値情報を取得

# メタ情報(CLASS_INF)から取得した品目名称を図のタイトルに使う
title = [co['CLASS']['@name'] for co in class_obj if co['@id'] == 'cat01'][0]
print(title)

# 各要素が [年, 月, 支出金額] の2次元リストにする
data = [[int(v['@time'][0:4]), int(v['@time'][6:8]), int(v['$'])] for v in values]
print("n =", len(data))  # 120 = 10年 x 12カ月

# Pandasデータフレームの準備
df = pd.DataFrame(data, columns=['year', 'month', '支出(円)'])
df['days'] = [calendar.monthrange(df.loc[i, 'year'], df.loc[i, 'month'])[1] for i in df.index]  # 各月の日数
df['支出(円/日)'] = df['支出(円)'] / df['days']  # 1日あたりの支出金額
df['y/m'] = df['year'].astype(str) + '/' + df['month'].astype(str)  # 結合用

# 気象庁の気温データとマージ
df_jma = pd.read_csv('csv/data.csv', skiprows=5, header=None, usecols=[0,1], encoding='Shift_JIS')
df_jma.columns = ['y/m', '平均最高気温(℃)']
df = pd.merge(df, df_jma, on='y/m')  # データフレームの結合

if remove_month > 0:
    df = df.query('month != @remove_month')  # 特定月を除く場合

# 相関係数を計算
corr, _ = stats.pearsonr(df['平均最高気温(℃)'], df['支出(円/日)'])
corr_str = "相関係数: {:2f}".format(corr)
print(corr_str)

# 散布図をプロット
ax = df.plot(kind='scatter', x='平均最高気温(℃)', y='支出(円/日)')
ax.set_title(title + ', ' + corr_str)
plt.show()