간단한 애플리케이션을 만들어 봅니다. koscrap 패키지를 이용해서 네이버 뉴스 검색 애플리케이션을 만듭니다.
네이버 뉴스 검색 애플리케이션의 기능은 다음과 같이 정의합니다.
navbarPage(
"네이버 뉴스 검색 애플리케이션",
tabPanel(
"뉴스 검색하기",
sidebarLayout(
sidebarPanel(
textInput("client_id",
label = h4("Client ID:"),
value = client_id),
textInput("client_secret",
label = h4("Client Secret:"),
value = client_secret),
textInput("keyword",
label = h4("검색 키워드:"),
value = ""),
radioButtons("sort", label = h4("정렬 옵션:"),
choices = list("날짜순" = "date", "유사도순" = "sim"),
selected = "date"),
sliderInput("max_record", label = h4("검색 건수:"), min = 0,
max = 500, value = 100, step = 100),
actionButton("search_keyword", label = "뉴스 검색",
icon = icon("newspaper")),
width = 3
),
# Reactable에 검색 결과 리스트업
mainPanel(
reactableOutput("news_list"),
width = 9
)
)
),
tabPanel(
"워드 클라우드",
sidebarLayout(
sidebarPanel(
numericInput("remove_n",
label = h4("상위 최대 돗수 제외 건:"),
value = 5, min = 0
),
numericInput("min_freq",
label = h4("포함할 워드의 최소 돗수:"),
value = 5, min = 1
),
colourInput("col_bg",
label = h4("배경 색상:"),
value = "white"),
width = 3
),
# 검색 결과 전체의 워드클라우드
mainPanel(
wordcloud2Output("cloud", height = "600px"),
width = 9
)
)
)
)
function(input, output, session) {
newsList <- reactiveValues(
list = data.frame(
title = character(0),
description = character(0),
publish_date = character(0),
link = character(0),
stringsAsFactors = FALSE
) %>%
reactable(
defaultColDef = colDef(
align = "left"
),
columns = list(
title = colDef(
name = "타이틀",
width = 250,
),
description = colDef(name = "뉴스내용 요약"),
publish_date = colDef(
name = "뉴스 계시시간",
width = 150,
),
link = colDef(
name = "뉴스 링크",
width = 250,
html = TRUE,
cell = function(url) {
htmltools::tags$a(href = as.character(url), target = "_blank", as.character(url))
}
)
),
showPageSizeOptions = TRUE,
pageSizeOptions = c(5, 10, 15),
defaultPageSize = 5,
bordered = TRUE,
highlight = TRUE
)
)
output$news_list <- renderReactable({
newsList$list <-scraped_news() %>%
mutate(title = title_text) %>%
mutate(description = description_text) %>%
mutate(publish_date = stringr::str_remove_all(publish_date,
"[[:alpha:]]")) %>%
select(title, description, publish_date, link) %>%
reactable(
defaultColDef = colDef(
align = "left"
),
columns = list(
title = colDef(
name = "타이틀",
width = 250,
),
description = colDef(name = "뉴스내용 요약"),
publish_date = colDef(
name = "뉴스 계시시간",
width = 150,
),
link = colDef(
name = "뉴스 링크",
width = 250,
html = TRUE,
cell = function(url) {
htmltools::tags$a(href = as.character(url), target = "_blank", as.character(url))
}
)
),
showPageSizeOptions = TRUE,
pageSizeOptions = c(5, 10, 15),
defaultPageSize = 5,
bordered = TRUE,
highlight = TRUE
)
})
scraped_news <- eventReactive(input$search_keyword, {
# 3개의 텍스트는 반드시 입력해야 함
req(input$keyword)
req(input$client_id)
req(input$client_secret)
write_api_key(input$client_id, input$client_secret)
koscrap::search_naver(
query = input$keyword,
sort = input$sort,
chunk = min(input$max_record, 100),
max_record = input$max_record,
do_done = TRUE,
client_id = input$client_id,
client_secret = input$client_secret)
})
output$cloud <- renderWordcloud2({
data <- scraped_news()
create_wordcloud(
data,
remove_n = input$remove_n,
min_freq = input$min_freq,
background = input$col_bg
)
})
}
# attach packages
library("shiny")
library("dplyr")
library("koscrap")
library("reactable")
library("htmltools")
library("tidytext")
library("wordcloud2")
library("colourpicker")
# Initialize global environments
write_api_key <- function(client_id = NULL, client_secret = NULL) {
if (is.null(client_id)) {
return()
} else {
client_info <- glue::glue("{client_id}:{client_secret}") %>%
charToRaw() %>%
base64enc::base64encode()
write(client_info, file = ".api_key.info")
}
}
get_api_key <- function(client_info) {
client_info <- rawToChar(base64enc::base64decode(client_info)) %>%
strsplit(":") %>%
unlist()
client_id <- client_info[1]
client_secret <- client_info[2]
list(client_id = client_id, client_secret = client_secret)
}
options(api.key.file = TRUE)
client_id <- ""
client_secret <- ""
if (getOption("api.key.file")) {
if (grepl("scrap_app", getwd()) && file.exists(".api_key.info")) {
client_info <- scan(file = ".api_key.info", what = "character")
client_info <- get_api_key(client_info)
client_id <- client_info$client_id
client_secret <- client_info$client_secret
} else {
options(api.key.file = FALSE)
}
}
# create UDF
create_wordcloud <- function(data, remove_n = 5, min_freq = 5, background = "white") {
data %>%
filter(nchar(description_text) > 0) %>%
unnest_tokens(noun, description_text, bitTA::morpho_mecab, type = "noun") %>%
group_by(noun) %>%
count() %>%
arrange(desc(n)) %>%
ungroup() %>%
filter(n >= min_freq) %>%
filter(row_number() > remove_n) %>%
wordcloud2::wordcloud2(backgroundColor = background,
fontFamily = "NamumSquare")
}
# Run the application
shinyApp()
애플리케이션을 실행하면 다음과 같의 화면을 얻을 수 있습니다.
네번째 컬럼인 뉴스링크는 뉴스 원문의 URL 정보입니다. 이 URL을 클릭하면, 뉴스의 원문을 읽을 수 있습니다. 다음은 특정 페이지의 뉴스를 링크를 클릭하여 열린 웹 페이지입니다. 네이버 뉴스 검색 애플리케이션에서 불평등이라는 키워드로 검색하였기 때문에, 불평등이란 단어를 검색하였더니 1개 단어가 매칭되었습니다.
수집한 전체 뉴스 데이터 중에서 뉴스 요약을 집계합니다. 워드 클라우드를 그려 어떤 단어들이 발화되는지 살펴봅니다.
다음은 워드클라우드 탭을 클릭하여 열린 웹 페이지입니다. 불평등이라는 키워드로 검색된 기사들에서 어떤 단어들이 발화되는지 살펴봅니다.
For attribution, please cite this work as
유충현 (2022, July 9). Tidyverse Meets Shiny: 네이버 뉴스 검색 애플리케이션. Retrieved from https://choonghyunryu.github.io/tidyverse-meets-shiny/news_app
BibTeX citation
@misc{유충현2022네이버, author = {유충현, }, title = {Tidyverse Meets Shiny: 네이버 뉴스 검색 애플리케이션}, url = {https://choonghyunryu.github.io/tidyverse-meets-shiny/news_app}, year = {2022} }