Rでスクレイピングをするならrvestパッケージを使うのがベスト。表データ、リンク先URL、テキストなどのデータを簡単に入手できます。netkeibaのレース結果を題材にrvestパッケージの使い方をまとめています。
スクレイピングは大きく3ステップ
スクレイピングは大きく分けると以下の3ステップで実現できます。
- read_htmlでhtmlデータを取得
- html_element or html_elementsでノードを取得
- ノードセットから、html_text, html_atr, html_tableを用いて必要なデータを抽出
スクレイピングする場合の注意
スクレイピングする場合は先方のサーバーに負荷をかけないように、必ず「データを取得したら1秒待つ」等の対応を行なってください。
read_htmlでhtmlデータを取得
指定したurlからウェブページの情報を取得するために、まず、実行するのがread_html()関数。
使い方
read_html(x, encoding = "")
x : URLの文字列
encoding : デフォルトのエンコーディングを指定する。特に指定がない場合、
UTF-8またはUTF-16とみなされます。
早速、使ってみます。netkeibaの2015年の皐月賞の結果をスクレイピング対象とします。
library(tidyverse)
library(rvest)
url <- "https://db.netkeiba.com/race/201506030811/" # 2015年皐月賞のURL
html <- read_html(url)
Sys.sleep(1) # データを取得したら1秒待つ
実行してみると、read_html関数の部分で以下のようなエラーが出てしまいました。
> html <- read_html(url) read_xml.raw(raw, encoding=encoding, base_url=base_url, as_html=as_html, でエラー: input conversion failed due to input error, bytes 0xC30 x320 x300 x31 [6003]
htmlファイルを読む際、インプットの変換にエラーが出る、ということなので、おそらくエンコーディングが違うためだろうと考えられます。
Chromeを使って、htmlファイルのエンコーディングを調べます。Chromeで右クリックで検証を開くとhtmlページの詳細を確認することができます。
htmlファイルでエンコーディングを指定するキーワードは「charset」です。Chromeの真ん中あたりに「検索」があるのでキーワード「charset」を調べてみると、以下のように出力されます。これをみるとどうやら、エンコーディングはeuc-jpのようです。
エンコーディングを指定して、read_htmlを実行すると、今度はエラーメッセージなく完了します。
html <- read_html(url, encoding = "euc-jp")
Sys.sleep(1) # データを取得したら1秒待つ
これでhtmlページデータを取得することができました。
read_htmlの戻り値
read_html()関数で得られたデータ(html)を出力してみます。
> html {html_document} <html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja" id="html"> [1] <head>\n<title>皐月賞|2015年4月19日 | 競馬データベース - netkeiba.com</title>\n<meta http-equiv="X-UA-Compati ... [2] <body class="db" id="db_race_detail">\n<div id="page">\n\n<link rel="stylesheet" href="https:// ...
これは読み込んだhtmlファイルのツリー構造を示していて、トップが<head>と<body>で示されるノードであることを表しています。ドキュメントの本体は<body>の下に存在します。
<body>タグの下のノードにアクセスするには、以下のようにhtml_children()関数を使います。
x <- html_children(html)[2] # x = htmlの子供の2番目 = <body>タグの要素 html_children(x) # xの子供
> html_children(x) {xml_nodeset (2)} [1] <div id="page">\n\n<link rel="stylesheet" href="https://cdn.netkeiba ... [2] <div id="geniee_overlay_outer" style="position:fixed; bottom: 0px; w ...
これを延々と繰り返していけば、必要とする情報に辿り着くことができます。とはいえ、延々とこれを繰り返すのも大変。当然、別の方法があります。html_element()関数やhtml_elements()関数を使ってノードを取得するやり方です。
html_element , html_elementsでノードを取得
CSSセレクタまたはXPathで指定されたノードをhtml_element()やhtml_elements()で抽出できます。
使い方
html_element(x, css, xpath)
html_elements(x, css, xpath)
x : ドキュメント、ノードセット、ノード
css, xpath: 抽出する要素を示すCSSセレクタ、または、XPath。
cssとxpathはどちらか一つしか引数に使えません。
戻り値
html_element() : xの要素数と同じ数のノードセット。
各xに対し一番最初にマッチしたもののみ
html_elements() : 該当する全ての要素のノードセット。
スクレイピングに必要なCSSやXPathについてはこちらでまとめています。
抽出したい情報部分のCSSセレクタやXPathはChromeの検証ツールを使います。
Chromeの検証ツールを用いて、抽出したい部分のタグを見つけ、そのタグのところで右クリックして「コピー」 – 「selectorをコピー」または「XPathをコピー」を選ぶことで、CSSセレクタ、XPathを取得することができます。
コピーした結果をhtml_element()やhtml_elements()のcss,xpathに貼り付ければ当該部分のノードセットを取得できます。下のコードは「selectorをコピー」した場合(赤線部分にコピー結果をペーストしています)
tbl <- html_element(html, css = "#contents_liquid > table")
tbl
> tbl {html_node} <table class="race_table_01 nk_tb_common" summary="レース結果" cellspacing="1" cellpadding="0"> [1] <tr class="txt_c">\n<th nowrap>着<br>順</th>\n<th nowrap>枠<br>番</th>\n<th nowrap>馬<b ... [2] <tr>\n<td class="txt_r" nowrap>1</td>\n<td class="w2ml" align="right" nowrap><span ... [3] <tr>\n<td class="txt_r" nowrap>2</td>\n<td class="w3ml" align="right" nowrap><span ... [4] <tr>\n<td class="txt_r" nowrap>3</td>\n<td class="w4ml" align="right" nowrap><span ... [5] <tr>\n<td class="txt_r" nowrap>4</td>\n<td class="w1ml" align="right" nowrap><span ... [6] <tr>\n<td class="txt_r" nowrap>5</td>\n<td class="w8ml" align="right" nowrap><span ... [7] <tr>\n<td class="txt_r" nowrap>6</td>\n<td class="w5ml" align="right" nowrap><span ... [8] <tr>\n<td class="txt_r" nowrap>7</td>\n<td class="w5ml" align="right" nowrap><span ... [9] <tr>\n<td class="txt_r" nowrap>8</td>\n<td class="w6ml" align="right" nowrap><span ... [10] <tr>\n<td class="txt_r" nowrap>9</td>\n<td class="w2ml" align="right" nowrap><span ... [11] <tr>\n<td class="txt_r" nowrap>10</td>\n<td class="w7ml" align="right" nowrap><spa ... [12] <tr>\n<td class="txt_r" nowrap>11</td>\n<td class="w8ml" align="right" nowrap><spa ... [13] <tr>\n<td class="txt_r" nowrap>12</td>\n<td class="w6ml" align="right" nowrap><spa ... [14] <tr>\n<td class="txt_r" nowrap>13</td>\n<td class="w4ml" align="right" nowrap><spa ... [15] <tr>\n<td class="txt_r" nowrap>14</td>\n<td class="w7ml" align="right" nowrap><spa ... [16] <tr>\n<td class="txt_r" nowrap>15</td>\n<td class="w3ml" align="right" nowrap><spa ...
ノードセットからデータの抽出
データが記載されているノードセットを選択することができたら、あとは、そこから必要なデータを抽出するだけ。データ抽出には、html_text(), html_text2(),html_attr(), html_table()などを使います。
html_text(), html_text2()関数
html_text(), html_text2()関数を使用することで、ドキュメント、ノード、ノードセットに含まれるデータをテキストで抽出できます。
使い方 html_text(x, trim = FALSE) html_text2(x, preserve_nbsp = FALSE) 引数 x : ドキュメント、ノード、ノードセット trim : TRUEの場合、文字の先頭末尾の空白が覗かれる preserve_nbsp : nbsp(Non Breaking Spaces)を保つかどうか 「Non Breaking Spaces」は英文等で「ここの空白では改行したくない」 という時に使われる 戻り値 xと同じ要素数の文字列ベクトル html_text2()を使うと、ブラウザでの見え方にならうような 文字列を返す(</br>を"\n"に書き換える)が、スピードは遅くなる。
html_attr(), html_attrs()関数
html_attr(), html_attrs()関数を使用することで、ドキュメント、ノード、ノードセットに含まれる属性(リンク属性、等)を抽出できます。
使い方 html_attr(x, name, default = NA_character_) html_attrs(x) 引数 x : ドキュメント、ノード、ノードセット name : 抽出したい属性の名前 default : 指定した属性が村勢しなかった場合に返す値 戻り値 属性の値(html_attr()の場合はベクトル、 html_attrs()の場合はリスト)
html_table()関数
html_table()関数を使用することで、htmlの表をdata.frame形式で抽出できます。
使い方 html_table( x, header=NA, trim=TRUE, fill= deprecated(), dec=".", na.strings = "NA", convert=TRUE ) 引数 x : ドキュメント、ノード、ノードセット header : 1行目をヘッダとして使うかどうか。 NAの場合、1行目に<th>タグがあれば1行目を使う。 trim : 各セルの先頭末尾の空白を取り除くかどうか fill : 非推奨なので基本的には使わない dec : 小数点に使う文字を指定する na.strings: NAとして変換する値(convertがTRUEの場合) convert: テキストをinteger, double, NAに変換するかどうか
レース結果の抽出
レース結果は表になっているので、html_table()関数を使います。
df <- html_table(tbl)
結果は以下の通り。
はじめから、すべてのコードをまとめて書くと以下のようになります。たった6行でスクレイピングができてしまうんです。簡単ですね。
library(tidyverse)
library(rvest)
url <- "https://db.netkeiba.com/race/201506030811/" # 2022年皐月賞
html <- read_html(url, encoding = "euc-jp")
Sys.sleep(1) # データを取得したら1秒待つ
tbl <- html_element(html, css = "#contents_liquid > table")
df <- html_table(tbl)
library(tidyverse)
library(rvest)
url <- "https://db.netkeiba.com/race/201506030811/" # 2022年皐月賞
df <- url %>% # urlから
read_html(encoding="euc-jp") %>% # htmlページを読み込んで、
html_element(css="#contents_liquid > table") %>% # 抽出したい部分を選択して
html_table() # data.frameに読み込む
Sys.sleep(1) # データを取得したら1秒待つ
パイプ演算子についてはこちら。
html_text(), html_attrs()の使用例
html_text()の使用例
レース名を取得します。
html_element(html, css="#main h1") %>% html_text()
> html_element(html, css="#main h1") %>% + html_text() [1] "第75回皐月賞(G1)"
html_attrs()の使用例
馬名をクリックした時に移動するリンク先のURLを取得します。
html_elements(tbl, "tr > td:nth-child(4) > a") %>% html_attr("href")
> html_elements(tbl, "tr > td:nth-child(4) > a") %>% + html_attr("href") [1] "/horse/2012104511/" "/horse/2012104889/" [3] "/horse/2012102013/" "/horse/2012104799/" [5] "/horse/2012106160/" "/horse/2012104668/" [7] "/horse/2012104164/" "/horse/2012104672/" [9] "/horse/2012103484/" "/horse/2012103165/" [11] "/horse/2012103193/" "/horse/2012104084/" [13] "/horse/2012100517/" "/horse/2012104823/" [15] "/horse/2012106019/"
まとめ
rvestパッケージを使えば、簡単にRでスクレイピングを行うことができます。Chromeを使ってCSSセレクタ、XPathを選ぶ時に若干トライアンドエラーが必要になるかもしれませんが、慣れてくればすぐに適切な適切なCSSセレクタ、XPathを選べるようになります。ぜひ挑戦してみてください。