11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめに

こんにちは、京セラコミュニケーションシステム 西田(@kccs_hiromi-nishida)です。
今回は前編に続いて、後編の記事となります!

たとえば基幹システム等の既存システムから何かデータを取得・連携して分析したいと思っても、費用対効果もわからないうちから正式にデータ連携出力を依頼するのはハードルが少し高いな・・と思ったことはありませんか?

そんな時、まずはスクレイピングで気軽に試してみるのもよいかもしれません。

前編、後編を合わせて読んでいただくことで、スクレイピングからデータ連携・分析までの一連の流れを確認できますので、ぜひ両編とも目を通して頂ければと思います。
そして、この記事が業務改善の一助になれれば幸いです!

本記事は2022年12月ごろに作成しております。よって、引用している文章などはこの時点での最新となります。ご了承ください。

この記事の対象者

  • WEBサイトからのデータ収集を自動化してみたいと思っている方
  • Pythonを使ったスクレイピングに興味のある方
  • CSVデータをBigQueryに連携し、分析してみたい方

今回の記事の範囲

前回の記事ではSelenium+Pythonを使ってWEBサイトからデータを自動収集してCSVファイル出力するところをご紹介しました。

今回の記事範囲は出力したCSVファイルをCloud Storage APIを使ってCloud Storageにアップロードし、BigQueryから外部テーブルでCloud Storage上のCSVファイルを参照、そのデータをコネクテッドシートで分析するところ(下図のオレンジ枠の範囲)となります!

img12.png

前提

前編記事からの続きとなる記事ですので、前編記事の前提もそのまま引き継いでいます。

また、Cloud Storage、BigQueryを利用しますので、Google Cloudの利用開始がまだの方は以下サイトより利用登録を行うことができますので、利用を開始しておいてください。

[参照]公式サイト・ドキュメント
無料トライアルと無料枠
  上記サイトの「無料で開始」ボタンから登録が可能
Google Cloud の無料プログラム
  Google Cloudの無料プログラムの詳細を確認できます

Cloud Storage APIを使えるようにする

PythonからCloud Storageの操作を行うために、今回はCloud Storage APIを使います。
順を追って利用方法を説明します。

サービスアカウントの作成

使用画像について
特別な記載のない限り、画像はGoogle Cloud - IAMと管理画面をキャプチャしたものとなります

サービスアカウント1とはプログラムからGoogle APIへアクセスするために使用される特殊なGoogleアカウントです。
今回、PythonプログラムからCloud Storage APIを使用するため、まずサービスアカウントの作成が必要となります。

Google Cloudにアクセスしてハンバーガーメニューを開き、IAMと管理 ⇒ サービスアカウントを選択します。
サービスアカウントの開き方
画面上部の[サービスアカウントを作成]を選択してください。
サービスアカウントの開き方
サービスアカウント名とサービスアカウントの説明を入力し、[作成して続行]を選択してください。
サービスアカウント作成1
[ロールを追加]を選択し、Cloud Storage ⇒ ストレージ管理者を選択し、[完了]を選択してください。これでサービスアカウントの作成は完了です。
サービスアカウント作成2 サービスアカウント作成3

サービスアカウントキーの作成

次に作成したサービスアカウントのキーを作成します。
※このキーを使って認証を行います。

先ほど作成したサービスアカウントを選択します。
作成したサービスアカウント
[キー]を選択し、[鍵を追加]⇒[新しい鍵を作成]を選択してください。
キー作成1
[JSON]を選択し、[作成]を選択すると、キーが自動的にダウンロードされます。
キー作成2
このキーをわかりやすい場所に移動させておきましょう。
キー配置
※私の場合は、sample.py(前編で作成したプログラム)と同じ階層に配置し、わかりやすい名前にリネームしました(storage_key.json)

Google Cloud Storage用のライブラリをインストール

Python用のGoogle Cloud Storageのライブラリをインストールします。

コマンドプロンプト(Macの方はターミナル)を起動して以下を入力するだけでインストールが完了します。

インストールコマンド
pip install google-cloud-storage

Cloud Storageにバケットを作成

使用画像について
特別な記載のない限り、画像はGoogle Cloud - Cloud Storage画面をキャプチャしたものとなります

早速プログラミング!といきたいところですが、その前にCSVファイルをアップロードするバケットをCloud Storageに作成しておく必要があるので、作成しておきます。

Google Cloudにアクセスしてハンバーガーメニューを開き、Cloud Storage ⇒ バケットを選択します。
Cloud Storage-バケット
画面上部の[作成]を選択してください。
Cloud Storage-バケット
バケットの名前を入力し、[続行]を選択してください。
Cloud Storage-バケット作成画面1
ロケーションタイプは今回Regionとし、asia-northeast1を選択しました。
[続行]を選択してください。
Cloud Storage-バケット作成画面2
後の項目はとくに変更の必要はないので、[続行]を選択していき、最後[作成]を選択してください。バケットが作成されました。
Cloud Storage-バケット作成画面2

PythonからCloud Storageにファイルをアップロード

では、前編-CSVファイルを出力するからの続きに、順を追って記述していきましょう!

まずは、必要なモジュールをインポートします。

sample.py
# 冒頭部分に追記してください
from google.cloud import storage

次にサービスアカウントキーを使って認証を行い、バケットを取得します。

sample.py
# 保管したサービスアカウントキーを指定します
client = storage.Client.from_service_account_json("storage_key.json")
# 作成したCSVファイルアップロード先バケット名を指定します
bucket = client.get_bucket("sample_csv_upload_bucket")

バケットを取得できたら、そのバケットにCSVファイルをアップロードします。

sample.py
blob = bucket.blob(file_name)
blob.upload_from_filename(output_path)

ここまで記述できたら、sample.pyを実行してみてください。
指定したバケットにCSVファイルがアップロードされているはずです。
Cloud Storage-バケット画面

Cloud Storage上のCSVファイルをBigQueryのテーブルに読み込んでみよう

使用画像について
特別な記載のない限り、画像はGoogle Cloud - BigQuery画面をキャプチャしたものとなります

まず、BigQureyにデータセットを作成し、作成したデータセット内にテーブルを作成します。

データセットを作成する

Google Cloudにアクセスしてハンバーガーメニューを開き、BigQuery ⇒ SQLワークスペースを選択します。
BigQuery画面
データセットを作成するプロジェクトを選択 ⇒ データセットを作成を選択します。
これでデータセットの作成は完了です。
BigQuery画面
データセットIDを入力し、データのロケーションにはasia-northeast1を選択し、[データセットを作成]を選択してください。
※作成したCloud Storageのバケットのロケーションと合わせておいてください
データセット作成画面

作成したデータセットにテーブルを作成する

作成したデータセットを選択 ⇒ テーブルを作成を選択します。
テーブル作成画面1
テーブルの作成元にはGoogle Cloud Storageを選択してください。
選択すると、バケットからファイルを選択するための参照ボタンが表示されるので、[参照]ボタンを選択します。
テーブル作成画面2
バケットを選択 ⇒ CSVファイルを選択して、画面下部の選択ボタンを押下してください。
テーブル作成画面3 テーブル作成画面3
テーブル名を入力し、テーブルタイプは[外部テーブル]を選択してください。
テーブル作成画面4

スキーマは、[テキストとして編集]をONにすると、入力できる状態になるので、
テーブル作成画面5
以下のスキーマ定義を貼り付けてください。
※長いので折りたたんでいます。クリックすると展開します。

スキーマ定義
スキーマ定義
[
  {
    "mode": "NULLABLE",
    "name": "year",
    "type": "STRING",
    "description": "年"
  },
  {
    "mode": "NULLABLE",
    "name": "atm_p_local_ave",
    "type": "STRING",
    "description": "気圧_現地_平均"
  },
  {
    "mode": "NULLABLE",
    "name": "atm_p_sea_surf_ave",
    "type": "STRING",
    "description": "気圧_海面_平均"
  },
  {
    "mode": "NULLABLE",
    "name": "precipitation_total",
    "type": "STRING",
    "description": "降水量_合計"
  },
  {
    "mode": "NULLABLE",
    "name": "precipitation_max_day",
    "type": "STRING",
    "description": "降水量_最大_日"
  },
  {
    "mode": "NULLABLE",
    "name": "precipitation_max_hour",
    "type": "STRING",
    "description": "降水量_最大_1時間"
  },
  {
    "mode": "NULLABLE",
    "name": "precipitation_max_min",
    "type": "STRING",
    "description": "降水量_最大_10分間"
  },
  {
    "mode": "NULLABLE",
    "name": "temp_ave_daily_avg",
    "type": "STRING",
    "description": "気温_平均_日平均"
  },
  {
    "mode": "NULLABLE",
    "name": "temp_ave_daily_high",
    "type": "STRING",
    "description": "気温_平均_日最高"
  },
  {
    "mode": "NULLABLE",
    "name": "temp_ave_daily_min",
    "type": "STRING",
    "description": "気温_平均_日最低"
  },
  {
    "mode": "NULLABLE",
    "name": "temp_highest",
    "type": "STRING",
    "description": "気温_最高"
  },
  {
    "mode": "NULLABLE",
    "name": "temp_lowest",
    "type": "STRING",
    "description": "気温_最低"
  },
  {
    "mode": "NULLABLE",
    "name": "hum_avg",
    "type": "STRING",
    "description": "湿度_平均"
  },
  {
    "mode": "NULLABLE",
    "name": "hum_min",
    "type": "STRING",
    "description": "湿度_最小"
  },
  {
    "mode": "NULLABLE",
    "name": "wind_dir_speed_avg_wind_speed",
    "type": "STRING",
    "description": "風向・風速_平均風速"
  },
  {
    "mode": "NULLABLE",
    "name": "wind_dir_speed_max_wind_speed",
    "type": "STRING",
    "description": "風向・風速_最大風速_風速"
  },
  {
    "mode": "NULLABLE",
    "name": "wind_dir_speed_max_wind_dir",
    "type": "STRING",
    "description": "風向・風速_最大風速_風向"
  },
  {
    "mode": "NULLABLE",
    "name": "wind_dir_speed_max_inst_wind_speed",
    "type": "STRING",
    "description": "風向・風速_最大瞬間風速_風速"
  },
  {
    "mode": "NULLABLE",
    "name": "wind_dir_speed_max_inst_wind_dir",
    "type": "STRING",
    "description": "風向・風速_最大瞬間風速_風向"
  },
  {
    "mode": "NULLABLE",
    "name": "sunlight_hours",
    "type": "STRING",
    "description": "日照時間"
  },
  {
    "mode": "NULLABLE",
    "name": "solar_radiation_day_avg",
    "type": "STRING",
    "description": "全天日射量平均"
  },
  {
    "mode": "NULLABLE",
    "name": "snow_snowfall_total",
    "type": "STRING",
    "description": "雪_降雪_合計"
  },
  {
    "mode": "NULLABLE",
    "name": "snow_snowfall_max_day_total",
    "type": "STRING",
    "description": "雪_降雪_日合計の最大"
  },
  {
    "mode": "NULLABLE",
    "name": "snow_deepest_snow",
    "type": "STRING",
    "description": "最深積雪"
  },
  {
    "mode": "NULLABLE",
    "name": "cloud_cover_avg",
    "type": "STRING",
    "description": "雲量平均"
  },
  {
    "mode": "NULLABLE",
    "name": "atm_phenomenon_num_of_snow_days",
    "type": "STRING",
    "description": "大気現象_雪日数"
  },
  {
    "mode": "NULLABLE",
    "name": "atm_phenomenon_fog_days",
    "type": "STRING",
    "description": "大気現象_霧日数"
  },
  {
    "mode": "NULLABLE",
    "name": "atm_phenomenon_thunder_days",
    "type": "STRING",
    "description": "大気現象_雷日数"
  }
]

詳細オプションを開き、スキップするヘッダー行に[1]を入力し、[テーブルを作成]ボタンを選択してください。
テーブル作成画面6
テーブル作成が完了しました。
テーブル作成画面7

データを加工してViewを作る

今回の気象データの中には、よーく見ると数値だけではなく記号2も含まれています。

その為、今回テーブルのスキーマ定義で項目の方はすべて文字列型(STRING)にしました。
ですが、データを使って分析する際に、数値型でないとグラフなどを作成するのが難しくなります。
そこで文字列型を数値型(NUMERIC)に変換したViewを作成し、分析時はこのViewをインプットにしたいと思います。

作成したテーブルを選択し、クエリ ⇒ 新しいタブを選択してください。
View作成1
画面が開いたら、以下のSQL文を貼り付けて、保存 ⇒ ビューを保存を選択してください。
View作成2
※長いので折りたたんでいます。クリックすると展開します。
※SQL中の「★プロジェクト名★」は使用しているプロジェクト名に置き換えてください。

View作成用SQL
View作成用SQL
SELECT
  SAFE_CAST(`year` AS NUMERIC) AS `year`,
  SAFE_CAST(`atm_p_local_ave` AS NUMERIC) AS `atm_p_local_ave`,
  SAFE_CAST(`atm_p_sea_surf_ave` AS NUMERIC) AS `atm_p_sea_surf_ave`,
  SAFE_CAST(`precipitation_total` AS NUMERIC) AS `precipitation_total`,
  SAFE_CAST(`precipitation_max_day` AS NUMERIC) AS `precipitation_max_day`,
  SAFE_CAST(`precipitation_max_hour` AS NUMERIC) AS `precipitation_max_hour`,
  SAFE_CAST(`precipitation_max_min` AS NUMERIC) AS `precipitation_max_min`,
  SAFE_CAST(`temp_ave_daily_avg` AS NUMERIC) AS `temp_ave_daily_avg`,
  SAFE_CAST(`temp_ave_daily_high` AS NUMERIC) AS `temp_ave_daily_high`,
  SAFE_CAST(`temp_ave_daily_min` AS NUMERIC) AS `temp_ave_daily_min`,
  SAFE_CAST(`temp_highest` AS NUMERIC) AS `temp_highest`,
  SAFE_CAST(`temp_lowest` AS NUMERIC) AS `temp_lowest`,
  SAFE_CAST(`hum_avg` AS NUMERIC) AS `hum_avg`,
  SAFE_CAST(`hum_min` AS NUMERIC) AS `hum_min`,
  SAFE_CAST(`wind_dir_speed_avg_wind_speed` AS NUMERIC) AS `wind_dir_speed_avg_wind_speed`,
  SAFE_CAST(`wind_dir_speed_max_wind_speed` AS NUMERIC) AS `wind_dir_speed_max_wind_speed`,
  SAFE_CAST(`wind_dir_speed_max_wind_dir` AS NUMERIC) AS `wind_dir_speed_max_wind_dir`,
  SAFE_CAST(`wind_dir_speed_max_inst_wind_speed` AS NUMERIC) AS `wind_dir_speed_max_inst_wind_speed`,
  SAFE_CAST(`wind_dir_speed_max_inst_wind_dir` AS NUMERIC) AS `wind_dir_speed_max_inst_wind_dir`,
  SAFE_CAST(`sunlight_hours` AS NUMERIC) AS `sunlight_hours`,
  SAFE_CAST(`solar_radiation_day_avg` AS NUMERIC) AS `solar_radiation_day_avg`,
  SAFE_CAST(`snow_snowfall_total` AS NUMERIC) AS `snow_snowfall_total`,
  SAFE_CAST(`snow_snowfall_max_day_total` AS NUMERIC) AS `snow_snowfall_max_day_total`,
  SAFE_CAST(`snow_deepest_snow` AS NUMERIC) AS `snow_deepest_snow`,
  SAFE_CAST(`cloud_cover_avg` AS NUMERIC) AS `cloud_cover_avg`,
  SAFE_CAST(`atm_phenomenon_num_of_snow_days` AS NUMERIC) AS `atm_phenomenon_num_of_snow_days`,
  SAFE_CAST(`atm_phenomenon_fog_days` AS NUMERIC) AS `atm_phenomenon_fog_days`,
  SAFE_CAST(`atm_phenomenon_thunder_days` AS NUMERIC) AS `atm_phenomenon_thunder_days`
FROM 
    `★プロジェクト名★.weather_dataset.tokyo_data`

データセットを選択し、テーブル名を入力して保存を選択してください。
これでViewの作成は完了です。
View作成3

コネクテッドシートでデータを見てみよう

BigQueryにデータを取り込むことができたので、さっそくコネクテッドシートでデータを見てみましょう。
コネクテッドシートに関しては以前記事投稿していますので、合わせてみて頂ければ嬉しいです!

使用画像について
特別な記載のない限り、画像はGoogleスプレッドシート画面をキャプチャしたものとなります

BigQueryに接続して、スプレッドシート上にデータを表示する

スプレッドシートを新規作成し、データ → データコネクタ → BigQueryに接続を選択します。
接続
プロジェクトを選択します。
プロジェクト選択

データセットを選択し、tokyo_data_viewを選択し接続ボタンを押下してください。
データセット選択 ビュー選択
スプレッドシート上でデータを見ることができるようになりました!
データ画面

グラフを作ってみる

では、試しにグラフを作ってみましょう。シートの[グラフ]を選択してください。
グラフ作成
新しいシートを選択し、作成ボタンを押下します。
グラフ作成
今回は、年別の最高気温と最低気温の推移がわかるグラフを作ってみようと思います。
グラフエディタで、画像のように設定を行います。

● グラフの種類:縦棒グラフ
● X軸:year
● 系列:temp_highestとtemp_lowest
● フィルタ:temp_highestとtemp_lowestそれぞれ空白ではない値
グラフ作成 グラフ作成
ここまで設定したら、適用ボタンを押下してください。
グラフが表示されました!
グラフ作成
あとは、グラフのタイトルや凡例を変更して、こんな感じにしてみました。
こうみると、最高気温・最低気温が徐々に上昇していることがわかります。
とくに最低気温!昔の東京ってすごく寒かったんですね!
グラフ作成
グラフ以外にもピボットテーブルを作成したり、データの抽出もできるのでコネクテッドシートを使って色々な分析をしてみてください!

まとめ

  • SeleniumはWEBブラウザの自動化(スクレイピング)を行える便利なツール!
  • スクレイピングには注意点があるのでよく確認すること(前編記事:スクレイピングの注意点
  • Cloud Storage APIを使えば、プログラムからCloud Storageの操作が可能
  • BigQueryにデータがあれば、色んなツールで分析ができる!(手軽に始めるならコネクテッドシート)

おまけ

今回作成したsample.pyですが、記事上では説明の為、ソースコードを細切れで掲載しました。
ここにソースコードをまとめて掲載しておきます。
※長いので折りたたんでいます。クリックすると展開します。

完成版プログラムコード
sample.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from google.cloud import storage
import pandas as pd

# 使用しているChromeブラウザのバージョンにあったDriverがインストールされます
# バージョンに相違がない場合はインストールされません
chrome_service = Service(ChromeDriverManager().install())

# Driverのオプションを指定できます
options = webdriver.ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-logging'])
options.use_chromium = True
options.add_argument("--headless")  # 最初はコメントアウトしておいてもOKです
driver = webdriver.Chrome(service=chrome_service, options=options)
# 要素がロードされるまでの待ち時間を10秒に設定します
driver.implicitly_wait(10)

# 取得したいWEBサイトのURLを指定します(今回は気象庁-過去の気象データ検索画面のURL)
driver.get("https://www.data.jma.go.jp/obd/stats/etrn/")

# 都府県・地方選択画面を表示
area_link = driver.find_element(
    By.XPATH, '//*[@id="main"]/table[4]/tbody/tr/td[1]/div/table/tbody/tr[2]/td/a')
driver.execute_script('arguments[0].click();', area_link)

# 地図の中から東京都を選択
select_area_link = driver.find_element(
    By.XPATH, '//*[@id="main"]/map/area[25]')
driver.execute_script('arguments[0].click();', select_area_link)

# 東京都のエリアから東京を選択
select_tokyo = driver.find_element(
    By.XPATH, '//*[@id="ncontents2"]/map/area[9]')
driver.execute_script('arguments[0].click();', select_tokyo)

# 年ごとの値を選択
select_year = driver.find_element(
    By.XPATH, '//*[@id="main"]/table[4]/tbody/tr/td[3]/div/table/tbody/tr[1]/td[1]/table/tbody/tr[1]/td[2]/a')
driver.execute_script('arguments[0].click();', select_year)

# 表全体を取得
table = driver.find_element(By.XPATH, '//*[@id="tablefix1"]')
html = table.get_attribute('outerHTML')
df_temp_data = pd.read_html(html)

# CSVヘッダーの定義
header_columns = ["", "気圧_現地_平均", "気圧_海面_平均", "降水量_合計", "降水量_最大_日", "降水量_最大_1時間",
                  "降水量_最大_10分間", "気温_平均_日平均", "気温_平均_日最高", "気温_平均_日最低", "気温_最高",
                  "気温_最低", "湿度_平均", "湿度_最小", "風向・風速_平均風速", "風向・風速_最大風速_風速",
                  "風向・風速_最大風速_風向", "風向・風速_最大瞬間風速_風速", "風向・風速_最大瞬間風速_風向",
                  "日照時間", "全天日射量平均", "雪_降雪_合計", "雪_降雪_日合計の最大", "最深積雪",
                  "雲量平均", "大気現象_雪日数", "大気現象_霧日数", "大気現象_雷日数"]

output_data = []

# 行を1行ずつ取得
for i, data in df_temp_data[0].iterrows():
    record = []
    # 行から項目数分のデータだけ取得して保持
    for j in range(0, len(header_columns)):
        record.append(data[j])
    output_data.append(record)

# CSV出力用のDataFrameを定義
output_df = pd.DataFrame(data=output_data, columns=header_columns)

file_name = "tokyo_weather_data.csv"
# output_pathはどこでも構いませんが、今回はsample.pyと同じ階層に
# outputフォルダを作成しておき、そのフォルダに出力するようにしました
output_path = "./output/" + file_name
output_df.to_csv(output_path, index=False, encoding="shift-jis")

# Chrome Driverのclose処理
driver.close()

# Cloud Storageにファイルをアップロードする
client = storage.Client.from_service_account_json(
    'storage_key.json')
bucket = client.get_bucket("sample_csv_upload_bucket")

# CSVファイルをアップロード
blob = bucket.blob(file_name)
blob.upload_from_filename(output_path)
  1. サービス アカウントについて ――公式ドキュメント

  2. 値欄の記号の説明 ――気象庁

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?