はじめに
RSA Conference の講演の座席予約は Full Agenda から行ないます。17コマに300以上のセッションが立ち並びますから、興味のあるものを厳選しなくてはいけません。ところがRSACのサイトはページの応答速度が芳しくなく、一覧性もよくないため、選定作業は難儀します。それで私は毎年ウェブスクレイピングしてExcelファイルに書き出し、それを見ながら選定していました。
今年はデザインに変更が入り、一覧ページから抽出できる情報が少なくなりました。たとえば概要(Abstract)を得るためには、個別のセッションのページにもアクセスする必要があります。さらに、これらのページはJavaScriptで動的に描画されるので、単純にHTTPリクエストを投げるだけでは適切なスクレイピングができません。
観念してSeleniumの使用を検討していたところ、rvest に read_html_live() という関数が生まれていることを知りました(2024年2月リリースのrvest 1.0.4から利用可能だった)。
既に「rvestで動的サイトをスクレイピングする(Seleniumを使わずに) 」という記事があります。この記事も同じ趣向で、RSACのページを題材にしてread_html_live()を使ったデータの取得を取り上げます。
講演一覧から個別ページへのリンクを抽出する
まずはFull Agendaから一覧ページを取得することを考えましょう。たとえば28日月曜のTrack Sessionを取得するなら、次のようなURLを組み立てます。
pacman:: p_load (tidyverse, httr2, rvest, chromote)
base_url <- "https://path.rsaconference.com/"
url <- url_parse (base_url)
url$ path <- "flow/rsac/us25/FullAgenda/page/catalog"
url$ query <- lst (
"tab.day" = "20250428" ,
"search.typeformat" = "16330260516930029l9w"
)
url_0428 <- url_build (url)
url_0428
[1] "https://path.rsaconference.com/flow/rsac/us25/FullAgenda/page/catalog?tab.day=20250428&search.typeformat=16330260516930029l9w"
次に、一覧ページへのリクエストを発生させて、個別ページへのリンクを抽出します。それには開発者ツールを使い、必要なクラス名などを把握しておかなくてはいけません。ここでは分量を節約するため、最初の3ページ分だけ出力することにします。
session <- read_html_live (url_0428)
Sys.sleep (5 )
# 「CLICK HERE TO VIEW MORE SESSIONS」ボタンをクリック
session$ click ("button.mdBtnR.mdBtnR-primary.show-more-btn" )
Sys.sleep (5 )
details_path <-
session$ html_elements (".catalog-result-title.session-title.rf-simple-flex-frame" ) |>
html_elements ("a" ) |>
html_attr ("href" ) |>
head (3 )
details_path
[1] "/flow/rsac/us25/FullAgenda/page/catalog/session/1727292939934001IAdE"
[2] "/flow/rsac/us25/FullAgenda/page/catalog/session/1727005184409001IAvU"
[3] "/flow/rsac/us25/FullAgenda/page/catalog/session/1725992567518001moQM"
個別ページをファイルに書き出す
書き出すディレクトリ名は「details_files」とし、存在しなければ作るようにします。 fs パッケージは、Tidyverseとともにインストールされています。
walk()やwalk2() は purrr パッケージに 含まれる関数で、map()と異なり戻り値がありません。ファイル操作のような副作用が発生するときに使用します。 walk2()の中で、個別ページの
タグを抽出し、文字列ベクトルから文字列に変換し、最後にファイルに書き出しています。単にhtmlオブジェクトを丸ごとテキストにするには迂遠なので、read_html_live()の機能拡充を期待したいところです。
フォーミュラ式(~{...})の代わりにR 4.1の無名関数(\(x) {...})を使ってみましたが、これはちょっと格好をつけただけでして、普段はフォーミュラ式を使っています。
dir_name <- "detail_files"
if (! fs:: dir_exists (dir_name)) {
fs:: dir_create (dir_name)
}
details_filepath <- str_c (dir_name, "/" , basename (details_path), ".htm" )
details_url <- str_c ("https://path.rsaconference.com" , details_path)
walk2 (details_url,
details_filepath,
\(url, filepath) {
session <- read_html_live (url)
Sys.sleep (5 )
session$ html_elements ("html" ) |>
map_chr (as.character) |>
str_c (collapse = " \n " ) |>
write_file (filepath)
}
)
個別ページから要素を抽出する
下に掲げるextract_event()は、上で書き出した個別ページから必要な要素を抽出し、読みやすいように加工したものです。これも開発者ツールを使ってクラスを見つけるところから始めなくてはいけません。
extract_event <- function (html_file_path){
pacman:: p_load (tidyverse, rvest)
html <- read_html (html_file_path)
date <-
html |>
html_element (".session-date" ) |>
html_text () |>
str_split_i ("," , 2 ) |>
str_c (" 2025" ) |>
str_trim () |>
mdy (locale = "C" )
time_start <-
html |>
html_element (".session-time" ) |>
html_text () |>
str_extract_all (" \\ d{1,2}: \\ d{2} [AP]M" ) |>
pluck (1 , 1 ) |>
parse_time ()
time_end <-
html |>
html_element (".session-time" ) |>
html_text () |>
str_extract_all (" \\ d{1,2}: \\ d{2} [AP]M" ) |>
pluck (1 , 2 ) |>
parse_time ()
topic <-
html |>
html_element (".attribute-TopicTrack" ) |>
html_text () |>
str_split_i (":" , 2 ) |>
str_trim () |>
str_replace ("Governance," , "Governance__" ) |>
str_replace (", " , " \n " ) |>
str_replace ("Governance__" , "Governance," )
level <-
html |>
html_element (".attribute-SessionClassification" ) |>
html_text () |>
str_split_i (":" , 2 ) |>
str_trim ()
id <-
html |>
html_element (".title-text" ) |>
html_text () |>
str_replace_all (c (" \\ [" = "___" , " \\ ]" = "___" )) |>
str_split_i ("___" , 2 )
title <-
html |>
html_element (".title-text" ) |>
html_text () |>
str_replace_all (c (" \\ [" = "___" , " \\ ]" = "___" )) |>
str_split_i ("___" , 1 ) |>
str_trim () |>
str_sub (1 , - 2 ) |>
str_trim ()
speaker <-
html |>
html_elements (".profile-container" ) |>
html_text () |>
str_flatten (" \n " )
abstract <-
html |>
html_element (".abstract-component" ) |>
html_text ()
basedir <- "https://path.rsaconference.com/flow/rsac/us25/FullAgenda/page/catalog/session/"
linkurl <-
html_file_path |>
fs:: path_file () |>
fs:: path_ext_remove () |>
(\(text) {str_c (basedir, text)})()
tribble (
~ date, ~ time_start, ~ time_end, ~ topic, ~ level, ~ id, ~ title, ~ speaker, ~ abstract, ~ linkurl,
date, time_start, time_end, topic, level, id, title, speaker, abstract, linkurl
)
}
個別ファイルをデータフレームに集約
以上で準備が整ったので、1つのデータフレームに載せます。このときに、先に定義した関数extract_event()を使っています。
本来はCSVファイルに出力して眺めますが、ここでは4列だけ抜き出したデータフレームを表示させるにとどめます。
html_files <- fs:: dir_ls (path = dir_name, glob = "*.htm" )
df_events <-
html_files |>
map_df (extract_event)
df_events |>
select (date, time_start, id, title)
# A tibble: 3 × 4
date time_start id title
<date> <time> <chr> <chr>
1 2025-04-28 08:30 TPV-M01 Cracks in the Fortress: How Major Companies Lea…
2 2025-04-28 08:30 HTA-M01 Beyond the Black Box: Revealing Adversarial Neu…
3 2025-04-28 08:30 DSA-M01 A Stuxnet Moment for Supply Chain Security?
# write_excel_csv(df_events, file = "rsac2025_tracks.csv")
おわりに
以上で見てきたように、read_html_live()を使うと動的ページのスクレイピングも割と気軽に実施できます。ただ、まだ日が浅い関数なので、不足を感じる機能もあります。たとえばwaitを指定するオプションは搭載してほしいところです。
read_html_live()は、chromote というChrome DevTools Protocol(CDP)を扱うパッケージをバックエンドとしています。CDPはSeleniumが使うWebDriverよりも低レベルなAPIで、直接ブラウザーを制御できるものです。
よって、read_html_live()に不足を感じたときには、chromoteパッケージを触りに行くことになるでしょう。