You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现R Shiny按钮跳转至新页面展示yfinance季度财务表?

解决方案:在R Shiny中实现多页面展示财务数据

核心修改方向

  • 用shiny.router实现应用内页面跳转,满足"新页面展示数据"的需求
  • 重构数据获取逻辑,用响应式变量存储财务数据,替代原代码的全局环境赋值方式
  • 修复原代码中的SQL注入风险、变量定义顺序问题

完整可运行代码

先确保安装所需依赖包:

install.packages(c("shiny", "DT", "RSQLite", "reticulate", "shiny.router", "shinyautocomplete"))

代码实现

library(shiny)
library(DT)
library(RSQLite)
library(reticulate)
library(shiny.router)
library(shinyautocomplete)

# 提前加载美股代码列表,避免UI加载时找不到变量
us_tickers <- stockSymbols()$Symbol

# 预初始化Python环境,避免重复导入库
py_run_string("
import yfinance as yf
import pandas as pd
pd.set_option('display.max_columns', None)
")

# ---------------------- 页面UI定义 ----------------------
# 主页面(Ticker管理)
main_page <- fluidPage(
  titlePanel(h1("Securities Analytical Intelligence", align = "center")),
  sidebarLayout(
    sidebarPanel(
      autocomplete_input("new_ticker", "Enter Ticker", us_tickers, placeholder = "Type to search..."),
      actionButton("add_ticker", "Add Ticker"),
      actionButton("create_datablock", "Create Datablock")
    ),
    mainPanel(
      DTOutput("ticker_table")
    )
  )
)

# 数据展示页面
data_page <- fluidPage(
  titlePanel(h1("Quarterly Financial Data", align = "center")),
  uiOutput("financial_tables")
)

# 配置路由规则
router <- make_router(
  route("/", main_page),
  route("/data", data_page)
)

# 应用总UI
ui <- fluidPage(
  router$ui
)

# ---------------------- 服务器逻辑 ----------------------
server <- function(input, output, session) {
  # 初始化路由
  router$server(input, output, session)
  
  # 数据库连接(应用关闭时自动断开)
  con <- dbConnect(RSQLite::SQLite(), "tickers.db")
  onStop(function() {
    dbDisconnect(con)
  })
  
  # 创建tickers表(添加UNIQUE约束避免重复插入)
  dbExecute(con, "CREATE TABLE IF NOT EXISTS tickers (ticker TEXT UNIQUE)")
  
  # 响应式变量存储应用数据
  app_data <- reactiveValues(
    tickers = data.frame(ticker = character()),
    financial_data = list()
  )
  
  # 从数据库加载ticker列表
  load_tickers <- function() {
    app_data$tickers <- dbGetQuery(con, "SELECT * FROM tickers")
  }
  
  # 初始加载数据
  load_tickers()
  
  # 添加Ticker(用参数化查询避免SQL注入)
  observeEvent(input$add_ticker, {
    new_ticker <- trimws(input$new_ticker)
    if (nchar(new_ticker) > 0) {
      tryCatch({
        dbExecute(con, "INSERT INTO tickers (ticker) VALUES (?)", params = list(new_ticker))
        load_tickers()
        updateAutocompleteInput(session, "new_ticker", value = "") # 清空输入框
      }, error = function(e) {
        showNotification("该Ticker已存在或输入无效", type = "error")
      })
    }
  })
  
  # 删除Ticker
  observeEvent(input$ticker_table_cell_clicked, {
    info <- input$ticker_table_cell_clicked
    if (!is.null(info$value) && info$col == 1) {
      dbExecute(con, "DELETE FROM tickers WHERE ticker = ?", params = list(info$value))
      load_tickers()
    }
  })
  
  # 渲染Ticker表格
  output$ticker_table <- renderDT({
    datatable(
      app_data$tickers,
      options = list(dom = 't', paging = FALSE),
      colnames = c("Ticker"),
      selection = "none",
      rownames = FALSE
    )
  })
  
  # 获取财务数据(返回列表存储,避免全局赋值)
  get_financial_data <- function(tickers) {
    financial_list <- list()
    for (ticker in tickers) {
      py_run_string(paste0(
        "ticker_obj = yf.Ticker('", ticker, "')\n",
        "financial_data = ticker_obj.quarterly_financials"
      ))
      # 转成R数据框并添加Ticker标识
      r_financial <- py$financial_data
      r_financial$Ticker <- ticker
      financial_list[[ticker]] <- r_financial
    }
    return(financial_list)
  }
  
  # 点击Create Datablock后的逻辑
  observeEvent(input$create_datablock, {
    tickers <- app_data$tickers$ticker
    if (length(tickers) == 0) {
      showNotification("请先添加至少一个Ticker", type = "warning")
      return()
    }
    
    # 获取并存储财务数据
    app_data$financial_data <- get_financial_data(tickers)
    
    # 跳转到数据展示页面
    navigate_to("/data")
  })
  
  # 在数据页面生成所有财务表格的UI
  output$financial_tables <- renderUI({
    if (length(app_data$financial_data) == 0) {
      return(tags$p("暂无财务数据,请先在主页面创建Datablock"))
    }
    
    # 为每个Ticker生成独立表格
    table_outputs <- lapply(names(app_data$financial_data), function(ticker) {
      tagList(
        h3(paste("Ticker:", ticker)),
        DTOutput(paste0("table_", ticker))
      )
    })
    
    do.call(tagList, table_outputs)
  })
  
  # 渲染每个财务表格
  observe({
    lapply(names(app_data$financial_data), function(ticker) {
      output[[paste0("table_", ticker)]] <- renderDT({
        datatable(
          app_data$financial_data[[ticker]],
          options = list(scrollX = TRUE, dom = 'ltip'),
          rownames = TRUE
        )
      })
    })
  })
}

# 运行应用
shinyApp(ui, server)

关键改进说明

  1. 页面跳转:通过shiny.router实现两个页面的切换,点击按钮后用navigate_to("/data")直接跳转,符合"新页面展示"的需求。
  2. 数据管理:用reactiveValues$financial_data统一存储所有Ticker的财务数据,替代原代码的assign操作,确保Shiny能跟踪数据变化,正确渲染UI。
  3. SQL安全:将原代码的字符串拼接SQL改为参数化查询,彻底避免SQL注入风险。
  4. 效率优化:提前初始化Python环境并导入yfinance,避免每次循环重复导入库,提升运行效率。
  5. 用户体验:添加重复Ticker提示、空Ticker提示、清空输入框等细节优化。

内容的提问来源于stack exchange,提问作者Adin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 05:34:53