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

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

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アプリケーションをテストするためのポータブルフレームワークである。

gtrendsRの使い方 と Googleトレンドの情報取得・可視化をやってみた件

はじめに

今回紹介する、gtrendsRは、Google Trendsの情報を取得・表示するためのパッケージである。

そもそも、Google Trendsは、Googleが提供する無料ツールで、Googleの検索エンジンでの検索クエリの人気動向を分析できる。現在、Google検索のシェアが、世界中で約90%とほぼ独占状態であることからも、このワードトレンドの重要度が分かる。

つまりは、市場調査の観点からも、どの国・地域で、どのような行動、ワードが、どういう時系列でトレンドになっているかを素早く把握できるのは良い。

この記事では、gtrendsRでGoogle検索のトレンドデータを取得して、plotlyによるインタラクティブな可視化などをやってみる。検索の実行例は、アメリカ株ネタにやや偏っているが、あしからず。

gtrendsRなどの関連パッケージのインストール

さっそく、gtrendsRなどの関連パッケージをインストールしてみる。

#パッケージ・インストール
pack <- c("gtrendsR", "plotly", "magrittr")
install.packages(pack[!(pack %in% unique(rownames(installed.packages())))])

#ロード
for(n in 1:length(pack)){ eval(parse(text = paste0("library(", pack[n], ")"))) }; rm("n", "pack")

gtrendsの使い方

次に、gtrends関数の使い方をまとめてみた。 この関数では、keyword、geo、timeの引数設定をよく使うことになる。

#gtrendsの使い方
gtrends( keyword , geo , time = "today+5-y", gprop = "web" )

#引数について
#keyword: Google Trends query キーワードである文字ベクトル。複数のキーワード入力も可。
#geo: queryの地理的な地域を示す文字ベクトル。世界中の場合には、"all"を指定する。
#また上記の国コードなどを使用することで、複数の国・地域を指定できる。
#time: queryの期間を指定する文字列。以下を参照のこと。
#"now 1-H" 最後の1時間
#"now 4-H" 最後の4時間
#"now 1-d" 最後の1日
#"now 7-d" 過去7日間
#"today 1-m" 過去30日間
#"today 3-m" 過去90日
#"today 12-m" 過去12ヶ月
#"today+5-y" 過去5年間(デフォルト)
#"all" Google Trends の開始時(2004 年)から
#"Y-m-d Y-m-d" 2つの日付の間で指定(例:「2010-01-01 2010-04-03」)
#gprop: トレンドクエリを収集する Google product を定義する文字列。「web」(デフォ)のままで良いと思う。

代表的な国コード

また、gtrendsRで使う、国コード(country_code)・地域コード(sub_code)は、countriesというデータに格納されている。

代表的な国コードには、日本(JP)、アメリカ(US)、カナダ(CA)、イギリス(GB)などがある。 また、地域コードだと、東京(JP-13)、大阪(JP-27)、ニューヨーク(US-NY)、カリフォルニア(US-CA)などがある。

実際のcountriesからの取り出し方を、以下に記した。

#国コード(country_code)・地域コード(sub_code)の取得
data("countries")
head(countries)
#  country_code sub_code        name
#1           AF     <NA> AFGHANISTAN
#2           AF   AF-BDS  BADAKHSHAN
#3           AF   AF-BDG     BADGHIS
#4           AF   AF-BGL     BAGHLAN
#5           AF   AF-BAL       BALKH
#6           AF   AF-BAM      BAMIAN

#アメリカUSの州・都市
head(countries[grepl("^US-", countries$sub_code),])
#     country_code sub_code       name
#3796           US    US-AL    ALABAMA
#3797           US    US-AK     ALASKA
#3798           US    US-AZ    ARIZONA
#3799           US    US-AR   ARKANSAS
#3800           US    US-CA CALIFORNIA
#3801           US    US-CO   COLORADO

#日本JPの都道府県
head(countries[grepl("^JP-", countries$sub_code),])
#     country_code sub_code         name
#2038           JP    JP-23 AITI [AICHI]
#2039           JP    JP-05        AKITA
#2040           JP    JP-02       AOMORI
#2041           JP    JP-38        EHIME
#2042           JP    JP-21  GIHU [GIFU]
#2043           JP    JP-10        GUNMA

#その他の取得例
#アメリカのカリフォルニア
head(countries[grepl("US", countries$country_code) & grepl("CALIFORNIA", countries$name),])
#       country_code sub_code            name
#3800             US    US-CA      CALIFORNIA
#102327           US    US-MD      CALIFORNIA
#102328           US    US-MO      CALIFORNIA
#102329           US    US-PA      CALIFORNIA
#102330           US    US-CA CALIFORNIA CITY

#ニューヨーク
head(countries[grepl("New York", countries$name),])
#       country_code sub_code         name
#122761           US    US-NY New York, NY

#東京
head(countries[grepl("TOKYO", countries$name),])
#      country_code sub_code             name
#2077            JP    JP-13    TOKYO [TOKYO]
#79623           JP    JP-13    ARIAKE, TOKYO
#79697           JP    JP-13    CHUO-KU/TOKYO
#79806           JP    JP-13    FUTAMI, TOKYO
#79831           JP    JP-13      HABU, TOKYO
#79869           JP    JP-13 HANEDA APT/TOKYO

#大阪
head(countries[grepl("OSAKA", countries$name),])
#      country_code sub_code               name
#2069            JP    JP-27      OSAKA [OSAKA]
#79702           JP    JP-27       DAITO, OSAKA
#79914           JP    JP-27      HIGASHI-OSAKA
#79915           JP    JP-27  HIGASHIOSAKA CITY
#80114           JP    JP-27       IZUMI, OSAKA
#80358           JP    JP-27 KONOHANA-KU, OSAKA

gtrendsのシンプルな実行例

keywordやgeoについて条件を変えて、トレンド検索を実行してみた。

実行例(1)NASDAQのトレンドを国別で12ヶ月分取得して、折れ線プロットを描く

まずは、NASDAQ(ナスダック)のトレンドを、カナダ(CA)と米国(US)、日本(JP)で12ヶ月分取得して、gtrendsRの折れ線プロットをしてみた。

#実行例(1)
res01 <- gtrendsR::gtrends(keyword = c("NASDAQ"), geo = c("US", "CA", "JP"),
                           gprop="web", time = "today 12-m")
plot(res01)
#quartz.save(file = paste0("./Graph_01.png"), type = "png", dpi=150); dev.off()

実行例(2)NASDAQ銘柄のトレンドを、米国(US)で12ヶ月分取得して、折れ線プロットを描く

次に、NASDAQ、TSLA(テスラ)、PLTR(パランティア)、ZM(ズーム), ARKKのトレンドを、米国(US)で12ヶ月分取得して、gtrendsRの折れ線プロットをしてみた。

#実行例(2)
res02 <- gtrendsR::gtrends(keyword = c("NASDAQ", "TSLA", "PLTR", "ZM", "ARKK"), geo = c("US"),
                         gprop="web", time = "today 12-m")
plot(res02)
#quartz.save(file = paste0("./Graph_02.png"), type = "png", dpi=150); dev.off()

TSLAの人気はやはりすごい。

ただ、ここで、Googleトレンドの検索指数は、対象期間内のピークを100とした相対値である。

そのため、取得期間や検索語の組み合わせで、上図の通り、検索指数が変わってくるので、その点は注意する必要である。

あと、同時に検索できるキーワード数は、5つまでのようだ*1

実行例(3)market crashやmarket highとかのキーワード検索

続いて、market crashやmarket highとかのキーワードで検索してみると。。

#実行例(3)
res03 <- gtrendsR::gtrends(keyword = c("market crash", "stock crash", "stock high"), geo = c("US"),
                         gprop="web", time = "2021-01-01 2021-03-25")
plot(res03)
#quartz.save(file = paste0("./Graph_03.png"), type = "png", dpi=150); dev.off()

「stock high」は少し先に反応しているようにも見える。

また、「market high」でも検索してみた(省略)が、それはあまり使わないワードみたい。。。

実行例(4)最近のロビンフット(米国株の人気銘柄)のトレンド

さらに、最近のrobinhoodのトレンドはどうだったのかと思い、それも検索してみた。

#実行例(4)
res04 <- gtrendsR::gtrends(keyword = c("robinhood"), geo = c("US"),
                         gprop="web", time = "2021-01-01 2021-03-25")
plot(res04)
#quartz.save(file = paste0("./Graph_04.png"), type = "png", dpi=150); dev.off()

やっぱり、ゲームストップ事件のあたりで、一時期サチってたのかもね。

plotlyを使った可視化の実行

これくらい遊んだところで、plotlyを使った可視化も実行してみた。

#トレンドデータの取り出し
res04 %>%
  .$interest_over_time -> res04.i 
head(res04.i)
#        date hits   keyword geo                  time gprop category
#1 2021-01-01    2 robinhood  US 2021-01-01 2021-03-25   web        0
#2 2021-01-02    4 robinhood  US 2021-01-01 2021-03-25   web        0
#3 2021-01-03    4 robinhood  US 2021-01-01 2021-03-25   web        0
#4 2021-01-04    5 robinhood  US 2021-01-01 2021-03-25   web        0
#5 2021-01-05    5 robinhood  US 2021-01-01 2021-03-25   web        0
#6 2021-01-06    4 robinhood  US 2021-01-01 2021-03-25   web        0

#グラフ作成
fig <- plotly::plot_ly() 
fig <- fig %>% add_trace(x=res04.i$date, y = res04.i$hits, 
                         name = 'robinhood', mode = 'lines') 
fig <- fig %>% layout(yaxis = list( title = "Search index"))
fig

#保存
#fig %>% htmltools::save_html(file="Graph_05.html")

まとめ

簡単に、トレンド解析できるのは大変良い。

ただ、検索語をどれにするかとか、適切にキーワードを選択できるかは悩みどころかも。

補足

r-bloggersをもとに改変版コードを作ってみた

r-bloggersでの実行コードが為になったので、その記事をもとに改変版コードを作成して実行してみた。

#install.packages("gtrendsR")
library(gtrendsR)
#install.packages("tidyverse")
library(tidyverse)
#install.packages("ggthemes")
library(ggthemes)

gtrends(keyword = "robinhood",
        geo = "US",
        time = "today 12-m") -> results

results %>% summary()
#                    Length Class      Mode
#interest_over_time  7      data.frame list
#interest_by_country 0      -none-     NULL
#interest_by_region  5      data.frame list
#interest_by_dma     5      data.frame list
#interest_by_city    5      data.frame list
#related_topics      6      data.frame list
#related_queries     6      data.frame list

results %>% 
  .$interest_over_time %>%
  glimpse()
#Rows: 52
#Columns: 7
#$ date     <dttm> 2020-03-29, 2020-04-05, 2020-04-12, 2020-04-19, 2020-…
#$ hits     <int> 7, 7, 9, 8, 9, 8, 8, 8, 6, 9, 12, 13, 8, 7, 8, 8, 7, 7…
#$ keyword  <chr> "robinhood", "robinhood", "robinhood", "robinhood", "r…
#$ geo      <chr> "US", "US", "US", "US", "US", "US", "US", "US", "US", …
#$ time     <chr> "today 12-m", "today 12-m", "today 12-m", "today 12-m"…
#$ gprop    <chr> "web", "web", "web", "web", "web", "web", "web", "web"…
#$ category <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …

results %>%
  .$interest_over_time %>%
  ggplot(aes(x = date, y = hits)) +
  geom_line(colour = "darkblue", size = 1) +
  facet_wrap(~keyword) +
  ggthemes::theme_economist()
#quartz.save(file = paste0("./Graph_robinhood.png"), type = "png", dpi=150); dev.off()

やっぱ、2月は、robinhoodバブルだったのかもね。

参考資料

https://cran.r-project.org/web/packages/gtrendsR/gtrendsR.pdf

www.r-bloggers.com

*1:length(keyword) <= 5 is not TRUE

R/Slack APIの諸設定、slackrの使い方、及びGoogle scholarで検索された新着論文情報を知らせるTips

はじめに : R版Slack API

RのSlack APIであるslackrパッケージの使い方について、いろいろとまとめてみた*1

APIの諸設定、基本的なslackrの使い方に加えて、新着論文情報をRからチャネルに送信するプログラムも実装してみた。

まずは、Salck API設定の手順からはじめよう。

Salck API設定の手順

1. Slackのワークスペースに、任意のチャネルを作る。(必要なら)

2. Slack アプリを新規作成する

Rからは以下の関数実行で、Slack APIのサイトに飛べる。

browseURL("https://api.slack.com/apps/new")

ここで、任意のApp Nameを記入して、Development Slack Workspaceを選択する*2

3. Incomming WebhooksをActiveにする

はじめに、Incomming Webhooks の設定に進む。

デフォルトでは、その設定はOFFになっている。

その設定を、ONにする。

次に、Add New Webhook to Workspaceをクリックする

次の画面で、対象となるチャネルを選択して、許可する。

そうすると、Webhook URLが作成される。

ただ、この後のBot Token Scopesを設定して、reinstallすると、新たなWebhook URLが作成されるっぽいので、ひとまずはスルーしておく。

4. Bot User OAuth Tokenの取得、OAuth & Permissionsの設定

次に、サイドバーで、OAuth & Permissionsをクリックする。

ここで、上部に表示される、Bot User OAuth Token は後ほど使用するので、コピーして、保存しておく。

続いて、Bot Token Scopesを設定する。

ここには、incoming-webhookのみが追加されているので、「Add an OAuth Scope」でScopeを追加する。

まずは、以下の項目あたりを追加して、様子をみるのが良い。

  • channels:read

  • chat:write

  • chat:write.customize

  • chat:write.public

  • users:read

  • files:read

  • files:write

  • groups:read

  • groups:write

  • im:read

  • im:write

  • channels:history

上記が選択できたら、reinstallして、次の画面で、対象となるチャネルを選択して、許可する。

そうしたら、Incomming Webhooksの設定に戻って、新しく生成されたWebhook URLを取得しておく。

Sample curl requestのコードの最後部分が最新のWebhook URLなので、それをコピーしておく。

5. Slack アプリをチャネルに招待する【これが重要!】

どうも、以下の設定は必須みたい。

Known Issues

Depending on your scopes, slackr could quietly fail (i.e. not throw an error, but also not post anything to your channel). If this happens, try explicitly adding the app you’re trying to have slackr post as to the channel you want in your Slack workspace with /invite @your_app_name or make sure you have chat:write.public enabled.

日本語への機械翻訳: 既知の問題

スコープによっては、slackrが静かに失敗することがあります(エラーをスローせず、チャンネルに何もポストしないなど)。この問題が発生した場合は、Slackrに投稿させようとしているアプリをSlackのワークスペースに/invite @your_app_nameで明示的にチャンネルに追加するか、chat:write.publicを有効にしてください。

これは、chat:write.publicを有効にするだけでは解決しない。

やることは、対象チャネルで、「/invite @App Name」を入力して、作成したSlack アプリをチャネルに加える。

以下は、「@r-slack」を追加したときの例である。

送信すれば、「@r-slack」がチャネルに招待される。

6. RでのSlack APIの設定

ようやく、slackr側の設定を行う。

Rを起動して、まずは関連パッケージをインストール・ロードする。

#パッケージのインストール
library(devtools)
devtools::install_github("hrbrmstr/slackr", force = TRUE)
library(slackr)

7. slackr config ファイルの設定

ここでは、Rからホームディレクトリに、~/.slackrを作成する。

上記で、コピー・保存しておいたOAuth TokenやWebhook URLを使用する。

.slackrの基本フォーマットは以下の通りである。

#基本的なフォーマット
bot_user_oauth_token: Bot User OAuth Token
channel: #チャネル名
username: 上記のApp Name
incoming_webhook_url: https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX

以下、R上から~/.slackrの作成を行う。tokenやチャネル名は、適時変更のこと。

また、~/.slackrを設定さえすれば、次回使用時からはslackr_setup()だけで完結する。

#Token & チャネル名 & Username の設定
OAuth_token <- "xoxb-XXXXXXX-XXXXXXX-XXXXXXX"
WebURL <- "https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX"
Channel <- "チャネル名"
#チャネル名は、"Channel ID"でも可 
Username <- "slack-r"
#Usernameは任意でOK

a <- paste0("
bot_user_oauth_token: ", OAuth_token, "
channel: ", Channel, "
username: ", Username, "
incoming_webhook_url: ", WebURL)

#作成
system(paste0("echo \"", a, "\" > ~/.slackr"))

#確認
system("cat ~/.slackr")

#削除 (必要なら)
#system("rm -rf ~/.slackr")

#slackr_setupの実行
slackr_setup()
#[1] "Successfully connected to Slack"

あと、オプション的な内容である。

#authentication & identity のチェック
auth_test()

#Slack usersの表示
slackr_users()

#Slack channelsの表示
slackr_channels()

#設定状況の確認
Sys.getenv("SLACK_CHANNEL")
Sys.getenv("SLACK_BOT_USER_OAUTH_TOKEN")

8. 簡単な投稿実行

次に、実際に、Slackrパッケージを使って見ていこう。

#メッセージ送信
slackr_msg("Test message")

#メッセージをR表現の結果 「```形式」 として送信
slackr_bot('Test message')

上記の出力結果

#Rスクリプトの実行結果の送信
slackr(summary(UKgas))

#Plot結果を送信する
plot(UKgas)
slackr_dev(channels="#test")
#OR
ggslackr(plot(UKgas))

#画像のアップロード: 任意の画像で試してください。
slackr_upload("./01.png")

#チャネルを指定して送信する場合
library(maps)
map('county')
slackr_dev(channels = "#test")

#その他
#チャネルからメッセージを削除する
#slackr_delete(count=1)

上記の出力結果(一部)

R/Slackrで、Google scholarで検索された新着論文情報を知らせる

応用例として、Google scholarで検索された論文情報を新着順に10件取得して、Slackのチャネルに投げるということをやってみる。

Rを用いたWebスクレイピングの詳細については、以下の記事を参考のこと。

skume.net

また、googleScholarSearchNewest関数については、下記の補足資料を参照のこと。

#パッケージとかの準備
if(!require("rvest")){install.packages("rvest")}; library(rvest)
if(!require("xml2")){install.packages("xml2")}; library(xml2)
if(!require("magrittr")){install.packages("magrittr")}; library(magrittr)
source("https://gist.githubusercontent.com/kumeS/ef4d750a191b847174b3b93a4b8391c9/raw/9281e95919903fa66f2c43b71b9ae2340404d8a8/googleSearch_function.R")

#検索語の準備 ex. electron microscopy
Query <- c("electron microscopy")

#Google Scholar 検索実行
d <- googleScholarSearchNewest(Query=Query)

#結果ベクトルの取得
res <- d$`electron microscopy`

res
#[1] "https://www.nature.com/articles/s41467-021-21709-z"                          
# [2] "https://pubs.acs.org/doi/abs/10.1021/acsmacrolett.1c00032"                   
# [3] "https://academic.oup.com/cdn/advance-article/doi/10.1093/cdn/nzab025/6174670"
# [4] "https://pubs.acs.org/doi/full/10.1021/acsnano.0c10065"                       
# [5] "https://pubs.acs.org/doi/abs/10.1021/acsnano.0c10065"                        
# [6] "https://www.sciencedirect.com/science/article/abs/pii/S2213138821001284"     
# [7] "https://link.springer.com/article/10.1140/epjp/s13360-021-01291-5"           
# [8] "https://pubs.acs.org/doi/abs/10.1021/acs.energyfuels.1c00167"                
# [9] "https://journals.sagepub.com/doi/abs/10.1177/0095244321996396"               
#[10] "https://www.sciencedirect.com/science/article/pii/S0269749121005315"  

#Slackへの出力
for(n in 1:length(res)){
slackr_msg(paste0("Search results No. ", n))
slackr_msg(res[n])
}

Slack上に、こんな感じで出力される

まとめ

ファイルのアップロードとか、スクリプトの情報共有とかは楽なのかもしれない。

今のところ、メッセージ送信とかレスポンスとかを、Rからすべてやるのはちょっとやり過ぎかもという所感である。

補足資料

googleScholarSearchNewest関数について

gist.github.com

参考資料

qiita.com

github.com

*1:Web上の日本語資料の2021版アップデート

*2:Slackにログインしておくとよいかも