我如何解决 Streamlit 会话持久化(在三次失败尝试后)

发布: (2026年2月1日 GMT+8 08:16)
5 分钟阅读
原文: Dev.to

Source: Dev.to

问题

我在构建一个 Streamlit 应用,需要在页面刷新后记住用户会话。听起来很简单,对吧?只需像普通网页一样使用 localStorage 或 cookie 即可。

错了。

Streamlit Cloud 在一个沙箱 iframe 中运行你的应用。这意味着:

  • 标准的 localStorage 被限制在组件 iframe 中
  • Cookie 也面临相同的隔离问题
  • 直接操作 DOM 受到安全限制

结果呢?三天的挫败感和三次失败的尝试。

尝试 1:通过 streamlit_js_eval 使用 localStorage

我的第一想法是:“我只要用 JavaScript 访问 localStorage 就行了。”

from streamlit_js_eval import streamlit_js_eval

# 尝试存储会话
streamlit_js_eval(js_expressions="""
localStorage.setItem('session_token', 'abc123');
""")

# 尝试检索
token = streamlit_js_eval(js_expressions="""
localStorage.getItem('session_token');
""")

失败原因: JavaScript 在 Streamlit 的组件 iframe 中运行,该 iframe 拥有独立的存储空间。数据虽然被保存了,但它与应用的主上下文完全分离。页面重新加载后,这些数据就不存在了。

接下来:Cookie。它们肯定可以跨 iframe 工作,对吧?

from extra_streamlit_components import CookieManager
from datetime import datetime, timedelta

cookie_manager = CookieManager()

# Set cookie
cookie_manager.set('session_token', 'abc123', expires_at=datetime.now() + timedelta(days=1))

# Retrieve
token = cookie_manager.get('session_token')

为什么会失败: 同样的 iframe 隔离问题。Cookie 在组件的上下文中设置,而不是在父框架中。Streamlit Cloud 的沙箱机制导致该 Cookie 在你需要的地方不可访问。

Attempt 3: window.parent.localStorage

在走投无路的情况下,我尝试直接访问父窗口的 localStorage

streamlit_js_eval(js_expressions="""
window.parent.localStorage.setItem('session_token', 'abc123');
""")

为什么会失败: 浏览器安全模型。跨源 iframe 访问被设计上阻止。Streamlit Cloud 的 iframe 沙箱机制触发了这些保护,这是有充分理由的——如果允许这种行为,将会是安全噩梦。

解决方案:st.query_params

在对 iframe 墙壁“撞头”之后,我发现一直在那里的是:st.query_params

该功能在 Streamlit 1.30 中引入,允许你直接在 URL 中以查询参数的形式存储数据。它具有:

  • 在首次渲染时同步可用
  • 在页面刷新之间持久化
  • 在 Streamlit Cloud 的 iframe 环境中完美工作
  • 零外部依赖

它的使用方式如下:

import streamlit as st
import base64
import json

def save_session(session_data):
    # Encode session data as base64 to handle special characters
    json_str = json.dumps(session_data)
    encoded = base64.b64encode(json_str.encode()).decode()
    # Store in URL
    st.query_params['s'] = encoded

def load_session():
    # Retrieve from URL
    if 's' in st.query_params:
        try:
            encoded = st.query_params['s']
            json_str = base64.b64decode(encoded).decode()
            return json.loads(json_str)
        except Exception:
            return {}
    return {}

# Usage
if 'session' not in st.session_state:
    st.session_state.session = load_session()

# Save whenever session changes
if st.button('Save'):
    save_session(st.session_state.session)

为什么这样有效

  • 基于 URL 的存储:查询参数是 URL 的一部分,可在任何地方访问——没有 iframe 问题。
  • 内置于 Streamlit:无需外部组件或 JavaScript hack。
  • 同步访问:在页面加载时立即可用,组件渲染之前。
  • Base64 编码:在 URL 格式中安全处理复杂数据结构。

课程

有时候,最好的解决方案就是最简单的。我花了三天时间与外部库和 JavaScript 变通方法斗争,而 Streamlit 一直都有内置的解决方案。

在求助第三方库或巧妙的 hack 之前:

  • 检查是否有内置解决方案。
  • 阅读最近的更新日志——像 st.query_params 这样的特性很容易被忽视。
  • 了解你的部署环境(本例中的 iframe 沙箱)。

想了解更多?

阅读完整故事,包括所有调试步骤和代码示例,请参阅 Chronicle 001: Genesis

Back to Blog

相关文章

阅读更多 »