Web スクレイピング

HTML と CSS の基礎

  • Web スクレイピングを行うためには、HTMLとCSSの基本的な知識が必要となる

HTML

  • Web ページを表示するための言語
    • HTML 自体はプログラミング言語ではない

理解すべき概念

  • 要素
    • タグ
      • 改行や画像などを除き、始点・終点となるタグがそれぞれ存在
        • 終点のタグには、中にスラッシュが入る
    • 属性
      • id
        • ID。1つのタグにつき1つ。重複が許されない
      • class
        • クラス名。複数設定可。重複OK
      • alt (代替文字)
      • href (リンク先)
      • src (画像などのソース)
  • 詳細な説明として、次のサイトも参考になる: https://developer.mozilla.org/ja/docs/Learn/Getting_started_with_the_web/HTML_basics

CSS

  • HTMLをデザインするための言語

理解すべき概念

  • 要素ごとにデザインを指定していく
    • 指定の仕方
      • ID
        • id=“test”なら”#test”と指定
      • class
        • class=“test”なら”.test”と指定
        • class=“test1 test2”なら”.test1.test2”と指定
          • 間にスペースは不要
        • class=“parent”の要素の中にある class=“child”の要素なら、“.parent .child”と指定
          • 間に半角スペース
          • 子要素、孫要素どちらにも適用
        • class=“parent”の要素の中の直下にある class=“child”の要素なら、“.parent > .child”と指定
          • 直属の子要素のみに適用
      • タグ
        • aタグ (リンク) なら “a” と指定
      • タグ、class、idを併用して指定することも可能
        • 例. “.parent .link a”, “#header .title span”
  • Web ページの HTML, CSS は、 ブラウザでF12 キーを押して表示される開発者ツールから確認可能

スクレイピングの注意点

  • スクレイピングが禁止されている Web サイトでは実行してはならない
    • 場合によっては処分されるかもしれない (大学のデータベースなどは特に注意)
  • 集中的にアクセスしてはならない
    • 一時的にサイトへアクセスしづらくさせ、一般利用者に迷惑をかける可能性がある
    • そのような行為が継続された場合、サイト運営者がアクセス制限をかけることも
    • アクセスするたびに 1 秒ほど操作を一時停止させるなどの対策を取る

スクレイピングの実行

  • Webスクレイピング (= Webページからデータを収集する) には,rvest パッケージが便利
  • rvest を読み込んでみる
pacman::p_load(rvest)
  • rvest::read_html(〜) のように, 関数名の前にライブラリ名を指定すれば,pacman::p_load()やlibrary()は不要
    • 複数のライブラリで関数名が重複した際に使える方法

ページからデータを取得

  • 東京都議会の会派別議員名簿のページを読み込む
data <- read_html("https://www.gikai.metro.tokyo.jp/membership/political-party.html")
  • 今回は tdタグ (=表のセル) だけ抽出し,HTMLからテキストに変換
member_name <- html_elements(data, "td") |> 
  html_text() # htmlをテキストに変換
  • どのタグを使えば良いか: Webページのソースから確認
    • ページ上で右クリックし,「ページのソースを表示」をクリック
    • タグ: <html>〜</html>, <a>〜</a> などのこと
      • 基本的に1セット:始まりのタグ <> と終わりのタグ </> で挟むかたち

取得したデータを整理

  • 奇数行が選挙区名,偶数行が議員名 -> データを2つに分ける
common <- tibble(value = member_name) |> 
  rownames_to_column() # 「行番号の列」を追加

distincts <- common |>
  filter(rowOE == 1) # 選挙区名
names <- common |>
  filter(rowOE == 0) # 議員名
  • 上記の2データを列として結合し,中身を確認
tokyo <- bind_cols(distinct = distincts$value, name = names$value)
View(tokyo) # データの表が新しいタブで閲覧できる

議員データの取得

  • 会派別議員名簿ページのリンクから飛べる各議員のページから, データを取得してみる
## とりあえず一番上に記載の議員のページを取得
data_person <- read_html(
    "https://www.gikai.metro.tokyo.jp/membership/number002.html") 

## 議員名はh3タグに含まれているので,h3タグの中身を取得し,HTMLからテキストに変換
person_name <- data_person |> 
  html_elements("h3") |> 
  html_text()

## 議員プロフィールはliタグ (複数) に含まれているので,
## 全てのliタグの中身を取得し,HTMLからテキストに変換
person_profs <- data_person |> 
  html_elements("li")
  • 余計なタグがあるので取り除く
person_prof # 取得したliタグの一覧を確認

## 余計なliタグが含まれている模様!
person_prof <- numeric()

## 12番目のliタグから 最後のliタグまで が欲しい情報 ← これを取得する
for(i in 12:length(person_profs)){
  person_prof[i-11] <- person_profs[i] |> html_text()
}

議員データの前処理

  • 取得できた文字列を整えるために、stringrパッケージの関数を用いる
person_prof # 取得できたデータを確認
# person_prof[1] # 氏名
# person_prof[2] # 会派
# person_prof[3] # 期数
# person_prof[4] # 委員会
# person_prof[5] # 事務所?の住所
# person_prof[6] # TEL
# person_prof[7] # 選挙区
# person_prof[8] # WebサイトURL

person_prof[3] <- str_sub(person_prof[3], start = 3, end = 3) # 期数抜き出し

person_prof[6] <- str_sub(person_prof[6], start = 4, end = -1) # TEL抜き出し

person_prof[7] <- str_sub(person_prof[7], start = 5, end = -2) # 選挙区抜き出し

person_prof # データが整序された!

データセットの作成

  • 今までの作業を関数としてまとめる
member_data <- tibble()
getMemberData <- function(url){
  data_person <- read_html(url)
  person_name <- data_person |> html_elements("h3") |> html_text()
  person_profs <- data_person |> html_elements("li")
  person_prof <- numeric()
  
  for(i in 12:length(person_profs)){
    person_prof[i-11] <- person_profs[i] |> html_text()
  }
  
  person_prof[3] <- str_sub(person_prof[3], start = 3, end = 3) # 期数抜き出し
  person_prof[6] <- str_sub(person_prof[6], start = 4, end = -1) # TEL抜き出し
  person_prof[7] <- str_sub(person_prof[7], start = 5, end = -2) # 選挙区抜き出し

  for(i in 1:8){
    return(tibble(person_prof))
  }
} 
  • 議員個人ページへアクセスしてデータを取得していくため, 各ページへのリンクのアドレスを取得し,議員名簿データに挿入する
member_link <- html_elements(data, "a") |> # aタグのデータを全て抽出
  html_attr("href") # aタグのhref属性 (<- URLが格納されてある) の中身だけ抽出
member_links <- numeric() # for文の結果を格納するオブジェクトを作成

## URLは相対パスで保存されていたので,前のURLと結合した絶対パスへ変換
for(i in 16:140){
  member_links[i-15] <- paste0("https://www.gikai.metro.tokyo.jp/membership/",
   member_link[i]) 
}

## for文で出力されたオブジェクトを,linkという列名で,tokyoデータに結合
tokyo <- bind_cols(tokyo, link = member_links) 
  • 関数を実行し,データセットを完成させる
## データの一斉取得
## 一気に大量にアクセスすると,アクセス先のサーバに負荷がかかるため注意する
for (i in 1:10){  ## 今回は上から10人分のみを実施
  assign(paste0("member_", i), getMemberData(tokyo$link[i]))
  Sys.sleep(1) # アクセス集中防止のため、1  アクセスごとに 1 秒待機させる
}

## データをtibbleに格納
tokyo2 <- tibble() # for文の結果を格納する空のオブジェクトを作成
for(i in 1:10) {
  for(j in 1:8){
    tokyo2[i, j] <- get(paste0("member_", i))[j, 1]
  }
}

## 列名を指定
tokyo2 <- tokyo2 |> 
  setNames(c("氏名", "会派", "期数", "委員会", "住所", "TEL", "選挙区", "URL"))