京橋のバイオインフォマティシャンの日常

南国のビーチパラソルの下で、Rプログラムを打ってる日常を求めて、、Daily Life of Bioinformatician in Kyobashi of Osaka

R言語で、サイコロ・ゲームを一様分布に従う確率でシミュレーションしてみた件

サイコロゲーム - 序章

サイコロを振って、1から6のそれぞれの目が出る確率は、等しいと仮定する。

いわゆる、一様分布に従うと考える。

Rでは、一様分布に従う、1〜6までの整数値の乱数は、以下で表される。

as.integer( runif(1, min = 1, max = 7) )  

この乱数で、サイコロの振る舞いはシミュレーションできるので、ちょっとしたアニメーションを作ってみた。

デフォルト設定だと、6の目が出ずに、サイコロを10回振り切れれば、ゲーム・クリアという設定にしてみた。

#下記のSaikoroGame関数をコピペせずに、Gitからsourceする
source("https://gist.githubusercontent.com/kumeS/a4c708ed1a9b73e886313389980c4793/raw/b2f9dab698cb3332ff835fe8a8f7d9cd1fd40fcd/SaikoroGame.R")

#実行01: デフォ実行 + 保存
SaikoroGame()

実行結果(1)

https://kumes.github.io/Blog/SaikoroGame/Animation_SaikoroGame03.gif

#実行02: おまけ写真付き + 保存
SaikoroGame(Saikoro=T)

#save実行時(GIF動画作成)には、事前に、ImageMagickをインストール
#save機能は、Macのみ実行可能である
#SaikoroGame(save=T)
#SaikoroGame(Saikoro=T, save=T)

実行結果(2)

https://kumes.github.io/Blog/SaikoroGame/Animation_SaikoroGame05.gif

(GIF動画作成の場合)ImageMagickのインストール

ターミナルを起動して、brewコマンドで、ImageMagickをインストールする。

brew install imagemagick

過去の参考記事

skume.net

SaikoroGame 関数

#パッケージのインストール・ロード
if(!require("plotrix")){install.packages("plotrix")}
if(!require("EBImage")){install.packages("EBImage")}
if(!require("beepr")){install.packages("beepr")}

library(plotrix)
library(EBImage)
library(beepr)

#環境変数を消す
rm(list=ls())

#関数の作成
SaikoroGame <- function(N=6, save=F, Saikoro=F, Sound=F){
#変数の準備
a = c(1:10)

#オプション設定
par(mfrow = c(1,1), family="HiraKakuProN-W3",
    cex=1, mgp=c(2.5, 1, 0), mai=c(0.5, 0.5, 0.5, 0.5), xpd=F)

#空枠の作成
plot(a, a, type="n", axes=F, xlab=NA, ylab=NA, xlim=c(0,11), ylim=c(0,11),
     main=paste0("「サイコロ ", N, " が出たらダメよ」ゲーム"), xaxs="i", yaxs="i")

#区切り線の作成
abline(h=a); abline(h=c(0, 11))
abline(v=a); abline(v=c(0, 11))

if(Saikoro){
ff <- as.raster(EBImage::readImage("https://kumes.github.io/Blog/SaikoroGame/Dice.png", type = "png"))
par(xpd=T)
rasterImage(ff,
            xleft=-0.5, xright=1, 
            ybottom=10, ytop=12)  
par(xpd=F)
}

#試行回数、回数を記載
text(a+0.5, 10.5, labels=paste0(a, "回目"), cex=0.75)
text(0.5, a-0.5, labels=rev(paste0("試行 ", a)), cex=0.75)

#試行回数
n <- 0

#ゲーム実行
repeat{
n <- n + 1
x <- 0
Fin <- FALSE

repeat{
if(save){
  DPI <- ifelse(Saikoro, 100, 300)
  quartz.save(file = paste0("./SaikoroGame_", formatC(n, width=2, flag=0), "_", 
                                       formatC(x, width=2, flag=0), ".png"), 
                         type = "png", dpi = DPI)}
Sys.sleep(1)
#回数
x <- x + 1

#1〜6までの一様分布
b <- as.integer( runif(1, min = 1, max = 7) )  

if(b != N){
  text(x + 0.5, 10.5 - n, labels = b )
  Sys.sleep(0.5); if(Sound){beepr::beep(2)}
  if(save){
    DPI <- ifelse(Saikoro, 100, 300)
    quartz.save(file = paste0("./SaikoroGame_", formatC(n, width=2, flag=0), "_", 
                                       formatC(x, width=2, flag=0), ".png"), 
                         type = "png", dpi = DPI)}
}else{
  text(x + 0.5, 10.5 - n, labels = b )
  if(save){
    DPI <- ifelse(Saikoro, 100, 300)
    quartz.save(file = paste0("./SaikoroGame_", formatC(n, width=2, flag=0), "_", 
                                       formatC(x, width=2, flag=0), "_a.png"), 
                         type = "png", dpi = DPI)}
  Sys.sleep(0.5)
  text(x + 0.5, 10.5 - n, labels = "X", col="red", cex=1.25 )
  if(Sound){beepr::beep(9)}
  if(save){
    DPI <- ifelse(Saikoro, 100, 300)
    quartz.save(file = paste0("./SaikoroGame_", formatC(n, width=2, flag=0), "_", 
                                       formatC(x, width=2, flag=0), "_b.png"), 
                         type = "png", dpi = DPI)}
  break
}
if(x == 10){
  if(Saikoro){
  v <- as.character(as.integer( runif(1, min = 1, max = 11)))
  switch (v,
    "1" = eval(parse(text = paste0('url <- "https://upload.wikimedia.org/wikipedia/commons/c/c4/Suwa-ko_firework_20080815_02.jpg"; type <- "jpg"'))),
    "2" = eval(parse(text = paste0('url <- "https://upload.wikimedia.org/wikipedia/commons/3/3e/MtFuji_FujiCity.jpg"; type <- "jpg"'))),
    "3" = eval(parse(text = paste0('url <- "https://upload.wikimedia.org/wikipedia/commons/5/52/Supermario_Kungsbacka.jpg"; type <- "jpg"'))),
    "4" = eval(parse(text = paste0('url <- "https://upload.wikimedia.org/wikipedia/commons/f/f5/Korean.food-Bibimbap-02.jpg"; type <- "jpg"'))),
    "5" = eval(parse(text = paste0('url <- "https://upload.wikimedia.org/wikipedia/commons/7/75/Ayiin_2020-01-03.jpg"; type <- "jpg"'))),
    "6" = eval(parse(text = paste0('url <- "https://upload.wikimedia.org/wikipedia/commons/2/2b/Lysozyme.png"; type <- "png"'))),
    "7" = eval(parse(text = paste0('url <- "https://upload.wikimedia.org/wikipedia/commons/a/ae/Aomori_Bay_Asamushi_Onsen_Japan02bs5.jpg"; type <- "jpg"'))),
    "8" = eval(parse(text = paste0('url <- "https://upload.wikimedia.org/wikipedia/commons/6/68/Staphylococcus_aureus%2C_50%2C000x%2C_USDA%2C_ARS%2C_EMU.jpg"; type <- "jpg"'))),
    "9" = eval(parse(text = paste0('url <- "https://upload.wikimedia.org/wikipedia/commons/5/57/Red_Fuji_southern_wind_clear_morning.jpg"; type <- "jpg"'))),
    "10" = eval(parse(text = paste0('url <- "https://upload.wikimedia.org/wikipedia/commons/9/97/The_Earth_seen_from_Apollo_17.jpg"; type <- "jpg"')))
  )
  
  ff <- as.raster(EBImage::readImage(url, type = type))  
  rasterImage(ff, 
            xleft=0, xright=11, 
            ybottom=0, ytop=11)
  if(save){
    DPI <- ifelse(Saikoro, 100, 300)
    quartz.save(file = paste0("./SaikoroGame_", formatC(n, width=2, flag=0), "_", 
                                       formatC(x+1, width=2, flag=0), "_a.png"), 
                         type = "png", dpi = DPI)}
  }
  Sys.sleep(1)
  plotrix::boxed.labels(5.5, 5,  "クリアー!!!", col="red",
                      cex=3, bg = "white", xpad = 1.5, ypad = 1.5)
  if(Sound){beepr::beep(8)}
  
  if(save){
    DPI <- ifelse(Saikoro, 100, 300)
    quartz.save(file = paste0("./SaikoroGame_", formatC(n, width=2, flag=0), "_", 
                                       formatC(x+1, width=2, flag=0), "_b.png"), 
                         type = "png", dpi = DPI)}
  options(show.error.messages = F)
  Fin <- TRUE
  break
}}
if(Fin){break}
if(n == 10){
  if(Saikoro){
  url <- "https://upload.wikimedia.org/wikipedia/commons/a/ad/Seikima-II_20100704_Japan_Expo_60.jpg"
  type <- "jpg"
  ff <- as.raster(EBImage::readImage(url, type = type))  
  rasterImage(ff, 
            xleft=0, xright=11, 
            ybottom=0, ytop=11)}
  if(save){
    DPI <- ifelse(Saikoro, 100, 300)
    quartz.save(file = paste0("./SaikoroGame_", formatC(n, width=2, flag=0), "_", 
                                       formatC(x+1, width=2, flag=0), ".png"), 
                         type = "png", dpi = DPI)}
  Sys.sleep(0.5)
  plotrix::boxed.labels(5.5, 2.5,  "クリアーならず!!", col="blue",
                      cex=3, bg = "white", xpad = 1.25, ypad = 1.5)
  Sys.sleep(0.5)
  if(Sound){beepr::beep(9); beepr::beep(9); beepr::beep(9)}
  if(save){
    DPI <- ifelse(Saikoro, 100, 300)
    quartz.save(file = paste0("./SaikoroGame_", formatC(n, width=2, flag=0), "_", 
                                       formatC(x+2, width=2, flag=0), ".png"), 
                         type = "png", dpi = DPI)}
  break
}}
if(save){
message(paste0("Current directory: ", getwd()))
len <- length(list.files(pattern="Animation_SaikoroGame"))
message(paste0("Save start !!: ", formatC(len+1, width=2, flag=0)))
Sys.sleep(0.2)
system(paste0("convert -delay 100 -loop 10 ./SaikoroGame_*.png ./Animation_SaikoroGame", formatC(len+1, width=2, flag=0), ".gif"))
Sys.sleep(0.2)
system("rm -rf ./SaikoroGame_*.png")
message("Save finished !!")
}else{message("Finished !!")}
}

補足

6以外が10回出続ける確率

ちなみに、6以外が10回出続ける確率は、

(5/6)^10

 0.1615056

アガリの確率は、結構高い。

list.files実行について

なぜだか、ファイル数の長さである、変数 len にファイル数と一致する数値が入らなかった。

今回は、その前に、何かしらの実行(ex. message(paste0("Current directory: ", getwd())))を入れてあげるとうまく解決した。

参考資料

takenaka-akio.org

【Rでの文字列処理シリーズ(その3)】文字列/テキストの検出・検索: 完全一致、部分一致、拡張正規表現、曖昧一致判定

はじめに

文字列処理・テキスト処理とは、プログラミングを行うなかで、文字列・テキストに対する色々な操作のことを指します。それら処理をうまく使いこなすことで、文字列を自由に処理できるようになります。文字列処理の活用事例は、キーワード抽出、テキスト分類、テキストマイニングの前処理など、多岐に渡ります。 今回の「Rでの文字列処理」シリーズで扱う、文字列処理のライブラリ・関数群やプログラムコードは、R環境上で無料で提供されている、オープン・ソフトウェアを用います。

この記事では、R言語のプログラミングでのテキストマイニング、文字列の検出・検索、テキストの検出・検索について、いろいろと試してみました。 主に、base、stringrのパッケージを扱っています。

テキスト処理の関連記事

skume.net

skume.net

skume.net

skume.net

skume.net

skume.net

下準備について

まずは、関連パッケージとテキストデータを準備します。

########################
#シリーズ共通
########################
#必要なパッケージの読み込み
require(readr)
require(stringr)
require(stringdist)

#テストファイルのダウンロード
utils::download.file(url="https://raw.githubusercontent.com/kumeS/Blog/master/TXT_proc/test.txt",
                     destfile="test.txt")

【1】完全一致で、その文字列を含むかどうかのの判定

比較演算子 == !=

完全一致の基本は、比較演算子 ==!= を用いて、文字列の一致有無を判定する方法です。

#入力データフレーム
c <- data.frame(readr::read_tsv(file="./test.txt", col_names=F))
c

#完全一致で、文字列を含むかどうかの判定
#比較演算子を使う場合、その文字列(右辺)を含むかの判定
c == "abc"
#        X1
#[1,] FALSE
#[2,] FALSE
#[3,] FALSE
#[4,] FALSE
#[5,] FALSE
#[6,] FALSE

c == "abc ABC abc"
#        X1
#[1,]  TRUE
#[2,] FALSE
#[3,] FALSE
#[4,] FALSE
#[5,] FALSE
#[6,] FALSE

#その文字列(右辺)を含まないか?
c != "abc ABC abc"
#        X1
#[1,] FALSE
#[2,]  TRUE
#[3,]  TRUE
#[4,]  TRUE
#[5,]  TRUE
#[6,]  TRUE

base::match、stringr::str_match、演算子 %in%

次に、base::match、stringr::str_match、演算子 %in% を使って、完全一致の判定を行います。

#base::matchを使った場合
base::match(c$X1, table="abc")
#[1] NA NA NA NA NA NA

base::match(c$X1, table="abc ABC abc")
#[1]  1 NA NA NA NA NA

#stringr::str_matchを使った場合
stringr::str_match(c$X1, pattern="abc ABC abc")

# %in% を使った場合
c$X1 %in% "abc"
#[1] FALSE FALSE FALSE FALSE FALSE FALSE

c$X1 %in% "abc ABC abc"
#[1]  TRUE FALSE FALSE FALSE FALSE FALSE

【2】部分一致で、その文字列を含むかどうかの判定

charmatch関数、pmatch関数

まずは、charmatch関数、pmatch関数を使った、部分文字列の一致判定の実行例を示します。どうも文字列の先頭との部分一致を判定しているみたいです。

実行の挙動は、直観的に使いにくいかもしれません・・・

#基本的な実行例(1)
charmatch("m",   c("mean", "median", "mode"))
#[1] 0
# => 複数ヒットの場合、「0」を返す

base::charmatch("mo", table=c("mean", "median", "mode"))
#[1] 3
## => modeの「3」を返ってくる

base::charmatch("ode", table=c("mean", "median", "mode"))
#[1] NA
# => 先頭の文字が含まれないと、「NA」を返す

#基本的な実行例(2)
base::pmatch("m",   c("mean", "median", "mode"))
#[1] NA
base::pmatch("mo",   c("mean", "median", "mode"))
#[1] 3

#デフォルトは、1対1で対応づける
base::pmatch(c("", "ab", "ab"), c("abc", "ab"))
#[1] NA  2  1
#「duplicates.ok = T」で、1対多での対応づけをOKにする
base::pmatch(c("", "ab", "ab"), c("abc", "ab"), duplicates.ok = T)
#[1] NA  2  2

grep関数、grepl関数、stringr::str_detect関数

次に、grep関数、grepl関数、stringr::str_detect関数を使った事例を紹介する。こっちの方が使い勝手が良いと思われます。

c$X1
#[1] "abc ABC abc" "ABC abc ABC" "def DEF def"
#[4] "DEF def DEF" "abc.ABC.abc" "ABC.abc.ABC"

base::grep(pattern="abc", c$X1)
#[1] 1 2 5 6

base::grepl(pattern="abc", c$X1)
#[1]  TRUE  TRUE FALSE FALSE  TRUE  TRUE

c$X1[base::grepl(pattern="abc", c$X1)]
#[1] "abc ABC abc" "ABC abc ABC" "abc.ABC.abc"
#[4] "ABC.abc.ABC"

stringr::str_detect(c$X1, pattern="abc")
#[1]  TRUE  TRUE FALSE FALSE  TRUE  TRUE

#上記の逆論理値を返す
stringr::str_detect(c$X1, pattern="abc", negate = T)
#[1] FALSE FALSE  TRUE  TRUE FALSE FALSE

【3】文字列部分一致の曖昧判定

base::grepl関数

拡張正規表現を使った、文字列部分一致の曖昧判定について、いろいろな事例を扱ってみる。一通り覚えると、結構使えます。

まずは、base::grepl / stringr::str_detect + 拡張正規表現 を使います。

#「A〜C」の3文字からなる文字列を含むかどうか
base::grepl(pattern="[A-C][A-C][A-C]", c$X1)
#[1]  TRUE  TRUE FALSE FALSE  TRUE  TRUE

#「A〜C」、任意文字、「A〜C」の3文字からなる文字列を含むかどうか
base::grepl(pattern="[A-C].[A-C]", c$X1)
#[1]  TRUE  TRUE FALSE FALSE  TRUE  TRUE

#任意文字、任意文字、任意文字の3文字からなる文字列を含むかどうか
base::grepl(pattern="...", c$X1)
#[1] TRUE TRUE TRUE TRUE TRUE TRUE
(メモ) 正規表現の違い
任意文字 .
.(ピリオド)を文字指定 [.]

続いて、新たな文字列サンプルで試してみます。

#新たな文字列
test0 <- c("123", "12345", "Q1", "Q12", "QQ123")
test0
#[1] "123"   "12345" "Q1"    "Q12"   "QQ123"

#「0〜9」からなる文字列を3連続で含むかどうか
base::grepl(pattern="[0-9][0-9][0-9]", test0)
#[1]  TRUE  TRUE FALSE FALSE  TRUE

#先頭「Q」から始まって、数字2文字からなる文字列を含むかどうか
base::grepl(pattern="^Q[1-9][0-9]", test0)
#[1] FALSE FALSE FALSE  TRUE FALSE

#語尾が「0-9」「3-5」で終わる、数字2文字からなる文字列を含むかどうか
base::grepl(pattern="[0-9][3-5]$", test0)
#[1]  TRUE  TRUE FALSE FALSE  TRUE
(メモ) よく使う正規表現
先頭文字指定 先頭に ^ マーク
末尾文字指定 最後に $ マーク

次に、少し複雑な内容をやってみます。

TRUE / FALSEだと長ったらしいので、該当文字列のみを抽出しています。

#少し応用編
test1 <- c("あ", "ア", "M", "m", "1", " ", "  ", "㍍", "〒", "*", "+", "漢")

#平仮名 [あ-ん] を抽出する
test1[base::grepl(pattern="[あ-ん]", test1)]
#[1] "あ"

#片仮名 [ア-ン] を抽出する
test1[base::grepl(pattern="[ア-ン]", test1)]
#[1] "ア"

#大文字・小文字アルファベットを抽出する
test1[base::grepl(pattern="[a-z]|[A-Z]", test1)]
#[1] "M" "m"
#OR
test1[base::grepl(pattern="[[:alpha:]]", test1)]
#[1] "M" "m"

#小文字アルファベットを抽出する
test1[base::grepl(pattern="[a-z]", test1)]
#[1] "m"
#OR
test1[base::grepl(pattern="[[:lower:]]", test1)]
#[1] "m"

#数値を抽出する
test1[base::grepl(pattern="[0-9]", test1)]
#[1] "1"
#OR
test1[base::grepl(pattern="[[:digit:]]", test1)]
#[1] "1"

#アルファベット OR 数値を抽出する
test1[base::grepl(pattern="[a-z]|[A-Z]|[0-9]", test1)]
#[1] "M" "m" "1"
#OR
test1[base::grepl(pattern="\\w", test1)]
#[1] "M" "m" "1"
test1[base::grepl(pattern="[[:alnum:]]", test1)]
#[1] "M" "m" "1"

#空白文字、タブをを抽出する: やや分かりづらい
test1[base::grepl(pattern=" |  ", test1)]
#[1] " "  "  "
#OR
test1[base::grepl(pattern="[[:blank:]]", test1)]
#[1] " "  "  "

#印字可能な文字列を抽出する
test1[base::grepl(pattern="[[:print:]]", test1)]
#[1] "あ" "ア" "M"  "m"  "1"  " "  "  " "㍍" "〒" "*"  "+"  "漢"

#グラフィカル文字列を抽出する
test1[base::grepl(pattern="[[:graph:]]", test1)]
#[1] "あ" "ア" "M"  "m"  "1"  "㍍" "〒" "*"  "+"  "漢"

#パンクチュエーション文字列を抽出する
test1[base::grepl(pattern="[[:punct:]]", test1)]
#[1] "㍍" "〒" "*"  "+" 

stringr::str_detect関数

次に、stringr::str_detect関数での実行例を示します。

#平仮名 [あ-ん] を抽出する
test1[stringr::str_detect(test1, pattern="\\p{Hiragana}")]
#[1] "あ"
#OR
test1[stringr::str_detect(test1, pattern="[あ-ん]")]
#[1] "あ"

#片仮名 [ア-ン] を抽出する
test1[stringr::str_detect(test1, pattern="\\p{Katakana}")]
#[1] "ア" "㍍"
#OR
test1[stringr::str_detect(test1, pattern="[ア-ン]")]
#[1] "ア"

#漢字 を抽出する
test1[stringr::str_detect(test1, pattern="\\p{Han}")]
#[1] "漢"

stringr::str_detect関数では、\\p{Katakana}で、が抽出できるのが素敵です。

まとめ

文字列の検出は、実際テキスト処理とかでよく使います。

個人的には、grepl関数でT/F結果で返すのが「テッパン」だと思います。

補足

データフレーム全体に対して、文字列の置換を行う場合のTips

data.frame関数、lapply関数、gsub関数を組み合わせて行う。

以下には、データフレームのDatオブジェクトの各列の%を``に置換する場合の実施例を示す。

Dat <- data.frame(lapply(Dat, 
                         function(x){gsub(pattern="%", replacement = "", x)}),
                  stringsAsFactors = FALSE)

TRUE/FALSEをカウントする

lengthをよく使ってたけど、sumでもT/Fのカウントができるみたい。

文字列サンプル
test0 <- c("123", "12345", "Q1", "Q12", "QQ123")

#(例)「0〜9」からなる文字列を3連続で含むかどうか
count <- base::grepl(pattern="[0-9][0-9][0-9]", test0)
count
#[1]  TRUE  TRUE FALSE FALSE  TRUE

#lengthで、TRUEのカウント
length(count[count])
#[1] 3

#lengthで、FALSEのカウント
length(count[!count])
#[1] 2

#sumで、TRUEのカウント
sum(count)
#[1] 3

#sumで、FALSEのカウント
sum(!count)
#[1] 2

【応用編】文字列の検出: character(0)、numeric(0)を検出する

character(0)numeric(0)の処理は結構悩ましいので、メモしておく。

#前準備
a <- character(0)
b <- numeric(0)

#character(0)検出の方法
identical(a, character(0))
#[1] TRUE

#失敗例
character(0) == 0
#logical(0)

#numeric(0)検出の方法
identical(b, numeric(0))
#[1] TRUE

#失敗例
numeric(0) == 0
#logical(0)

#character(0)あるいはnumeric(0)の条件で検出する
Hantei <- function(x){
  ch <- identical(x, character(0))
  nu <- identical(x, numeric(0))
  return(any(c(ch, nu)))
}

#判定結果
Hantei(a)
#[1] TRUE
Hantei(b)
#[1] TRUE
Hantei("Baka")
#[1] FALSE

参考資料

mikuhatsune.hatenadiary.com

www.karada-good.net

www.okadajp.org

R言語のRSeleniumを使って、ブラウザ(自動)操作とWebスクレイピングをやってみた件 〜Google検索でのトップヒットページ・ヒット件数・スクショの取得〜

はじめに

RでのWebスクレイピングのやり方の1つとして、rvestパッケージを使う方法がある。

詳細は、過去の記事を参照

skume.net

skume.net

ただ、このパッケージだと、Webスクレイピングがやや難解なケースがある。 実際、Google検索のヒット件数項目を取得するのを、小一時間ほどやったけど、rvestではうまくいかなかった。

この記事では、RSeleniumを使って、Webブラウザを介して、Webスクレイピングをやる方法を紹介する。RSeleniumは、Selenium*1 2.0 Remote WebDriver 用の R ラッパーである。使ってみたら、RSeleniumは、rvestがやや困難な部分を補完できそうである。また、htmlの扱い方は、rvestとの共通点もあり、rvestの経験も生かされる。

今回の内容は、基本的なRSeleniumの使い方が主で、「定期実行のプログラムを組んで、ブラウザを自動操作する」とかまでは含んでいない。

まずは、事前のセットアップを行う。

事前セットアップ

Webブラウザのダウンロード

FireFoxGoogle Chromeをダウンロード・インストールする。

Webブラウザ操作用ドライバーのインストール

ターミナルを起動して、以下を実行する。

#ドライバーのインストール
brew install geckodriver
brew install chromedriver
#実行時に許可が必要

Homebrewの詳細は、過去の記事を参照のこと。

skume.net

Javaのインストール

こういうのがでたら、javaのインストールも必要である。

ターミナルを起動して、以下を実行する。

#JAVA セットアップ
brew tap AdoptOpenJDK/openjdk
brew install --cask adoptopenjdk12
#インストールの確認
java -version

詳細は、過去の記事を参照のこと。

skume.net

selenium.jarのダウンロード・実行

RSeleniumの実行には、selenium-server-standalone が必要である。

R/RStudioを起動して、そのソフトウェアをダウンロードして、バックグラウンド実行する。

#ダウンロード実行: 2021年4月4日に、ダウンロード確認
download.file("https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar", 
              destfile = "selenium.jar")

#バックグラウンド実行
system("java -jar selenium.jar &")
#19:42:09.781 INFO [GridLauncherV3.parse] - Selenium server version: 3.141.59, revision: e82be7d358
#19:42:09.872 INFO [GridLauncherV3.lambda$buildLaunchers$3] - Launching a standalone Selenium Server on port 4444
#2021-04-04 19:42:09.977:INFO::main: Logging initialized @595ms to org.seleniumhq.jetty9.util.log.StdErrLog
#19:42:10.249 INFO [WebDriverServlet.<init>] - Initialising WebDriverServlet
#19:42:10.382 INFO [SeleniumServer.boot] - Selenium Server is up and running on port 4444

#selenium.jarのジョブ確認
system("ps -A | grep 'java'")
# 3921 ??         0:01.50 /usr/bin/java -jar selenium.jar
# 3925 ??         0:00.00 sh -c ps -A | grep 'java'
# 3927 ??         0:00.00 grep java

#OR
#system("ps -A | grep 'selenium'")
# 3921 ??         0:01.52 /usr/bin/java -jar selenium.jar
# 3928 ??         0:00.01 sh -c ps -A | grep 'selenium'
# 3930 ??         0:00.00 grep selenium
 
#selenium.jarの止め方 - 終了時に実行すること
#上記コマンドで、プロセスのPIDを見つけて、そのIDをkillする
system("kill 3921")
system("ps -A | grep 'java'")
# 3939 ??         0:00.01 sh -c ps -A | grep 'java'
# 3941 ??         0:00.00 grep java

ここでは、selenium.jarのジョブが動いていれば、問題ない。

RSeleniumの使い方 - ブラウザ操作の基本

次に、RSeleniumパッケージをインストールする。

ここでは、Google Chromeを使ったやり方を紹介する。FireFoxでの実行例は、補足部分に記載している。

RSeleniumのポートは、「4444」を使用する。

#RSeleniumのインストール・ロード
install.packages("RSelenium")
library(RSelenium)

#Google Chrome 起動の場合
rsChr <- RSelenium::remoteDriver(port = 4444L, browserName = "chrome")
#[1] "Connecting to remote server"
#19:44:57.403 INFO [ActiveSessionFactory.apply] - Capabilities are: {
#  "browserName": "chrome",
#  "javascriptEnabled": true,
#  "nativeEvents": true,
#  "version": ""
#}
#(中略)

この状態で、Webブラウザ操作ができる。

以下が、基本操作のコマンドである。

#基本操作
#Webブラウザを開く
rsChr$open()

#Googleページへ移動する
rsChr$navigate("https://www.google.com")

#Rページへ移動する
rsChr$navigate("https://www.r-project.org/")

#1つ前のページに戻る(ex. Rページ => Googleページ)
rsChr$goBack()

#1つ先のページに戻る(ex. Googleページ => Rページ)
rsChr$goForward()

#現在のページのURL取得
rsChr$getCurrentUrl()
#[[1]]
#[1] "https://www.r-project.org/"

#ページの更新
rsChr$refresh()

#Webブラウザウインドの最大化
rsChr$maxWindowSize()

#ブラウザを閉じる
rsChr$close()

Google検索とか情報取得とかをやってみる

まずは、基本の「キ」ということで、RSeleniumを使った、Google検索をやってみる。

簡単なGoogle検索だけなら、以下の4ステップできる。

#ブラウザを開く
rsChr$open()

#Googleページに移動する
rsChr$navigate("https://www.google.co.jp/")

##Google検索の実行
#検索ウインドを選択
WebSearch <- rsChr$findElement(using = "css", "[name = 'q']")

#検索語「R Cran」を入力して、実行
WebSearch$sendKeysToElement(list("R Cran", key = "enter"))

このように、「R Cran」での検索結果が表示されたら、うまくいっている。

検索結果を取得する

次に、検索結果を取得する。重複とか関係ないところがあるので、以下のように不要部分を切って、出力させる。

#検索結果(全部)を取得する
ResultLink <- rsChr$findElements(using = "css", "[href]")
getResultLink <- unlist(lapply(ResultLink, function(x) {x$getElementAttribute("href")}))

#google関係を除外 + 重複除外
getResultLink <- unique(getResultLink[!grepl("[.]google|[.]gstatic[.]", getResultLink)])
getResultLink
# [1] "https://cran.r-project.org/"                                                                                        
# [2] "https://cran.r-project.org/bin/windows/"                                                                            
# [3] "https://cran.r-project.org/web/packages/available_packages_by_name.html"                                            
# [4] "https://cran.r-project.org/mirrors.html"                                                                            
# [5] "https://cran.r-project.org/web/packages/"                                                                           
# [6] "https://cran.r-project.org/bin/windows/base/"                                                                       
# [7] "https://cran.r-project.org/doc/contrib/manuals-jp/R-admin-jp.v15.pdf"                                               
# [8] "http://www.okadajp.org/RWiki/?CRAN%E5%9B%BD%E5%86%85%E3%83%9F%E3%83%A9%E3%83%BC%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9"
# [9] "https://cran.ism.ac.jp/"                                                                                            
#[10] "https://mjin.doshisha.ac.jp/iwanami/R/index.pdf"                                                                    
#[11] "https://staffblog.amelieff.jp/entry/2018/04/19/152233"                                                              
#[12] "https://qiita.com/hoxo_m/items/ce478bf0debe963d9e40"                                                                
#[13] "https://www.trifields.jp/statistical-analysis-r-cran-packages-341"                                                  
#[14] "https://en.wikipedia.org/wiki/Rnn_(software)" 

検索ヒット数を取得する

次に、検索ヒット数の結果である「約 23,300,000 件 ...」の項目を取得してみる。

#検索ヒット数を取得する
ResultStats <- rsChr$findElements(using = "id", "result-stats")

#テキストを取得
getResultStats <- unlist(lapply(ResultStats, function(x) {x$getElementText()}))
getResultStats
"約 23,300,000 件 (0.89 秒) "

#数値に変換(日本語の場合)
as.numeric(strsplit(gsub("[,]", "", sub("約 ", "", getResultStats)), split=" 件 ")[[1]][1])
#[1] 23300000

ヒットしたページに移動する

トップヒット、あるいはセカンド・ヒットのページに移動してみる。

今回、セカンド・ヒットは、トップヒットであるCRANのページ以外で最初に出てくるページを、セカンド・ヒットと定義したので、greplで少々整形してある。

#トップヒットのページに移動する
rsChr$navigate(getResultLink[1])

#セカンド・ヒットのページに移動する
rsChr$navigate(getResultLink[!grepl(getResultLink[1], getResultLink)][1])

ページ移動後に、ページのスクショを録る

#トップヒットのページに移動する
rsChr$navigate(getResultLink[1])

#スクショを録る => Rの作業ディレクトリに保存
rsChr$screenshot(file = 'test.png')

##同じ結果だが
#magick::image_writeで保存する場合
screenshot <- rsChr$screenshot(display = FALSE)
magick::image_write(screenshot, "test1.png")

#Rでスクショを表示させる
Img <- magick::image_read(base64enc::base64decode(toString(screenshot), output = NULL))
Img

CRAN HPのスクショが作業ディレクトリに保存されていたら、動作OKである。

まとめ

「動作が遅い」という評価が多かったものの、 思いの外、サクサク動いて、結構良かった。

実際、自動操作して、数万回とかアクセスするときには、 スリープ時間を乱数で入れるとかで、実行は遅くなるのかもと。

補足

Google 検索の有料APIについて

Google 検索の有料APIは、結構高い。 1万回検索するのに、50ドルかかるみたい。

RSeleniumを覚える方が経済的だよね。

RSelenium / FirefoxでのGoogle検索実行

#Firefox起動の場合
rsFox <- RSelenium::remoteDriver(port = 4444L, browserName = "firefox")

#ブラウザ起動
rsFox$open()

#Googleページに移動
rsFox$navigate("https://www.google.co.jp/")

#Google検索実行
WebSearch.Fox <- rsFox$findElement(using = "css", "[name = 'q']")
WebSearch.Fox$sendKeysToElement(list("R Cran", key = "enter"))

Rで、Python版Seleniumを実行してみる

#pipの確認
system("which pip")
#/usr/local/bin/pip
system("pip -V")
#pip 21.0.1 from /usr/local/lib/python3.8/site-packages/pip (python 3.8)

#seleniumのインストール
system("pip install selenium")
#Collecting selenium
#  Downloading selenium-3.141.0-py2.py3-none-any.whl (904 kB)
#Requirement already satisfied: urllib3 in /usr/local/lib/python3.8/site-packages (from selenium) (1.26.3)
#Installing collected packages: selenium
#Successfully installed selenium-3.141.0

#Pythonの選択
reticulate::use_python("/usr/local/bin/python", required =T)

#ライブラリのロード
selenium <- reticulate::import(module = "selenium")

#Firefoxの起動
Firefox <- selenium$webdriver$Firefox()

#Googleページに移動
Firefox$get('http://google.com/')

#検索窓を選択
element <- Firefox$find_element_by_name("q")

#検索を実行する
element$send_keys('R Cran'); element$submit()

#Firefoxを閉じる
Firefox$quit()

Webスクレイピングについての関連図書

Webスクレイピングの関連図書を列挙しておきます。

参考資料

stackoverflow.com

docs.ropensci.org

qiita.com

*1:Webアプリケーションをテストするためのポータブルフレームワークである。