東京都の高校の情報を入手する

前回,東京都の高等学校一覧から,各校のWikipediaの記事へのリンクを収集したので,各校の記事から情報を取得していきます。
analyst-in-rekisen.hatenablog.com

引き続きRのrvestパッケージと,dplyrパッケージを使います。
Wikipediaの学校の記事にはテンプレートがあり,国公私立の別,住所などはひとつのテーブルにまとまっているので簡単です。

1. 検索するためのXPathの用意

各校の記事から,国公私立の別,共学か別学か,課程(全日制・通信制…),住所,の情報を検索するためのXPathを準備しておきます。
XPathをRの文字型でもつので,XPaht内の引用符''のエスケープが必要です。また,文字コードも変換しておきます。
記事をいくつかみてみると,住所のところは2段組になっていて1段目が住所,2段目が経緯度のようなので,1段目だけとります。

###################
##XPathで検索するときのパス
###################
info_label <- c("国公私立の別", "共学・別学", "課程")
names(info_label) <- c("sector", "gender", "course")
path_list <- list(NULL)
for( i in 1:length(info_label)){
	path_list[[i]] <- (
		paste('//table[@class=\'infobox\']/tr/td[../th/text()=\''
			, info_label[i]
			, '\']'
			, sep=""
			)
		)
	names(path_list)[i] <- names(info_label)[i]
}
##住所情報のパスだけは別立て
path_list[[ length(path_list) + 1 ]] <- (
	'//node()[preceding-sibling::tr/th/text()=\'所在地\']/td/div[1]'
)
names(path_list)[length(path_list)] <- "address"
##文字コードを直す
for(i in 1:length(path_list)){
	path_list[[i]] <- (
		path_list[[i]] 
		%>% iconv(from = "", to = "UTF-8")
	)
}

できあがったのは以下のようなものです。

> path_list
$sector
[1] "//table[@class='infobox']/tr/td[../th/text()='国公私立の別']"

$gender
[1] "//table[@class='infobox']/tr/td[../th/text()='共学・別学']"

$course
[1] "//table[@class='infobox']/tr/td[../th/text()='課程']"

$address
[1] "//node()[preceding-sibling::tr/th/text()='所在地']/td/div[1]"

2. 各校の記事の情報を取得する。

前回取得した学校の一覧は,Rのmatrixで, school_info_unique にもっていて,text列に学校名,href列にリンク先が入っています。

#####################
##各校の記事を取得してリストにする。
#####################
school_kiji_list <- list(NULL)

for(i in 1:nrow(school_info_unique)){
	url_kiji <- paste("https://ja.wikipedia.org"
				,school_info_unique[i,"href"]
				, sep="")
	school_kiji_list[[i]] <- read_html(url_kiji)
}

##大きさの確認
length(school_kiji_list)
##名前をつけておく
names(school_kiji_list) <- school_info_unique[,"text"]

けっこう時間がかかったので,進行状況とか表示させたらよかったかもしれません。

3. 各校の記事から必要な情報を抜き出す

2で取得した各校の記事から,1で作成しておいたXPathを使って情報を抜き出します。情報が掲載されていない場合があるのと,2段に分かれていたりするとやっかいなので,ノードが1個のときだけ抜き出すようにしました。

####################
##各校の記事から情報を抜き出す
###################

##結果の入れ物を作る
each_school_info <- matrix(  ""
					, nrow= length(school_kiji_list)
					, ncol=(length(path_list)+1)
				)
colnames(each_school_info) <- c("name", names(path_list))
for( i in 1:length(school_kiji_list)){
	each_school_info[i,"name"] <- names(school_kiji_list)[i]
	for( infoname in names(path_list)){
		#特定の情報のノードを取得してテキストを取り出す
		#その情報が含まれていない(ノードが見つからない)場合,複数ある場合は""のまま
		node_tmp <-(school_kiji_list[[i]] %>% html_nodes(xpath=path_list[[infoname]]))
		each_school_info[i,infoname] <- (
			ifelse(length(node_tmp) == 1 
					,html_text( node_tmp )
					,""
				)
		)
	}
	#時間かかりそうだから終わったら表示する
	msg <- paste("Finish: ", i, " : ", names(school_kiji_list)[i], sep="") 
	print(msg)
	rm(msg)
}

4. いちおう確認する

まず,各列にどんな値が起きているか確認しておきます。
国公私立の別は…

unique(each_school_info[,"sector"])

結果はこちら

[1] "国立学校"
[2] "私立学校"
[3] "公立学校(区立)"
[4] ""
[5] "\n私立学校\n設置者=学校法人昭和女子大学"
[6] "公立学校"
[7] "公立学校(都立)"

これは,国立,公立,私立にコーディングしなおす必要がありそうです。また,設置者がとれていないところもあるので別途調査が必要です。

共学・別学のほうは…

unique(each_school_info[,"gender"])

結果はこちら

[1] "男女別学(女子校)"
[2] "女子校→男女共学→男女別学"
[3] "男女共学"
[4] "女子校"
[5] "男女別学(男子校)"
[6] "男女別学\n(男子校)"
[7] "男女共学\n健康・スポーツ進学コースに限り男子のみ"
[8] "女子校(全日制)男女共学(通信制)"
[9] "男女別学(女子校)"
[10] "男女別学"
[11] "男子校"
[12] "共学校"
[13] "共学部は男女共学\n女子部は男女別学"
[14] ""
[15] "高等学校は一部男女共学\n中学校は男女共学"
[16] "男女共学※中学校男女別学(女子校)※高校"
[17] "男女共学(男女ほぼ同数)"
[18] "女子校\n音楽科は男女共学"
[19] "男女別学定時制(男子校)\n通信制は男女共学"
[20] "男女共学(男女比1:1)"
[21] "男女別学(女子のみ)"
[22] "男女別学・男子校"

こちらもバリエーションがあります。全日制と定時制でちがうとか,学科で違うというのは何か方針を決めてコーディングしなおす必要がありそうです。また情報が取れていないところは別途確認です。

課程のところは…

unique(each_school_info[,"course"])

結果はこちら

[1] "全日制課程"
[2] ""
[3] "全日制課程\n通信制課程"
[4] "昼間定時制課程"
[5] "通信制課程"
[6] "全日制課程定時制課程通信制課程"
[7] "全日制"
[8] "定時制課程通信制課程"
[9] "定時制(3部制)課程"
[10] "全日制課程定時制課程"
[11] "全日制課程\n定時制課程"
[12] "全日制課程、定時制課程"
[13] "全日制課程(全18学級)\n定時制課程(全10学級)"
[14] "全日制課程(6学級)\n定時制課程(2学級)"
[15] "定時制課程"
[16] "全日制課程(全24学級)\n定時制課程(全7学級)"
[17] "通信制課程全日制課程(最終募集は2015年3月)"
[18] "昼間定時制課程\n通信制通学課程\n通信制技能連携課程"
[19] "全日制課程(平日コース)通信制課程(土曜コース)"
[20] "全日制課程、通信制

これも,共学・別学と同様にコーディング方針を考える必要があります。

最後に,住所のところに東京以外のものがあるようなので確認します。
grepでname列に"東京都"を含む行を検索して,それにマイナスをつけて反転します。

each_school_info[
	 -grep(pattern="東京都", x=each_school_info[,"address"])
	,]

該当するのは,以下の12件でした。

name sector gender course address
[1,] "ウィッツ青山学園高等学校" "私立学校" "男女共学" "全日制課程\n通信制課程" "三重県伊賀市北山1373番地"
[2,] "クラーク記念国際高等学校" "私立学校" "男女共学" "全日制課程\n通信制課程" "北海道深川市納内町三丁目2番40号\n(北海道本校)"
[3,] "八洲学園高等学校" "私立学校" "男女共学" "通信制課程" "大阪府堺市西区鳳中町7丁225-3北緯34度32分13.2秒東経135度27分22秒"
[4,] "大智学園高等学校" "私立学校" "男女共学" "通信制課程" "福島県双葉郡川内村大字上川内字町分143"
[5,] "学芸館高等学校" "私立学校" "男女共学" "通信制課程" "群馬県高崎市高松町14番2"
[6,] "屋久島おおぞら高等学校" "私立学校" "男女共学" "通信制課程" "鹿児島県熊毛郡屋久島町平内34-2"
[7,] "文教大学付属高等学校" "" "" "" ""
[8,] "日本ウェルネス高等学校" "私立学校" "男女共学" "通信制課程" "愛媛県今治市大三島口総4010番地"
[9,] "星槎国際高等学校" "私立学校" "男女共学" "通信制課程" "北海道芦別市緑泉町5-12"
[10,] "青森山田高等学校" "私立学校" "男女共学" "全日制課程、通信制課程" "青森県青森市青葉三丁目13番40号北緯40度48分14.0秒東経140度44分57.5秒"
[11,] "飛鳥未来高等学校" "私立学校" "男女共学" "通信制課程" "奈良県天理市櫟本町1514-3"
[12,] "鹿島学園高等学校" "私立学校" "男女共学" "全日制課程\n通信制課程" "茨城県鹿嶋市田野辺141-9

これをみるに,通信制などの東京校等が一覧に出ていて,リンク先が本校の記事になっているようです。ちょっと考える必要がありそうです。また,経緯度は外したはずだったのが,くっついているのもあるので,これはうまく削る必要があります。

ということで,次回はコーディングの統一や抜けている情報の収集といった泥くさい作業が中心になりそうです。

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

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
  }
}

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