1 取得するデータ

気象庁の過去の気象データ検索から東京の2022年8月10日の気温と風向を取得する。
東京 2022年8月10日(1時間ごとの値)

出典:気象庁|過去の気象データ検索
出典:気象庁|過去の気象データ検索

2 URL解析

取得したいデータがあるウェブページのURLを見てどのような規則で表示されているかを推察する。

https://www.data.jma.go.jp/obd/stats/etrn/view/hourly_s1.php?prec_no=44&block_no=47662&year=2022&month=8&day=10&view=  

この例では,prec_no,block_noは恐らく気象観測所に関する番号であろう,year,month,dayが年月日だろうと当たりが付く。このようにパラメータを含む形でURLが記載されている場合でページソースにHTMLテーブルやXMLの形式でデータがある場合(ウェブ画面を右クリックしソースを表示すると分かる)は,パラメータをプログラムで変えることで異なるウェブページに機械的にアクセスしデータを取得できる。 ただし,Yahoo!ファイナンスのようにウェブサーバーの負荷の問題からウェブスクレイピングを禁止してるところもあるので利用規約を読むことが必要。ウェブスクレイピング自体は合法的である。

cf. スクレイピングは違法?Webスクレイピングに関する10のよくある誤解!

3 設定

# 出力ファイル
DB  <- 'weather.duckdb' # データベース名
F.O <- 'weather.csv'    # CSVファイル名

# 気象観測所
site <- data.frame(
  id   = 47662,   # 番号
  name = 'Tokyo') # 名称(データベースのテーブル名として使う)

# 対象日時(テーブル取得のためのURLに適用する日時)
lt <- as.POSIXlt('2022-08-10') # POSIX準拠ローカル時間
year  <- 1900 + lt$year
month <- 1 + lt$mon
day   <- lt$mday

4 URL作成

url <- paste0('https://www.data.jma.go.jp/obd/stats/etrn/view/hourly_s1.php?prec_no=44&block_no=', site$id, '&year=', year, '&month=', month, '&day=', day, '&view=')

cat('URL:', url, fill = T) # 作成したURLを表示
## URL: 
## https://www.data.jma.go.jp/obd/stats/etrn/view/hourly_s1.php?prec_no=44&block_no=47662&year=2022&month=8&day=10&view=

5 ウェブページのデータ取得 (Web scraping)

5.1 テーブル取得

library(rvest)
read_html(url) |> html_table() -> tbl
tbl
## [[1]]
## # A tibble: 1 × 2
##   X1    X2   
##   <lgl> <lgl>
## 1 NA    NA   
## 
## [[2]]
## # A tibble: 1 × 2
##   X1    X2   
##   <lgl> <lgl>
## 1 NA    NA   
## 
## [[3]]
## # A tibble: 1 × 7
##   X1    X2    X3    X4    X5    X6    X7   
##   <lgl> <lgl> <lgl> <lgl> <lgl> <lgl> <lgl>
## 1 NA    NA    NA    NA    NA    NA    NA   
## 
## [[4]]
## # A tibble: 1 × 3
##   X1    X2    X3   
##   <lgl> <lgl> <lgl>
## 1 NA    NA    NA   
## 
## [[5]]
## # A tibble: 25 × 17
##    時    `気圧(hPa)` `気圧(hPa)` `降水量(mm)` `気温(℃)` `露点温度(℃)`
##    <chr> <chr>       <chr>       <chr>        <chr>     <chr>        
##  1 時    現地        海面        降水量(mm)   気温(℃)   露点温度(℃)  
##  2 1     1007.0      1009.7      --           28.7      23.2         
##  3 2     1006.5      1009.2      --           28.7      23.2         
##  4 3     1006.9      1009.6      --           28.5      23.2         
##  5 4     1006.7      1009.4      --           28.2      23.1         
##  6 5     1007.0      1009.7      --           27.8      23.4         
##  7 6     1007.3      1010.0      --           28.0      23.6         
##  8 7     1007.4      1010.1      --           29.6      23.6         
##  9 8     1007.6      1010.3      --           31.2      23.8         
## 10 9     1007.5      1010.2      --           32.5      23.5         
## # ℹ 15 more rows
## # ℹ 11 more variables: `蒸気圧(hPa)` <chr>, `湿度(%)` <chr>,
## #   `風向・風速(m/s)` <chr>, `風向・風速(m/s)` <chr>, `日照時間(h)` <chr>,
## #   `全天日射量(MJ/㎡)` <chr>, `雪(cm)` <chr>, `雪(cm)` <chr>, 天気 <chr>,
## #   雲量 <chr>, `視程(km)` <chr>
## 
## [[6]]
## # A tibble: 1 × 1
##   X1   
##   <chr>
## 1 -- 
## 
## [[7]]
## # A tibble: 1 × 4
##   X1             X2                  X3                 X4                  
##   <chr>          <chr>               <chr>              <chr>               
## 1 利用される方へ よくある質問(FAQ) 気象観測統計の解説 年・季節・各月の天候

tblをプリントし,どのテーブルに必要なデータが格納されているか確認すると5番目のテーブル(リスト)にあることが分かる。 次で5番目のリストを取り出す。すべてテキストデータ(chr)になっている。

d0 <- as.data.frame(tbl[[5]])
str(d0)
## 'data.frame':    25 obs. of  17 variables:
##  $ 時               : chr  "時" "1" "2" "3" ...
##  $ 気圧(hPa)        : chr  "現地" "1007.0" "1006.5" "1006.9" ...
##  $ 気圧(hPa)        : chr  "海面" "1009.7" "1009.2" "1009.6" ...
##  $ 降水量(mm)       : chr  "降水量(mm)" "--" "--" "--" ...
##  $ 気温(℃)          : chr  "気温(℃)" "28.7" "28.7" "28.5" ...
##  $ 露点温度(℃)      : chr  "露点温度(℃)" "23.2" "23.2" "23.2" ...
##  $ 蒸気圧(hPa)      : chr  "蒸気圧(hPa)" "28.4" "28.4" "28.4" ...
##  $ 湿度(%)         : chr  "湿度(%)" "72" "72" "73" ...
##  $ 風向・風速(m/s)  : chr  "風速" "3.8" "4.8" "5.2" ...
##  $ 風向・風速(m/s)  : chr  "風向" "南" "南" "南" ...
##  $ 日照時間(h)      : chr  "日照時間(h)" "" "" "" ...
##  $ 全天日射量(MJ/㎡): chr  "全天日射量(MJ/㎡)" "" "" "" ...
##  $ 雪(cm)           : chr  "降雪" "×" "×" "×" ...
##  $ 雪(cm)           : chr  "積雪" "×" "×" "×" ...
##  $ 天気             : chr  "天気" "" "" "" ...
##  $ 雲量             : chr  "雲量" "" "" "2" ...
##  $ 視程(km)         : chr  "視程(km)" "" "" "20.0" ...

5.2 テーブルの整形

日時などの情報追加,変数の型指定,データの整形を行い,書込用テーブルを作成する。 日時のフォーマット(%Y-%m-%d %H:%M:%Sなど)はどのプログラミング言語でもほぼ共通なのでしっかりと記憶しておくこと。Rコンソールで「?strptime」とタイプすれば他の記号も調べることができる。

# 日時整形
hour <- d0[-1, '時'] # 1列目は時刻1~24(-1:一行目は不要なため削除)
# コンピュータの世界(POSIX準拠)では24時は存在しないので0~23にする必要がある。
# コンピュータ上では24時は翌日の日付になる。
datetime <- as.POSIXlt(paste(lt, hour))        # 例)2022-08-10 24
                                               # 自動で時刻が0~23に変換される。

# 書込用テーブル作成
d1 <- data.frame(site.id   = as.integer(site$id), # 整数型
                 site.name = site$name,
                 datetime  = paste(datetime),
                 temp      = as.double(d0[-1, 5]), # 倍精度浮動小数点型
                 wind      = d0[-1, 10])
str(d1)
## 'data.frame':    24 obs. of  5 variables:
##  $ site.id  : int  47662 47662 47662 47662 47662 47662 47662 47662 47662 47662 ...
##  $ site.name: chr  "Tokyo" "Tokyo" "Tokyo" "Tokyo" ...
##  $ datetime : chr  "2022-08-10" "2022-08-10" "2022-08-10" "2022-08-10" ...
##  $ temp     : num  28.7 28.7 28.5 28.2 27.8 28 29.6 31.2 32.5 33.4 ...
##  $ wind     : chr  "南" "南" "南" "南" ...

6 データ保存

6.1 データベース(DuckDB)への保存

library(duckdb)

# データベース接続
#con <- dbConnect(duckdb(), DB) # インメモリデータベース
con <- dbConnect(duckdb("test.duckdb"), DB) # 「test」というデータベース接続/新規作成

# 既存テーブル削除(必要に応じて実施)
dbSendQuery(con, paste('DROP TABLE IF EXISTS', site$name))
# テーブル追記書込
dbWriteTable(con, site$name, d1, append = T)
# データ選択(ちゃんと保存されたか確認すること)
res <- dbSendQuery(con, 'SELECT * FROM Tokyo')

# 選択結果取得
dbFetch(res)
##    site.id site.name   datetime temp   wind
## 1    47662     Tokyo 2022-08-10 28.7     南
## 2    47662     Tokyo 2022-08-10 28.7     南
## 3    47662     Tokyo 2022-08-10 28.5     南
## 4    47662     Tokyo 2022-08-10 28.2     南
## 5    47662     Tokyo 2022-08-10 27.8     南
## 6    47662     Tokyo 2022-08-10 28.0     南
## 7    47662     Tokyo 2022-08-10 29.6     南
## 8    47662     Tokyo 2022-08-10 31.2 南南西
## 9    47662     Tokyo 2022-08-10 32.5 南南西
## 10   47662     Tokyo 2022-08-10 33.4 南南西
## 11   47662     Tokyo 2022-08-10 34.3 南南西
## 12   47662     Tokyo 2022-08-10 34.3 南南西
## 13   47662     Tokyo 2022-08-10 34.3     南
## 14   47662     Tokyo 2022-08-10 33.8     南
## 15   47662     Tokyo 2022-08-10 33.6     南
## 16   47662     Tokyo 2022-08-10 32.6     南
## 17   47662     Tokyo 2022-08-10 31.3 南南西
## 18   47662     Tokyo 2022-08-10 30.2     南
## 19   47662     Tokyo 2022-08-10 29.6     南
## 20   47662     Tokyo 2022-08-10 28.7 南南東
## 21   47662     Tokyo 2022-08-10 28.5     南
## 22   47662     Tokyo 2022-08-10 28.7     南
## 23   47662     Tokyo 2022-08-10 28.2     南
## 24   47662     Tokyo 2022-08-10 28.1     南
# 選択結果解放
dbClearResult(res)

# データベース接続解除 
dbDisconnect(con, shutdown = T)
保存されたデータ(DBブラウザとしてTadを使用)
保存されたデータ(DBブラウザとしてTadを使用)

[Tad] https://www.tadviewer.com/

6.2 CSVファイルへの保存

# 既存ファイル削除(必要に応じて実施)
file.remove(F.O)
## [1] TRUE
# テーブル追記書込
# (macOSXはコマンド:brew install libomp で
# Open MPという並列計算ライブラリをインストールしておく)
library(data.table)
fwrite(d1, file = F.O, sep = ',', append = T)
# 読込確認
(d2 <- fread(file = F.O))
##     site.id site.name   datetime  temp   wind
##       <int>    <char>     <IDat> <num> <char>
##  1:   47662     Tokyo 2022-08-10  28.7     南
##  2:   47662     Tokyo 2022-08-10  28.7     南
##  3:   47662     Tokyo 2022-08-10  28.5     南
##  4:   47662     Tokyo 2022-08-10  28.2     南
##  5:   47662     Tokyo 2022-08-10  27.8     南
##  6:   47662     Tokyo 2022-08-10  28.0     南
##  7:   47662     Tokyo 2022-08-10  29.6     南
##  8:   47662     Tokyo 2022-08-10  31.2 南南西
##  9:   47662     Tokyo 2022-08-10  32.5 南南西
## 10:   47662     Tokyo 2022-08-10  33.4 南南西
## 11:   47662     Tokyo 2022-08-10  34.3 南南西
## 12:   47662     Tokyo 2022-08-10  34.3 南南西
## 13:   47662     Tokyo 2022-08-10  34.3     南
## 14:   47662     Tokyo 2022-08-10  33.8     南
## 15:   47662     Tokyo 2022-08-10  33.6     南
## 16:   47662     Tokyo 2022-08-10  32.6     南
## 17:   47662     Tokyo 2022-08-10  31.3 南南西
## 18:   47662     Tokyo 2022-08-10  30.2     南
## 19:   47662     Tokyo 2022-08-10  29.6     南
## 20:   47662     Tokyo 2022-08-10  28.7 南南東
## 21:   47662     Tokyo 2022-08-10  28.5     南
## 22:   47662     Tokyo 2022-08-10  28.7     南
## 23:   47662     Tokyo 2022-08-10  28.2     南
## 24:   47662     Tokyo 2022-08-10  28.1     南
##     site.id site.name   datetime  temp   wind

7 HTMLの表データが上手く取れない場合

7.1 [対応1] httr::GET関数を利用(リダイレクトされる場合に有効)

野球データ取得の例

url <- 'https://baseball-data.com/stats/hitter-all/'

library(httr)
url |> GET(timeout(30)) |> read_html() |> html_table() -> tbl
tbl
## [[1]]
## # A tibble: 56 × 21
##    順位  選手名   チーム 打率  試合  打席数 打数  安打  本塁打 打点  盗塁  四球 
##    <chr> <chr>    <chr>  <chr> <chr> <chr>  <chr> <chr> <chr>  <chr> <chr> <chr>
##  1 1     近藤 …  ソフ…  .337  46    195    166   56    6      24    4     28   
##  2 2     田宮 …  日本…  .333  39    143    123   41    1      19    4     11   
##  3 3     サンタナ ヤク…  .317  48    188    167   53    7      27    1     18   
##  4 4     長岡 …  ヤク…  .309  48    195    175   54    3      15    1     12   
##  5 4     村松 …  中日   .309  42    153    136   42    0      10    3     12   
##  6 6     小園 …  広島   .306  45    188    173   53    1      23    3     12   
##  7 7     柳田 …  ソフ…  .298  46    204    168   50    4      34    3     31   
##  8 8     宮﨑 …  DeNA   .296  47    174    152   45    3      16    0     21   
##  9 9     福田 …  オリ…  .293  41    162    140   41    1      10    6     17   
## 10 10    秋山 …  広島   .288  43    174    163   47    1      10    2     9    
## # ℹ 46 more rows
## # ℹ 9 more variables: 死球 <chr>, 三振 <chr>, 犠打 <chr>, 併殺打 <chr>,
## #   出塁率 <chr>, 長打率 <chr>, OPS <chr>, RC27 <chr>, XR27 <chr>
d.b <- as.data.frame(tbl[[1]])

library(DT)
datatable(d.b)

7.2 [対応2] HTMLファイルを取得し1行ずつ処理(王道的処理)

食べログの池袋ラーメンデータ取得の例

library(tidyverse)
lines <- readLines('https://tabelog.com/tokyo/A1305/A130501/R607/rstLst/ramen/')

names <- prs <- infos <- NULL

for (i in seq_along(lines))
{
  # 店舗名を取得
  is.name <- str_detect(string = lines[i],
                        pattern = "list-rst__rst-name")
  if ( is.name )
  {
    lines[i + 1] |> str_replace_all(pattern = "\\<.*?\\>",  replacement = "") |> str_trim() -> name

    if ( name != "" ) names <- c(names, name)
  }

  # 店舗情報を取得
  is.info <- str_detect(string = lines[i], 
                        pattern = "list-rst__area-genre cpy-area-genre")
  if ( is.info )
  {
    lines[i] |> str_replace_all(pattern = "\\<.*?\\>",  replacement = "") |> str_trim() |>
                str_replace_all(pattern = "ラーメン・つけ麺", replacement = "") |>
                str_replace_all(pattern = "\\(|\\)", replacement = "") -> info

    infos <- c(infos, info)
  }

  # PRコメントを取得
  is.pr <- str_detect(string = lines[i],
                      pattern = "list-rst__comment list-rst__comment--hover js-open-review-window")
  if ( is.pr )
  {
    lines[i + 1] |> str_replace_all(pattern = "\\<.*?\\>",  replacement = "") |> str_trim() -> pr

    prs <- c(prs, pr)
  }
}

d1 <- cbind(店舗名 = names, 店舗情報 = infos, PRコメント = prs)

library(DT)
datatable(d1)

8 Python

Pythonによるウェブスクレイピングは次のウェブサイトを参照のこと。

Pythonで気象庁の過去気象データをスクレイピング

9 演習課題

次をおこなうRソースファイル(拡張子:R)を作成,プログラム実行しデータベースを作成せよ。また,値が無いところはどうすれば良いか検討せよ。 forループを使いデータを取得する。

2021年12月31日~2022年1月1日までの気象データ(気温,湿度,日照時間,風向)をデータベースに格納する。

【重要】プログラムで連続してデータ収集する場合は,ウェブサーバーの負荷軽減のため,プログラムを一定時間休止させるコマンド:Sys.sleep(runif(1, min = 1, max = 2))をループ内に置き1~2秒の間隔でデータを取得すること。

ヒント:

t.fr <- as.POSIXlt('2021-12-30')
t.to <- as.POSIXlt('2022-01-01')
ts   <- as.POSIXlt(seq(t.fr, t.to, by = 'days'))
ts
## [1] "2021-12-30 JST" "2021-12-31 JST" "2022-01-01 JST"