Rでスクレイピングするならrvest 表もリンクもテキストも

R

Rでスクレイピングをするならrvestパッケージを使うのがベスト。表データ、リンク先URL、テキストなどのデータを簡単に入手できます。netkeibaのレース結果を題材にrvestパッケージの使い方をまとめています。

スクレイピングは大きく3ステップ

スクレイピングは大きく分けると以下の3ステップで実現できます。

  1. read_htmlでhtmlデータを取得
  2. html_element or html_elementsでノードを取得
  3. ノードセットから、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の子供
これを実行すると、x(bodyタグの要素)の子供は2つのdiv要素であることがわかります。
> 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についてはこちらでまとめています。

スクレイピングに必要な最低限のHTML/CSSの知識
スクレイピングでデータを収集するためには、HTMLで書かれたWebページの文書構造を理解し、どこに目的のデータが記載されているかをコンピュータに教えてやる必要があります。このページではスクレイピングに最低限必要なHTML/CSSの知識をまとめています。

 

抽出したい情報部分の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)の中身は以下のようになります。15頭立てで、1行目がヘッダになっていそうな感じです。
> 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)
Rのパイプ演算子を使うとこんな風にも書けます。
「〇〇して、〜〜して、××する」
みたいに書けて、考えが途切れないので、コードを書きやすく、読みやすくできます。
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秒待つ

 

パイプ演算子についてはこちら。

R : パイプ演算子( %>% )でコードの可読性を高めよう
Rではライブラリmagrittrをロードしてパイプ演算子(%>%)を使うことにより、コードの可読性を高めることができます。ここではパイプ演算子の使い方について整理します。

 

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を選べるようになります。ぜひ挑戦してみてください。