2018-11-06:小選挙区のデータと比例区の候補者別データを修正しました。愛知2区の田畑毅氏が比例で復活当選しているのに、元のデータでは落選扱いになっていました。台風の影響で開票が遅れ、スクレイピングを実行した時点では復活当選の結果が出ていなかったようです(前田耕さんからご指摘いただきました)。


データ

朝日新聞のウェブサイトで集めた、第48回衆議院議員総選挙(2017年10月22日実施)のデータ (encoding = UTF-8) を公開する。

小選挙区 (Single Member Districts)

含まれている変数は以下のとおり。

  • prefecture: 都道府県
  • dist_no: 選挙区の番号(都道府県毎)。北海道1区と青森1区はどちらも 1
  • district: ユニークな選挙区名。E.g., 北海道1区, 東京3区, etc.
  • name: 候補者の氏名(姓と名の間に半角スペース)
  • yomi: 候補者氏名の読み(姓と名の間に半角スペース)
  • lastname: 候補者の姓
  • firstname: 候補者の名
  • last_kana: 候補者の姓の読み
  • first_kana: 候補者の名の読み
  • age: 年齢
  • party: 所属政党
  • recommended: 推薦政党
  • status: 元(元職)、前(前職)、新(新人)
  • previous: 過去の当選回数
  • duplicate: 比例重複ダミー(重複立候補なら1、そうでなければ0)
  • win_smd: 小選挙区で勝利したことを示すダミー
  • win_pr: 比例区で復活当選したことを示すダミー
  • votes: 獲得得票数
  • voshare: 選挙区内での得票率 (%)

(2017-10-24 追記) 小選挙区のデータについては、私の集めた情報を元に変数名等を編集したものが、University of North Texas 准教授の前田耕さんのウェブサイトで手に入ります。前回までの選挙データと整合性があり、コードブックも付いていて便利です。 English version of the SMD results is availabe at Prof. Ko Maeda’s website.

比例区:候補者別 (PR Blocks: Candidates)

含まれている変数は以下のとおり。

  • block: 比例ブロック
  • party: 政党
  • rank: 名簿順位
  • name: 候補者の氏名(姓と名の間に半角スペース)
  • yomi: 候補者氏名の読み(姓と名の間に半角スペース)
  • lastname: 候補者の姓
  • firstname: 候補者の名
  • last_kana: 候補者の姓の読み
  • first_kana: 候補者の名の読み
  • age: 年齢
  • recommended: 推薦政党
  • status: 元(元職)、前(前職)、新(新人)
  • previous: 過去の当選回数
  • dup_dist: 重複立候補の選挙区(比例単独候補者は “比例単独”)
  • win_smd: 小選挙区での勝利を示すダミー
  • win_pr: 比例区での勝利を示すダミー
  • loss_ratio: 惜敗率 (%)

比例区:政党 (PR Blocks: Political Parties)

含まれている変数は以下のとおり。 - block: 比例ブロック - party: 政党 - n_cand: 名簿掲載人数 - pr_wins: 比例区での獲得議席数 - smd_wins: 名簿掲載者のうち、小選挙で議席を獲得した人数 - votes: 比例区での得票数 - vshare: 比例区での得票率


以下、R を使った収集方法をする。

Rを用いた2017衆院選データのスクレイピング

A. 準備

必要なパッケージを読み込む。 インストールされていないものは install.packages() でインストールする

# パッケージのインストールが必要な場合はまずインストール
# install.packages("plyr", dependencies = TRUE)
# install.packages("readr", dependencies = TRUE)
# install.packages("XML", dependencies = TRUE)
# install.packages("stringr", dependencies = TRUE)
# install.packages("dplyr", dependencies = TRUE)
library("plyr")
library("readr")
library("XML")
library("stringr")
library("dplyr")

B. データの収集元

今回は朝日新聞2017年衆院選特集ページ からデータを集める。

asahi <- "http://www.asahi.com"  # Base URL
asahi_cand <- str_c(asahi, "/senkyo/senkyo2017/koho")  # info about candidates
asahi_res <- str_c(asahi, "/senkyo/senkyo2017/kaihyo") # results

C. 小選挙区データの収集

まず、候補者情報が掲載されているページのURL を抜き出す。都道府県ごとに1ページずつある。

links <- getHTMLLinks(asahi_cand)
# 同じリンクが2回ずつ登場するので、最初のものだけ使う
cand_links <- str_c(asahi, unlist(links[str_detect(links, 'koho/A')][1:47])) %>%
  as.list()    # 必要なURLのリスト

各ページで、必要な情報をスクレイプするための関数 scrape_cand() を定義する。

scrape_cand <- function(url) {
  ## Argument: url = URL of the target page
  ## Return: res = data frame with scraped info
  
  # Read the page at the URL and parse the page
  source <- readLines(url, encoding = "UTF-8")
  parsed <- htmlParse(source, encoding = "UTF-8")
  
  ## Prefecture
  # ページのタイトルから都道府県名を抜き出す
  pref <- parsed["//title"][[1]] %>%
    xmlValue() %>%
    str_replace(pattern = "-候補者-.+", replacement = "")
  
  ## Name
  last <- parsed["//em[@class='sei']"] %>%
    sapply(xmlValue)    # last names: kanji and kana
  first <- parsed["//em[@class='mei']"] %>%
    sapply(xmlValue)    # first names: kanji and kana
  
  ## Number of candidates
  n2 <- length(last)          # num. of candidates x2 
  n_cand <- n2 / 2            # num. of candidates
  odd <- seq(1, n2, by = 2)   # odd-number index
  
  ## Extract names
  last_kanji <- last[odd]     # extract last name in kanji
  last_kana <- last[-odd]     # extract last name in kana
  first_kanji <- first[odd]   # extract first name in kanji
  first_kana <- first[-odd]   # extract first name in kana
  
  ## Age
  age <- parsed["//td[@class='Age']"] %>%
    sapply(xmlValue) %>%
    as.integer()

  ## Party
  party <- parsed["//td[@class='Party']"] %>%
    sapply(xmlValue)
  
  ## Recommendation 
  recom <- parsed["//td[@class='Suisenshiji']"] %>%
    sapply(xmlValue)
  recom <- ifelse(recom == "", NA_character_, recom)  # NA for no recom.
  
  ## Status(新旧)
  status <- parsed["//td[@class='Status']"] %>%
    sapply(xmlValue)
    
  ## Number of previous wins
  prev <- parsed["//td[@class='Tosenkaisu']"] %>%
    sapply(xmlValue) %>%
    str_replace(pattern = "回", replacement = "")  %>% # extract numbers
    as.integer()
  
  ## Duplicate(重複立候補)
  dup <- parsed["//td[@class='W']"] %>%
    sapply(xmlValue)
  dup <- ifelse(dup == "比例", 1, 0)  # dummy indicating double nomination
  
  ## Number of districts and # of candidates in each area
  Areas <- parsed["//div[@class='areabox']"]
  n_districts <- length(Areas)    # num of dist. in prefecture
  # count candidates in each district
  n_cand_area <- Areas %>%
    sapply(xmlValue) %>%
    str_count(pattern = "略歴") 
  dist <- rep(1:n_districts, n_cand_area)    # vector of district number
    
  pref <- rep(pref, n_cand)  # vector of pref, which is constant within pref
  
  ## create a data frame
  res <- data_frame(prefecture = pref,
                    dist_no = dist,
                    district = str_c(pref, dist, "区"),
                    name = str_c(last_kanji, first_kanji, sep = " "),
                    yomi = str_c(last_kana, first_kana, sep = " "),
                    lastname = last_kanji, firstname = first_kanji,
                    last_kana = last_kana, first_kana = first_kana,
                    age = age, party = party, recommended = recom,
                    status = status, previous = prev, duplicate = dup)
  
  Sys.sleep(1)  # pause one second to make the process slower
  
  return(res)
}

定義した関数を各都道府県の候補者ページで使い、1つのデータフレームにまとめる。

hr2017_dist_cand <- ldply(cand_links, .fun = scrape_cand)

次に、開票結果を集めるため、結果が掲載されているページのURLを抜き出す。都道府県ごとに1ページずつある。

res_links <- getHTMLLinks(asahi_res)
# 同じリンクが2回ずつ登場するので、最初のものだけ使う
res_links <- str_c(asahi, unlist(res_links[str_detect(res_links, 'kaihyo/A')][1:47])) %>%
  as.list()    # 必要なURLのリスト

結果を取得するための関数 scrape_results() を定義する。

scrape_results <- function(url) {
  ## Argument: url = URL of the target page
  ## Return: res = data frame with scraped info
  
  # Read the page at the URL and parse the page
  source <- readLines(url, encoding = "UTF-8")
  parsed <- htmlParse(source, encoding = "UTF-8")
  
  ## Prefecture
  # ページのタイトルから都道府県名を抜き出す
  Pref <- parsed["//title"][[1]] %>%
    xmlValue() %>%
    str_replace(pattern = "-開票速報-.+", replacement = "")
  
  ## Name
  last <- parsed["//span[@class='sei']"] %>%
    sapply(xmlValue)    # last names: kanji and kana
  first <- parsed["//span[@class='mei']"] %>%
    sapply(xmlValue)    # first names: kanji and kana

  ## Number of candidates
  n_cand <- length(last)           # num. of candidates
  
  ## Number of districts and # of candidates in each area
  Areas <- parsed["//div[@class='areabox']"]
  n_districts <- length(Areas)    # num of dist. in prefecture
  # count candidates in each district
  n_cand_area <- Areas %>%
    sapply(as, "character") %>% 
    str_count(pattern = "rose") - 1
  dist <- rep(1:n_districts, n_cand_area)    # vector of district number
    
  pref <- rep(Pref, n_cand)  # vector of pref, which is constant within pref
  
  ## Results
  Win <- parsed["//td[@class='rose']"] %>%
    sapply(as, "character")
  win_SMD <- as.numeric(str_detect(Win, "小選挙区で当選"))
  win_PR <- as.numeric(str_detect(Win, "比例区で当選"))
  
  ## Votes
  Votes <- parsed["//td[@class='num']"] %>%
    sapply(as, "character") %>%
    str_split("<span>") %>%
    sapply(str_extract, "[0-9,.]+")
    
  votes <- Votes[1, ] %>%
    str_replace_all(",", "") %>%
    as.numeric
  
  vshare <- Votes[2, ] %>%
    as.numeric()

  ## create a data frame
  res <- data_frame(district = str_c(pref, dist, "区"),
                    name = str_c(last, first, sep = " "),
                    win_smd = win_SMD,
                    win_pr = win_PR, 
                    votes = votes,
                    vshare = vshare)
  
  Sys.sleep(1)  # pause one second to make the process slower
  
  return(res)
}

各都道府県のページで関数を適用する。

hr2017_dist_res <- ldply(res_links, .fun = scrape_results)

候補者データを選挙結果データと結合する。

hr2017_districts <- full_join(hr2017_dist_cand, hr2017_dist_res,
                               by = c("district", "name"))

データを保存する。

write_csv(h2017_districts, path = "hr2017_districts.csv")

D. 比例区データの収集

続いて、比例区の候補者データを収集する。比例区の候補者情報が掲載されているページのURLを抜き出す。比例ブロックごとに1ページずつある。

# links <- getHTMLLinks(asahi_cand) # リンク先を探すページは小選挙区と同じ
# 同じリンクが2回ずつ登場するので、最初のものだけ使う
pr_links <- str_c(asahi, unlist(links[str_detect(links, 'koho/O')][1:11])) %>%
  as.list()    # 必要なURLのリスト

各ページで、必要な情報をスクレイプするための関数 scrape_pr() を定義する。

scrape_pr <- function(url) {
  ## Argument: url = URL of the target page
  ## Return: res = data frame with scraped info
  
  # Read the page at the URL and parse the page
  source <- readLines(url, encoding = "UTF-8")
  parsed <- htmlParse(source, encoding = "UTF-8")
  
  ## PR Block
  # ページのタイトルから比例ブロック名を抜き出す
  block <- parsed["//title"][[1]] %>%
    xmlValue() %>%
    str_replace(pattern = "(比例区).+", replacement = "")
  
  ## Name
  last_kanji <- parsed["//em[@class='sei']"] %>%
    sapply(xmlValue)    # last names in kanji
  first_kanji <- parsed["//em[@class='mei']"] %>%
    sapply(xmlValue)    # first names in kanji
  last_kana <- parsed["//span[@class='sei']"] %>%
    sapply(xmlValue)    # last names in kana
  first_kana <- parsed["//span[@class='mei']"] %>%
    sapply(xmlValue)    # first names in kana
  
  ## Number of candidates
  n_cand <- length(last_kanji)      # num. of candidates
  block_vec <- rep(block, n_cand)    

  # List Rank
  rank <- parsed["//td[@class='lstNum']"] %>%
    sapply(xmlValue)    # Rank in the party list
  
  ## Age
  age <- parsed["//td[@class='Age']"] %>%
    sapply(xmlValue) %>%
    as.integer()

  ## Recommendation 
  recom <- parsed["//td[@class='Suisenshiji']"] %>%
    sapply(xmlValue)
  recom <- ifelse(recom == "", NA_character_, recom)  # NA for no recom.
  
  ## Status(新旧)
  status <- parsed["//td[@class='Status']"] %>%
    sapply(xmlValue)
    
  ## Number of previous wins
  prev <- parsed["//td[@class='Tosenkaisu']"] %>%
    sapply(xmlValue) %>%
    str_replace(pattern = "回", replacement = "")  %>% # extract numbers
    as.integer()
  
  ## Duplicate(重複立候補)
  dup <- parsed["//td[@class='W']"] %>%
    sapply(xmlValue) %>%
    str_replace(pattern = "\\s", replacement = "比例単独")

  ## Parties and # of candidates from each party
  Parties <- parsed["//div[@class='areabox']"]
  n_parties <- length(Parties)    # num of parties in the block
  ## Count candidates in each party's list
  n_cand_party <- sapply(Parties, xmlValue) %>%
    str_count(pattern = "略歴") 
  ## Assign a party to each candidate
  party_names <- parsed["//div[@class='snkH2Box']"] %>%
    sapply(xmlValue) %>%
    str_replace_all(pattern = "\\n", replacement = "")
  party <- rep(party_names, n_cand_party)    # vector of parties

  ## Create a data frame
  res <- data_frame(block = block_vec,
                    party = party,
                    rank = rank,
                    name = str_c(last_kanji, first_kanji, sep = " "),
                    yomi = str_c(last_kana, first_kana, sep = " "),
                    lastname = last_kanji, firstname = first_kanji,
                    last_kana = last_kana, first_kana = first_kana,
                    age = age, recommended = recom,
                    status = status, previous = prev, dup_dist = dup)
  
  Sys.sleep(1)  # pause one second to make the process slower
  
  return(res)
}

定義した関数を各比例ブロックのページで使い、1つのデータフレームにまとめる。

hr2017_pr_cand <- ldply(pr_links, .fun = scrape_pr)

次に、比例区の開票結果を集める。 対象となるページは、比例ブロックごとに1つずつある。

# res_links <- getHTMLLinks(asahi_cand) # リンク先を探すページは小選挙区と同じ
# 同じリンクが2回ずつ登場するので、最初のものだけ使う
res_pr <- str_c(asahi, unlist(res_links[str_detect(links, 'kaihyo/O')][1:11])) %>%
  as.list()    # 必要なURLのリスト

各ページで、必要な情報をスクレイプするための関数 scrape_pr_res() を定義する。

scrape_pr_res <- function(url) {
  ## Argument: url = URL of the target page
  ## Return: res = data frame with scraped info
  
  # Read the page at the URL and parse the page
  source <- readLines(url, encoding = "UTF-8")
  parsed <- htmlParse(source, encoding = "UTF-8")
  
  ## PR Block
  # ページのタイトルから比例ブロック名を抜き出す
  block <- parsed["//title"][[1]] %>%
    xmlValue() %>%
    str_replace(pattern = "(比例区).+", replacement = "")
  
  ## Name
  last_kanji <- parsed["//span[@class='sei']"] %>%
    sapply(xmlValue)    # last names in kanji
  first_kanji <- parsed["//span[@class='mei']"] %>%
    sapply(xmlValue)    # first names in kanji
    
  ## Number of candidates
  n_cand <- length(last_kanji)      # num. of candidates
  block_vec <- rep(block, n_cand)    

  
  ## Parties and # of candidates from each party
  ## Count candidates in each party's list
  n_cand_party <- parsed["//div[@class='areabox']"] %>%
    sapply(as, "character") %>% 
    str_count(pattern = "rose") - 1
  ## Assign a party to each candidate
  party_names <- parsed["//div[@class='snkH2Box']"]  %>%
    sapply(xmlValue) %>%
    str_replace(pattern = "\\n", replacement = "")
  party_end <- str_locate(party_names, pattern = "\\n")[, 1] - 1
  party_names <- str_sub(party_names, start = 1, end = party_end)
  party <- rep(party_names, n_cand_party)    # vector of parties
  
  ## Results
  Win <- parsed["//td[@class='rose']"] %>%
    sapply(as, "character")
  win_SMD <- as.numeric(str_detect(Win, "小選挙区で当選"))
  win_PR <- as.numeric(str_detect(Win, "比例区で当選"))
  
  ## Loss ratio(惜敗率)
  Ratio <- parsed["//td[@class='ritsu']"] %>%
    sapply(xmlValue) %>%
    as.numeric()

  ## Create a data frame
  res <- data_frame(block = block_vec,
                    party = party,
                    name = str_c(last_kanji, first_kanji, sep = " "),
                    win_smd = win_SMD,
                    win_pr = win_PR,
                    loss_ratio = Ratio)
  
  Sys.sleep(1)  # pause one second to make the process slower
  
  return(res)
}

定義した関数を各比例ブロックのページで使い、1つのデータフレームにまとめる。

hr2017_pr_res <- ldply(res_pr, .fun = scrape_pr_res)

開票結果ページの政党名と候補者ページの政党名が違うところを修正する。

hr2017_pr_res <- hr2017_pr_res %>%
  mutate(party = ifelse(party == "共産党", "日本共産党", party),
         party = ifelse(party == "社民党", "社会民主党", party),
         party = ifelse(party == "自民党", "自由民主党", party))

候補者データと開票結果データを結合し、結果を保存する。

hr2017_pr_candidates <- full_join(hr2017_pr_cand, hr2017_pr_res,
                                  by = c("block", "party", "name"))
write_csv(hr2017_pr_candidates, path = "hr2017_pr_candidates.csv")

E. 比例区データを各ブロックで政党ごとにまとめる

比例区の候補者データをまとめてデータフレームを作る。

hr2017_pr_parties <- hr2017_pr_candidates %>%
  group_by(block, party) %>%
  summarize(n_cand = n(),
            pr_wins = sum(win_pr),
            smd_wins = sum(win_smd))

次に、開票結果のページから各ブロックでの各政党の得票データを収集する。 そのために、各ページで使う関数 scrape_pr_party() を定義する。

scrape_pr_party <- function(url) {
  ## Argument: url = URL of the target page
  ## Return: res = data frame with scraped info
  
  # Read the page at the URL and parse the page
  source <- readLines(url, encoding = "UTF-8")
  parsed <- htmlParse(source, encoding = "UTF-8")
  
  ## PR Block
  # ページのタイトルから比例ブロック名を抜き出す
  block <- parsed["//title"][[1]] %>%
    xmlValue() %>%
    str_replace(pattern = "(比例区).+", replacement = "")
  
  ## Parties
  party_names <- parsed["//div[@class='snkH2Box']"]  %>%
    sapply(xmlValue) %>%
    str_replace(pattern = "\\n", replacement = "")
  party_end <- str_locate(party_names, pattern = "\\n")[, 1] - 1
  party_names <- str_sub(party_names, start = 1, end = party_end)
  n_party <- length(party_names)
  
  ## Results
  Votes <- parsed["//li[@class='OptItm']"] %>%
    sapply(xmlValue) %>%
    str_split(pattern = "得票率|得票|票|%") %>%
    unlist()
  n_vec <- 1:n_party
  votes <- Votes[5*n_vec - 3] %>%
    str_replace_all(pattern = ",", replacement = "") %>%
    as.numeric()
  vshare <- Votes[5*n_vec - 1] %>% as.numeric()

    ## Create a data frame
  res <- data_frame(block = block,
                    party = party_names,
                    votes = votes,
                    vshare = vshare) %>%
    mutate(party = ifelse(party == "共産党", "日本共産党", party),
           party = ifelse(party == "社民党", "社会民主党", party),
           party = ifelse(party == "自民党", "自由民主党", party))

  
  Sys.sleep(1)  # pause one second to make the process slower
  
  return(res)
}

定義した関数を各比例ブロックのページで使い、1つのデータフレームにまとめる。

hr2017_pr_party_res <- ldply(res_pr, .fun = scrape_pr_party)

先ほど作ったデータフレームと結合して保存する。

hr2017_pr_parties <- full_join(hr2017_pr_parties, hr2017_pr_party_res,
                               by = c("block", "party"))
write_csv(hr2017_pr_parties, path = "hr2017_pr_parties.csv")


ホームに戻る