R Shiny每日从Google Drive更新图表:加载位置与优化问询
R Shiny 定期更新图表的实现疑问
初始工作流与疑问
我的工作流是:每日运行R脚本清理数据,生成Plotly图表(耗时数百毫秒,不宜在Shiny应用内生成,避免影响用户体验),将图表保存为.RData文件存储到Google Drive。需要让R Shiny应用每日从Google Drive获取该文件更新图表。
疑问:获取.RData文件的操作应该放在global文件还是server端?这个选择对用户加载速度有什么影响?
初始示意代码
library(shiny) library(plotly) library(googledrive) # Define UI ui <- fluidPage( titlePanel("Load Plotly Plot from Google Drive"), mainPanel( plotlyOutput("loaded_plot") ) ) # Define server logic server <- function(input, output, session) { MyPlot <- observe({ invalidateLater(86400000, session) #auth Google Drive drive_auth() #download plot from Google Drive drive_download("plot.RData") }) # Render the plotly plot output$loaded_plot <- renderPlotly({ MyPlot }) } # Run the application shinyApp(ui = ui, server = server)
更新后的工作流与优化疑问
因googledrive包的下载仅支持本地存储,我调整了工作流,已实现通过定期检查Google Sheet来更新的功能,示例中每30秒检查一次。目前将MyPlot的更新逻辑放在reactivePoll中,想咨询这个实现方式是否可以更优雅?
更新后的代码
# * Global library(shiny) library(plotly) library(googledrive) library(googlesheets4) # Function plotly plot plotly_scatter <- function(df, x, y) { fig <- plot_ly(df) fig <- fig %>% add_lines(type = 'scatter', mode = "lines", x = ~x, y = ~y) return(fig) } data_frame <- reactivePoll( intervalMillis = 30000, session = NULL, checkFunc = function() { #authentication drive_auth() gs4_auth(token = drive_token()) #Read in Google Sheet gs_file<- drive_get('df') gs_data <<- read_sheet(gs_file) #Create plotly plot MyPlot <<- plotly_scatter(gs_data, x, y) } ) # * UI ui <- fluidPage( titlePanel("Load Plotly Plot from Google Drive"), mainPanel( plotlyOutput("loaded_plot") ) ) # * Server server <- function(input, output, session) { # Render the plotly plot output$loaded_plot <- renderPlotly({ MyPlot }) } # Run the application shinyApp(ui = ui, server = server)
解答
关于初始疑问:global vs server端的选择
- 放在global文件:
- 优点:
global.R代码在应用启动时仅运行一次,所有用户共享下载的图表数据,不会重复触发Google Drive请求,节省资源,用户加载速度快。 - 缺点:若需每日更新,需结合定时任务(如
later包)实现全局定时刷新,或重启应用触发下载。
- 优点:
- 放在server端:
- 若每个用户会话独立执行下载:新用户加载时需等待下载完成,体验差,还会消耗额外的Google Drive请求配额。
- 若结合全局缓存(如
memoise包):可避免重复下载,同时支持灵活的定时更新,但需处理缓存过期逻辑。
对用户加载速度的影响:
- 放在global:首次启动下载一次,后续用户直接读取本地缓存,加载快;但更新逻辑相对固定。
- 放在server端(无缓存):每个新用户都要等待下载,加载慢;带全局缓存则和global方式体验接近,但实现更灵活。
关于更新后reactivePoll的优化建议
当前代码有几个可优化的点,让实现更优雅且稳定:
- 避免全局变量赋值(
<<-):全局变量在多用户环境下易出现竞态问题,建议让reactivePoll直接返回生成好的图表,而非赋值给全局变量。 - 分离检查与数据处理逻辑:
checkFunc仅做轻量的更新判断(如获取Google Sheet的最后修改时间),valueFunc负责实际读取数据和生成图表,减少不必要的计算。 - 提前完成认证:认证操作无需每次检查都执行,可放在
global.R中,应用启动时完成一次即可(建议使用服务账号认证,避免交互式操作)。 - 明确参数传递:绘图函数的
x、y参数需指定具体列名,避免依赖全局变量。
优化后的示例代码:
# Global.R library(shiny) library(plotly) library(googledrive) library(googlesheets4) # 提前完成服务账号认证(替换为你的密钥路径) drive_auth(path = "service_account_key.json") gs4_auth(token = drive_token()) # 定义绘图函数 plotly_scatter <- function(df, x_col, y_col) { plot_ly(df) %>% add_lines(type = 'scatter', mode = "lines", x = ~.data[[x_col]], y = ~.data[[y_col]]) } # 定义reactivePoll获取更新后的图表 get_updated_plot <- reactivePoll( intervalMillis = 30000, session = NULL, # 检查函数:仅获取文件修改时间判断是否更新 checkFunc = function() { gs_file <- drive_get("df") as.character(drive_reveal(gs_file, "modified_time")$modified_time) }, # 取值函数:仅当文件更新时执行,读取数据并生成图表 valueFunc = function() { gs_file <- drive_get("df") gs_data <- read_sheet(gs_file) plotly_scatter(gs_data, x_col = "x", y_col = "y") # 替换为实际列名 } ) # UI.R ui <- fluidPage( titlePanel("Load Plotly Plot from Google Drive"), mainPanel( plotlyOutput("loaded_plot") ) ) # Server.R server <- function(input, output, session) { output$loaded_plot <- renderPlotly({ get_updated_plot() }) } shinyApp(ui = ui, server = server)
优化点说明:
- 移除全局变量,
reactivePoll直接返回图表对象,避免多用户冲突。 checkFunc仅做轻量检查,减少不必要的网络请求和计算。- 认证操作提前完成,减少重复执行的开销。
- 参数传递更明确,避免潜在的作用域问题。
内容的提问来源于stack exchange,提问作者H. berg
相关产品推荐
相关产品推荐

