東京都の高校一覧を入手する。

Wikipediaの,「東京都高等学校一覧」という記事から,高校の一覧と各校のページへのリンクを取得します。
Rでスクレイピングを行なうrvestパッケージを使いました。

だいたいの方針は以下の記事の内容を参照しました。

ホテル街を見つける(1)Rでスクレイピング入門 - 社会ノマド

おおまかに,以下の3ステップです。

  1. 記事の情報の,XML文書としての取得。
  2. XML文書中の必要な情報の切り出し。
  3. Rで扱い易い形式への整形

1.記事の情報の取得

rvestパッケージでちょちょいとできます。日本語を含むURLなので文字コードを適切に変換する必要があります。dplyrパッケージでパイプ記法を使っています。

# install.packages("rvest")
library(rvest)
library(dplyr)

# URLをread_html関数に渡すとHTMLが読み込まれる
# URLを適切にエンコード
url_all_utf8 <- (
	"https://ja.wikipedia.org/wiki/東京都高等学校一覧"
	%>% iconv(from = "", to = "UTF-8") %>% URLencode
)
all_info_html <- read_html(url_all_utf8)

2. 必要な情報の切り出し

ここが,もっとも苦労しました。最初は,< a > タグのうち,テキストに「学校」などを含んでいるものを抜き出せばよいと思ったのですが,それほど甘くありません。

たとえば,「目次」にはページ内の「国立高等学校・国立中等教育学校」へのリンクがありますし,「関連項目」には「東京私立中学高等学校協会」の記事へのリンクはられていて,これらも該当してしまいます。

F12キーを押して,HTMLのソースとにらめっこした結果,以下のような方針に落ち着きました。
XMLについてよく分かっていないので,ノードとか属性とかの言葉遣いが誤っているかもしれません。

  1. ひとまず,目次より後,関連項目より前のa要素を抽出する。
  2. 目次は,id属性がtocになっている,div要素ということで特定する。
  3. 関連項目は,h2の下位にある,span要素で,テキストが"関連項目"になっているものという条件で特定する。テキストが"関連項目"のspanというだけだと,目次の中の関連項目へのリンクも該当してしまう。

この条件を,XPathで表現するのにまたひと苦労でした。
ここでは以下が参考になりました。
XPathちーとシート
XPathの不便なところ - ぶろぐ。@はてな

ひっかかったのは以下の2点です。

  1. XPathとして渡す文字列も文字コードを変換する必要がある。
  2. Rの文字列の中で,XPath中の引用符を \' などとしてエスケープする。
####### 必要な箇所を引っ張る
##目次より後方で,関連項目(目次内でない)より前方の,Siblingのなかで,
##それらのノードの下位にあるli/aをとる
##注意点は2か所
##xpath中の引用符をエスケープする
##encodingをなおす
path0 <- (
	paste('//node()[preceding-sibling::'
		,'div[@id=\'toc\']'
		,' and following-sibling::'
		,'h2[span/text()=\'関連項目\'][span/@class=\'mw-headline\']'
		,']//li/a[@title and @href]'
		,sep = "" )
	%>% iconv(from = "", to = guess_encoding(all_info_html)$encoding[1])
)

info_node <- all_info_html %>% html_nodes(xpath=path0)

3. Rで扱い易い形式への整形

XMLノードとして取り出したものを,Rの行列の形にします。rvestパッケージのhtmel_attr, html_text 関数を使います。html_attr関数は指定した属性の値を取り出してくれます。

# href と title に分ける
size_all <- info_node %>% length
all_info <- matrix( "", nrow=size_all, ncol=3)
colnames(all_info) <- c("text", "title", "href")
for(i in 1:size_all){
	(all_info[i, "text"] 
		<- info_node[[i]] %>% html_text() )
	(all_info[i, "title"] 
		<- info_node[[i]] %>% html_attr(name="title") )
	(all_info[i, "href"] 
		<- info_node[[i]] %>% html_attr(name="href") )
}

これで記事内のリンクを取り出せたわけですが,念のため学校に関するものだけに絞ります(結果として,この作業はなくても同じでした)。テキストか,titleに「学校」が入っていればいいだろうと思ったのですが,「玉川学園高等部」だとか「アメリカンスクール・イン・ジャパン」だなんていうものがあって,結局,「高等,中等,スクール」のいずれかを含むという条件にしました。

# text か title に 「学校,高等,中等,スクール」のいずれかを含むものの添え字
# 中等教育学校や,玉川学園高等部みたいなのがあるから注意
# アメリカンスクール・イン・ジャパン	なんていうのも
school_ind_all <- (
	union(
		(all_info[,"text"] %>% grep(pattern=".*学校|.*スクール|.*高等|.*中等"))
		,(all_info[,"title"] %>% grep(pattern=".*学校|.*スクール|.*高等|.*中等"))
	) %>% sort()
)

#いちおう,絞るがここでは同じ
school_info <- all_info[school_ind_all,]

あとは,いくつか同じページへのリンクがあるので,整理します(分校のようなもので,本校へのリンクしかないなど)。

#同じリンク先があるか確認
#レコードの数
nrow(school_info) #=>456
#ユニークなURL
size_unique <- length(unique(school_info[,"href"])) #=> 453
#重複を除いていく
##URLで整列させて直近のやつと比較していく
temp_mat <- school_info[order(school_info[,"href"]),]
##入れ物
school_info_unique <- matrix("", nrow = size_unique, ncol = 4)
colnames(school_info_unique) <- c("text", "title", "href", "alltext")

hreflook <- 1
hrefcnt <- 0
for( i in 1:nrow(temp_mat) ){
 
  if( school_info_unique[hreflook, "href"] == temp_mat[i,"href"] ){
    school_info_unique[hreflook, "alltext"] <- (
      paste( school_info_unique[hreflook, "alltext"]
            , temp_mat[i, "text"]
            , sep = ";"
            )
    )
  } else {
     hrefcnt <- hrefcnt + 1
     school_info_unique[hrefcnt, "text"] <- temp_mat[i, "text"]
     school_info_unique[hrefcnt, "title"] <- temp_mat[i, "title"]
     school_info_unique[hrefcnt, "href"] <- temp_mat[i, "href"]
     school_info_unique[hrefcnt, "alltext"] <- temp_mat[i, "text"]
     hreflook <- hrefcnt
  }
}

ひとまず,ここまでです。ここで収集したリンクをたどって各校の情報を集めるというのが次のお題です。