気象データが欲しくなって過去にスクレイピングで入手した話

# この記事を書いた理由

- 1年半前に使ったものを見つけたので整理(年末に向けたお掃除)
- なんちゃって分析レベルでも分析大事だなと再認識したので書きながら記憶を呼び起こしたい

# 当時の状況

- 当時、一般ユーザー向けのサービスを担当していて、ユーザーがどんなシチュエーションの時にどういう行動を取るのか見たかった
  ~~(大人の事情で詳しく書けない)~~
- 天気とか気温が関係する(と考えられている)サービスだったから気象データと紐付けて分析すれば何かわかるのでは？という考えがあった
- 分析タイミングは不定期だったので必要なタイミングで手動で叩いて取得していた

## データ分析例

仮に1ヶ月の間にAさんがランチをどこで食べるのかを集計。 ~~実際に分析したものじゃないです~~
(ここでは お店の名前、お店に訪れた日時、お店の場所 を保持してるものとする。)

集計したものを見たところ、職場から離れた飲食店に行くこともあれば近くの飲食店やコンビニで済ませてしまうこともある。
→それぞれの店の使用回数にバラつきはあるものの、 `何故` そうなってるのかがわからない :thinking: 
→なかなか傾向がつかめない 😓

相関関係で調べてみると・・・

まずは既に保持している日時を分解してみる。

↓

- 時間軸との相関関係
    - 時間別
        - 11:00台に早めに行ったランチは軽食の傾向が強い
    - 曜日別
        - 水曜日はC店に行くことが多い(ルーチンありそう)

→特定の要素に分解することで傾向が見えてくる。

じゃあ、他のデータを掛け合わせてみるとどうなる？
→気象データでやったらなんかわかる？

↓

- 気象データ(天気、気温)との相関関係
    - 天気
        - 晴れの日は職場から離れた飲食店まで行くことが多い(散歩も兼ねてそう)
        - 雨の日は近くの飲食店orコンビニで済ませてしまうことが多い(そりゃそうだよね)
    - 気温
        - 暑い日は蕎麦が多い

※実際に分析するとこんなにきれいに傾向は見えないと思います。。

とまあ、こんな感じでいろいろ分析できればいいなと思って、気象データを取得することにしました。

# スクレイピング準備

- [過去の気象データ検索](https://www.data.jma.go.jp/obd/stats/etrn/) のページがどうなってるか確認
    - ページ構造わかってないと、スクレイピングできないので要確認。
- [都府県・地方の選択](https://www.data.jma.go.jp/obd/stats/etrn/select/prefecture00.php?prec_no=&block_no=&year=&month=&day=&view=) で地域コードがどうなってるか確認
    - 本記事ではここをスクレイピングする部分は省略。代わりに取得済みのシートを公開しておきます。
    - 「天気概況」は特定の観測地点でしか記録されていません。

# スクレイピング実施

- ググってサンプルプログラムをいくつか確認して必要なとこに手を加えただけです(手抜き)

```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# for Python3

import requests
from bs4 import BeautifulSoup
import csv

# codeA,codeB,nameをセットで記載
place_codeA = [44]
place_codeB = [47662]
place_name = ["東京"]

year_start = 2020
year_end = 2021  # +1 基本は2020
month_end = 13  # +1 基本は13

base_url = "http://www.data.jma.go.jp/obd/stats/etrn/view/daily_s1.php?prec_no=%s&block_no=%s&year=%s&month=%s&day=1&view=p1"

def str2float(str):
    try:
        return float(str)
    except:
        return 0.0

# ヘッダー
headers = ['場所', '年月日', '陸の平均気圧(hPa)', '海の平均気圧(hPa)', '降水量(mm)', '平均気温(℃)',
           '平均湿度(%)', '平均風速(m/s)', '日照時間(h)', '天気概況(昼)', '天気概況(夜)']

if __name__ == "__main__":
    for place in place_name:
        print(place)

        outputDatas = [headers]

        index = place_name.index(place)

        for year in range(year_start, year_end):
            print(year)
            # その年の1月~12月の12回を網羅する。
            for month in range(1, month_end):
                # URLに都市コードと年と月を当てはめる。
                print(base_url %
                      (place_codeA[index], place_codeB[index], year, month))
                r = requests.get(base_url % (
                    place_codeA[index], place_codeB[index], year, month))
                r.encoding = r.apparent_encoding

                soup = BeautifulSoup(r.content, 'html.parser')
                rows = soup.findAll('tr', class_='mtx')
                # 冒頭のカラム情報をスライス
                rows = rows[4:]

                for row in rows:
                    data = row.findAll('td')

                    # 必要なデータを取り出す
                    rowData = []
                    rowData.append(place)
                    rowData.append(str(year) + "/" + str(month) +
                                   "/" + str(data[0].string))
                    rowData.append(str2float(data[1].string))
                    rowData.append(str2float(data[2].string))
                    rowData.append(str2float(data[3].string))
                    rowData.append(str2float(data[6].string))
                    rowData.append(str2float(data[9].string))
                    rowData.append(str2float(data[11].string))
                    rowData.append(str2float(data[16].string))
                    rowData.append(data[19].string)
                    rowData.append(data[20].string)

                    # 次の行にデータを追加
                    outputDatas.append(rowData)

        # 都市ごとにデータをファイルを書き出す。(csvファイル形式。名前は都市名)
        # Excel向けにBOM付きのUTF-8とする(文字化け防止)
        with open(place + '_A' + str(place_codeA[index]) + '_B' + str(place_codeB[index]) + '.csv', mode="w", encoding="utf_8_sig") as file:
            writer = csv.writer(file, lineterminator='\n')
            writer.writerows(outputDatas)

```

# まとめ

蓄積されたデータだけでも分析はできるけど、サービス以外の世の中のデータを掛け合わせることで見えてくるものもあるんだなと再認識。
コロナ関連で何か発表された、とかGoToトラベルの開始、中止のタイミングとか、コロナで自粛始まってNヶ月とか、人の生活に影響出そうなものはリストアップしておきたい。(パッと思いつくのは全部コロナｗ)

あとはN1分析もちゃんとやらなきゃと思ってできてない。念じておけば近いうちに分析できそうだから念じておこう 🙏

# 資料とか

## 使用した都道府県リスト

- 気象庁のHPから集めたprec_no、block_noの一覧  
https://docs.google.com/spreadsheets/d/1RfRxpbpbM55CbckctMz0DU1NZ9EaDKq0O0Ik2YQJrmU/edit?usp=sharing

## 参考URL

- 気象庁HP
  https://www.jma.go.jp/jma/kishou/info/coment.html
- [GoogleColaboratoryで気象庁の過去気象データをスクレイピングしてみた。](https://qiita.com/Cyber_Hacnosuke/items/122cec35d299c4d01f10#comments)
    - 1年半前ここを見ていた気がする

---

# おまけ

スクレイピングつながりで。

## 認証付きサイトからファイルをダウンロードする

これも1年半くらい前に作ったものです。
いまとなってはおなじみ(？)のHeadless Chromeです。
当時、担当者が分析のために毎日同じ手順でDLしてたのを自動化する目的で作りました。
※実際には複数サイト、各サイトで数ファイルDLしてましたがサンプル実装時のものを載せてます。

担当者が各サイトを巡回してファイル取得してたのをやめて、自動でサイト巡回＆ファイル取得してSlackに投げるようにして運用してました。

```python
# -*- coding: utf-8 -*-

import chromedriver_binary
from selenium import webdriver
from pathlib import Path
from time import sleep

import datetime
import os

os.environ["MY_LOGIN_ID"] = ""
os.environ["MY_LOGIN_PASSWORD"] = ""

dldir_name = '/tmp/download'  # 保存先フォルダ名
dldir_path = Path(dldir_name)
dldir_path.mkdir(exist_ok=True)  # 存在していてもOKとする（エラーで止めない）
download_dir = str(dldir_path.resolve())  # 絶対パス

now = datetime.datetime.now()
#現在時刻を織り込んだファイル名を生成
fmt_name = "{0:%Y%m%d-%H%M%S}".format(now)

#main class
class ScrapeLoginAuthSite():
    def __init__(self,username,password):
        self.username = username
        self.password = password
        self.url = "login url"
        options = webdriver.ChromeOptions()
        # chrome driver -headless mode
        options.add_argument('--headless')
        options.add_argument('--ignore-certificate-errors')
        self.driver = webdriver.Chrome(options=options)

    def main(self):
        driver = self.driver
        # ヘッドレスChromeでファイルダウンロードするにはここが必要
        driver.command_executor._commands["send_command"] = ("POST", '/session/$sessionId/chromium/send_command')
        driver.execute("send_command", {
            'cmd': 'Page.setDownloadBehavior',
            'params': {
                'behavior': 'allow',
                'downloadPath': download_dir # ダウンロード先
            }
        })

        #Login window
        print("login window open")
        driver.get(self.url)
        sleep(1)
        # ログイン実施
        driver.execute_script('document.getElementsByName("email")[0].value="' + self.username + '"')
        driver.execute_script('document.getElementsByName("password")[0].value="' + self.password + '"')
        driver.execute_script('document.getElementsByTagName("button")[0].click()')
        sleep(5)
        print(driver.current_url)

        # site1
        driver.get("site1 url")
        sleep(10)
        print(driver.current_url)
        driver.execute_script("document.getElementsByTagName('a')[20].click()")
        sleep(10)

        os.chdir(download_dir)
        files = filter(os.path.isfile, os.listdir(download_dir))
        files = [os.path.join(download_dir, f) for f in files] # add path to each file
        files.sort(key=lambda x: os.path.getmtime(x))
        newest_file = files[-1]
        os.rename(newest_file, "/tmp/site1_" + fmt_name + ".csv")

        sleep(3)

        driver.close()
        driver.quit()

    # destractor
    def __del__(self):
        print("del:driver")

if __name__ == "__main__":
    login_id = os.environ.get("MY_LOGIN_ID")
    login_pass = os.environ.get("MY_LOGIN_PASSWORD")
    text = ScrapeLoginAuthSite(login_id,login_pass).main()
```