- なぜ文字列操作が必要なのか?
- 基本的な文字列操作(
tidyr::separete
) - 文字列操作パッケージ
stringr
- 文字列を別の型に変換する(
readr
) - より高度に処理するための正規表現
- 正規表現を用いた操作(
tidyr::extract
) - おまけ(スクレイピングあれこれ)
- 文字コードを調べる(サイトのエンコーディングを知る)
- ページ切り替え
- 結構重要なスクレイピング知識
- DOMとは?
- エンコーディングを推定する
2018年12月13日
tidyr::separete
)stringr
readr
)tidyr::extract
)文字列を複数の列に分割するにはtidyr
パッケージが便利です。tidry::separete
関数はsep
パラメータに指定されているセパレータで文字列を分割する関数です。これを用いると先ほどのデータは以下のように分割できます。ただし、分割数はコーディング側で指定する必要があります。
result %>% tidyr::separate(col = X2, into = c("X2_1", "X2_2", "X2_3"))
stringr
tidyr::separete
関数で分割できない場合には文字列操作に特化したstringr
パッケージが便利です。より細かな操作が可能になります。主な関数には以下のようなものがあります。
function | description |
---|---|
stringr::str_sub |
文字列を部分的に参照・変更する |
stringr::str_c |
複数の文字列を結合する |
stringr::str_split |
文字列を分割する(返り値はリスト型) |
stringr::str_extract |
指定したパターンにマッチした部分の文字列を取り出す |
stringr::str_replace |
指定したパターンにマッチした部分のみ置換する |
Rでは数字区切りや単位がある数字は文字型データとして扱われます。このような場合、readr
パッケージを用いると文字型を数値型(intger
型やdouble
型など)に簡単に変換できます。
function | description |
---|---|
readr::parse_logical |
「TRUE, FALSE」などを論理型に変換する |
readr::parse_integer |
数字文字列を整数型に変換する |
readr::parse_double |
数字文字列を実数型に変換する |
readr::parse_number |
数字文字列を数値に変換する |
readr::parse_date |
数字文字列を日付型に変換する(lubridate::as_date と同様) |
文字列を高度に処理する場合に欠かせないのが正規表現です。Rでは拡張正規表現(POSIX 1003.2)が使えます。正規表現は、大雑把にいうとメタ文字とリテラルを組み合わせてマッチさせる文字列を表現するための決まりです。
メタ文字は特殊な意味を持つ文字で、様々な表現(どちらか、繰り返し、除く、など)が可能になります。メタ文字自体を表現したい場合(リテラルとして扱う場合)はバックスラッシュ(\
)を直前に配置します。
リテラルとはメタ文字でない単なる文字列のことです。メタ文字と組み合わせることで多様な表現(パターン)を可能にします。
Operator | Description |
---|---|
| |
どちらか(A|B でA かB のどちらかに一致する) |
* |
0回以上の一致(最長一致) |
*? |
0回以上の一致(最短一致) |
+ |
1回以上の一致(最長一致) |
+? |
1回以上の一致(最短一致) |
? |
0回または1回の一致(1回が優先) |
?? |
0回または1回の一致(0回が優先) |
Operator | Description |
---|---|
. |
任意の一文字 |
() |
括弧内を一文字として扱う |
{n} |
n回の繰り返し(n = 0, 1, 2, …) |
{n, m} |
n回以上、m回未満の繰り返し(n < m, n = 0, 1, 2, …) |
\ |
エスケープシーケンス |
Set Expression | Description |
---|---|
[...] |
[] 内に含まれる一文字に一致([abc] はa , b , c の一文字に一致) |
[...-...] |
[] 内に含まれる- 範囲内の一文字に一致([a-z] は英小文字の一文字に一致) |
[^...] |
[] 内に含まれる一文字とは異なる一文字に一致([^abc] は!a or !b or !c ) |
[]
は|
を簡易に表記するイメージです。例えば[abc]
とa|b|c
は正規表現としては等価の扱いになります。ただし、他の演算子と組み合わせる場合には注意してください。
grep("[^abc]", c("abc", "def", "xyz", "0ab"))
## [1] 2 3 4
頻繁に使われるパターンは名前付き文字クラスとして予め定義されています。代表的な名前付き文字クラスには以下のようなものがあります。
regexp | description |
---|---|
[:alnum:] |
アルファベットと数値、[:alpha:] + [:digit:] |
[:alpha:] |
大小文字アルファベット、 [:lower:] + [:upper:] |
[:digit:] |
数値 |
[:punct:] |
パンクチュエーション文字 ! " # $ % & ' ( ) * + , - . / |
[:space:] |
空白文字、タブ、改行、水平タブ、給紙、キャリッジリターン、空白 |
前述の正規表現を用いるとtidyr::extract
関数のようにセパレータの代わりに正規表現を用いてより細かい文字列操作が可能になります。
tidyr::extract(data, col, into, regex = "([[:alnum:]]+)", ...)
このような関数は 大相撲の取組表 のようなデータを処理するのに適しています。
最近ではあまりないようですが、サイトによっては文字コードにShift JIS
などを使っていて、rvest
パッケージで取得した情報が文字化けする場合があります。このような時にはヘッダーから文字コードの情報を取得(rvest::guess_encording
を使うより確実)してエンコードを変換します。
まず、ヘッダ(<head></head>
で囲まれた部分)にある文字コードのメタ情報(<meta
で始まりcharset=
が記載されいる部分)をrvest::html_attrs
関数を用いて取得します。
## [[1]] ## http-equiv content ## "Content-Type" "text/html; charset=UTF-8"
次に"charset="の後ろにある文字列の開始位置と終了位置をstringr
パッケージを用いて取得して、切り出します。
meta %>% stringr::str_sub(start = str_locate(., pattern = "charset=")[2] + 1, end = stringr::str_locate(., pattern = '\\)')[1] - 2)
## [1] "UTF-8"
終了位置を「\)
」で判定しているのは、rvest::html_attrs
関数の返り値が属性値を持ったリスト型であるため実際の文字列部分が下記のように格納されているためです。
meta %>% as.character()
## [1] "c(\"Content-Type\", \"text/html; charset=UTF-8\")"
もしくは、単純に属性名で参照して以下のように取り出すことも可能です。
meta[[1]]["content"]
## content ## "text/html; charset=UTF-8"
meta[[1]]["content"] %>% stringr::str_sub(start = stringr::str_locate(., pattern = "charset=")[2] + 1, end = stringr::str_length(.))
## [1] "UTF-8"
文字コードを決め打ちせずにメタ情報から取得することで、サイトのエンコーディングが変更されてもRのコードを変更することなくスクレイピングが可能になります。
スクレイピング時にページ切り替えを行うにはRSelenium
のようなパッケージが必要なことも多いですが、リンクのURLをよくよく見るとコードの工夫でページを切り替えることができるサイトも多いです。例えば、Kabutanのサイトは下図のように表を切り替えるようになっています。
表切り替えのリンクを調べるとpage=n
となっていることがわかります。つまり表を切り替えるためにはSeleniumのようなツールでリンクをクリックする必要はなくURLの指定を変更すれば良いことが分かります。
https://kabutan.jp/stock/kabuka?code=0000&ashi=day&page=2 # 2ページ目へのリンク https://kabutan.jp/stock/kabuka?code=0000&ashi=day&page=3 # 3ページ目へのリンク
すなわち以下のようなコードで切り替えるページのURLを生成すれば、Seleniumなどを利用しなくてもスクレイピングが可能になります。
"https://kabutan.jp/stock/kabuka?code=0000&ashi=day&page=" %>% paste0(., "n")
## [1] "https://kabutan.jp/stock/kabuka?code=0000&ashi=day&page=n"
※最初に処理対象の特徴を掴むことがスクレイピングのポイントです。
スクレイピングを実体験すると分かりますがテスト自動化にも使えます。この時に重要なポイントがCSSセレクタをどのように設計・実装するかです。テスト自動化を考慮しないで設計・実装すると画面構成の修正が発生するたびにセレクタ情報が変化してしまい、実装したスクレイピング・コードに手直が発生しテスト自動化が本末転倒な状態になってしまいます。
例えば Yahoo!スポーツの大相撲 では以下のようにID
情報を用いることで画面構成に変化が出てもテーブル・セレクタの変更なしに取得できるようになっています。
"//*[@id=\"makuuchi\"]/table" # 幕内取組一覧(テーブル)のCSSセレクタ "//*[@id=\"juryo\"]/table" # 十両取組一覧(テーブル)のCSSセレクタ
サイトの情報を見るとその会社がどの程度テスト自動化を考えているかが見えてきます。
Document Object Modelの略でHTMLドキュメントやXMLドキュメントをプログラムから利用するための仕組みです。 Web Hypertext Application Technology Working Group (WHATWG) が仕様を定義しています。
Webサイトに対するスクレイピングを行っていると取得したデータが文字化けを起こしている場合があります。多くはUTF-8
とは異なるエンコーディング(文字セット)が使用されている場合であり正しくエンコード指定をすれば文字化けは解消します。rvest
パッケージにはエンコードを推定するrvest::guess_encoding
関数が用意されており、この関数がどの程度正確にエンコードを推定するかを確認してみます(最大七候補出力されますが画面の都合上、上位三候補のみ表示)。
encoding | language | confidence |
---|---|---|
UTF-8 | 1.00 | |
windows-1252 | en | 0.28 |
windows-1250 | ro | 0.19 |
encoding | language | confidence |
---|---|---|
Shift_JIS | ja | 0.69 |
GB18030 | zh | 0.37 |
Big5 | zh | 0.31 |
encoding | language | confidence |
---|---|---|
UTF-8 | 1.00 | |
Shift_JIS | ja | 0.59 |
GB18030 | zh | 0.38 |
encoding | language | confidence |
---|---|---|
UTF-8 | 1.00 | |
Shift_JIS | ja | 0.72 |
GB18030 | zh | 0.57 |
encoding | language | confidence |
---|---|---|
UTF-8 | 1.00 | |
Shift_JIS | ja | 0.71 |
windows-1252 | en | 0.28 |
encoding | language | confidence |
---|---|---|
UTF-8 | 1.00 | |
Shift_JIS | ja | 0.72 |
GB18030 | zh | 0.52 |
encoding | language | confidence |
---|---|---|
UTF-8 | 1.00 | |
Shift_JIS | ja | 0.56 |
windows-1252 | en | 0.25 |
CC BY-NC-SA 4.0, Sampo Suzuki