简介:本资源是一套基于Python Streamlit框架构建的交互式Web应用示例集合,面向数据科学初学者、Python开发者及需快速交付数据可视化原型的工程师。它有效降低前端门槛,让使用者无需掌握HTML/CSS/JS即可通过纯Python代码开发可部署的数据应用。压缩包共7个文件,含2个核心.py脚本(如color_palette.py、folder.py,体现UI逻辑与模块组织)、3张JPG示例图(sunset.jpg等,直观展示应用界面效果)、1份README.md说明文档和1个requirements.txt依赖清单,整体仅518KB,轻量易读。已有2715人学习下载,适合边学边练:可直接运行复现配色工具、文件浏览等典型场景,理解st.write()、st.selectbox()等基础组件用法,掌握Streamlit项目结构、本地调试及静态资源引用方式,是入门实践与教学参考的高性价比素材。
1. Streamlit 不是“写网站”,而是把 Python 脚本变成可交互数据应用:为什么你写的 demo 总被说“像玩具”,而别人上线就跑通业务流程?
很多人第一次看到streamlit run app.py就弹出一个带滑块、按钮、图表的网页时,会下意识觉得:“哦,这是个轻量 Web 框架,类似 Flask 简化版”。错。Streamlit 的本质不是 Web 开发工具,而是一套「Python 数据工作流的实时可视化胶水层」——它不让你写路由、不处理 session、不碰 HTML 模板,而是强制你用纯 Python 函数式逻辑组织状态,所有 UI 元素(st.slider、st.button、st.dataframe)既是控件,也是变量声明点。这导致一个反直觉事实:一个 300 行的 Streamlit 脚本,可能比 2000 行的 Flask+Vue 前后端分离项目更快接入真实数据源、更易被非 Web 工程师维护、更少因状态同步翻车。它适合的不是“做官网”或“搭商城”,而是:某高校实验室要让导师每天点两下就能看模型训练曲线;某公司风控组需要业务同事拖拽 CSV 就生成特征分布报告;某开发者想把本地调试好的 PyTorch 推理脚本,30 分钟内变成销售能自己上传图片试效果的内部工具。本文不讲“怎么装 Streamlit”,而是带你从零构建一个真正能离线运行、支持多页导航、可复用组件、带基础权限提示、且不依赖云托管的本地化应用集合——所有代码在 Windows/macOS/Linux 上开箱即用,连requirements.txt的每一行都经过实测验证。
2. 从单页脚本到结构化应用集合:用pages/目录 +st.navigation实现真正的多页管理
Streamlit 2.0+ 原生支持多页应用,但很多教程仍教人用st.radio或st.selectbox手动切换页面,这会导致状态丢失、URL 不可分享、SEO 友好性为零。正确路径是pages/目录约定 +st.navigationAPI,它让每个.py文件成为独立路由,且共享st.session_state。
2.1 创建符合官方规范的目录结构
Streamlit 要求多页应用必须满足以下物理结构:
my_streamlit_app/ ├── main.py # 入口文件,仅含 st.set_page_config 和 st.navigation ├── pages/ │ ├── 1_🏠_Home.py │ ├── 2_📊_Data_Explorer.py │ ├── 3_📈_Model_Dashboard.py │ └── 4_⚙️_Settings.py ├── assets/ │ ├── logo.png │ └── style.css └── requirements.txt提示:
pages/下的文件名必须以数字开头(控制导航顺序),支持中文和 emoji,但不能有空格或特殊符号(如&,#)。文件名中的_是分隔符,Streamlit 会自动将1_🏠_Home.py显示为 “🏠 Home”。
2.2main.py:最小入口,最大可控性
# main.py import streamlit as st # 全局配置:必须放在最顶部,且只执行一次 st.set_page_config( page_title="数据应用集合", page_icon="🚀", layout="wide", # "centered" 或 "wide" initial_sidebar_state="expanded", menu_items={ 'Get Help': 'https://docs.streamlit.io', 'Report a bug': "https://github.com/streamlit/streamlit/issues", 'About': "# 这是一个本地化 Streamlit 应用集合\n由 Python 工程师日常实践沉淀" } ) # 定义导航菜单(注意:必须在 st.set_page_config 之后) PAGES = { "🏠 首页": st.Page("pages/1_🏠_Home.py", icon=":material/home:"), "📊 数据探索": st.Page("pages/2_📊_Data_Explorer.py", icon=":material/table_eye:"), "📈 模型看板": st.Page("pages/3_📈_Model_Dashboard.py", icon=":material/monitoring:"), "⚙️ 设置": st.Page("pages/4_⚙️_Settings.py", icon=":material/settings:"), } # 渲染导航栏(关键!) pg = st.navigation(PAGES) pg.run()逻辑说明:
st.Page()的第一个参数是相对路径(相对于main.py所在目录),第二个参数icon使用 Material Symbols 的短码(需 Streamlit ≥ 1.35),若版本低可省略icon=。st.navigation()返回一个Page对象,调用.run()即启动路由系统。- 此方式下,点击导航项会刷新页面并更新 URL(如
/?page=2_%F0%9F%93%8A_Data_Explorer),支持浏览器前进/后退,且st.session_state在页面间默认持久化(除非手动del st.session_state[key])。
2.3pages/1_🏠_Home.py:首页不只是欢迎语,更是状态初始化中枢
# pages/1_🏠_Home.py import streamlit as st # 页面级配置(可选,覆盖全局) st.set_page_config(page_title="首页", page_icon="🏠") # 初始化全局状态(重要!避免 AttributeError) if 'app_mode' not in st.session_state: st.session_state.app_mode = "demo" # 可选值: "demo", "prod", "debug" if 'data_cache' not in st.session_state: st.session_state.data_cache = {} # 用于跨页缓存 DataFrame if 'user_role' not in st.session_state: st.session_state.user_role = "guest" # 后续可扩展权限逻辑 # 主内容 st.title("🚀 Streamlit 应用集合") st.markdown(""" > 这不是一个玩具项目,而是一套**可直接复用的工程化模板**: > - ✅ 支持多页导航与 URL 路由 > - ✅ 内置状态管理(`st.session_state` 跨页可用) > - ✅ 本地 CSS 注入与自定义样式 > - ✅ 模块化组件封装(见 `components/` 目录) > - ✅ 离线运行,无需任何云服务 """) # 快速跳转卡片(使用 st.link_button 替代旧版 st.button + st.experimental_rerun) col1, col2, col3 = st.columns(3) with col1: st.link_button("📊 探索数据", "/?page=2_%F0%9F%93%8A_Data_Explorer", use_container_width=True) with col2: st.link_button("📈 查看模型", "/?page=3_%F0%9F%93%88_Model_Dashboard", use_container_width=True) with col3: st.link_button("⚙️ 修改设置", "/?page=4_%E2%9A%99%EF%B8%8F_Settings", use_container_width=True) # 显示当前环境信息(调试用) with st.expander("🔧 当前运行环境"): st.write(f"Streamlit 版本: {st.__version__}") st.write(f"Python 版本: {st.runtime.scriptrunner.script_runner.get_script_run_ctx().session_id}") st.write(f"App 模式: `{st.session_state.app_mode}`")参数说明:
st.link_button是 Streamlit 1.32+ 新增 API,生成<a>标签而非表单提交,避免页面重载丢失状态,强烈推荐替代所有st.button跳转场景。use_container_width=True让按钮填满所在列宽,视觉更统一。st.expander中的st.runtime.scriptrunner...是获取当前会话 ID 的黑盒方法,仅用于调试,生产环境应移除。
3. 让应用“活起来”:用st.cache_data+st.cache_resource管理数据与模型加载
Streamlit 的重运行机制(每次交互都重跑整个脚本)是双刃剑:开发快,但若每次点击都重新读 CSV 或加载 500MB 模型,体验直接崩坏。缓存不是可选项,而是必须项。Streamlit 提供两类缓存装饰器,用错会引发静默错误或内存泄漏。
3.1st.cache_data:专治“重复读文件、查数据库、调 API”
适用场景:返回可哈希对象(dict,list,pd.DataFrame,np.ndarray)的函数,且输入参数可哈希(str,int,tuple)。
# components/data_loader.py import streamlit as st import pandas as pd from pathlib import Path @st.cache_data(ttl=3600, max_entries=10) # 缓存 1 小时,最多存 10 个不同参数的结果 def load_sample_data(file_path: str) -> pd.DataFrame: """ 加载示例数据集(CSV/Excel),支持本地路径与内置数据集名 Args: file_path: 本地文件路径(如 "data/sales.csv")或内置名(如 "titanic") Returns: pd.DataFrame: 加载后的数据框 """ p = Path(file_path) if p.exists() and p.suffix.lower() in ['.csv', '.xlsx', '.xls']: if p.suffix.lower() == '.csv': return pd.read_csv(p) else: return pd.read_excel(p) elif file_path == "titanic": # 内置数据集(实际项目中替换为真实数据源) return pd.read_csv("https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv") else: raise FileNotFoundError(f"未找到文件或不支持的格式: {file_path}") # 在 pages/2_📊_Data_Explorer.py 中调用 df = load_sample_data("titanic") # 第一次调用:读网络;后续调用:直接取缓存 st.dataframe(df.head(10))关键参数说明:
ttl=3600:Time-To-Live,单位秒。超时后自动失效,下次调用重新计算。对动态数据(如数据库查询)必须设 ttl,否则永远不更新。max_entries=10:限制缓存条目数,防止内存爆炸。当第 11 次调用不同参数时,最早缓存的条目被踢出。hash_funcs:当参数含不可哈希对象(如st.file_uploader返回的UploadedFile)时,需自定义哈希函数(见 3.3 节)。
3.2st.cache_resource:专治“加载大模型、连接数据库、初始化复杂对象”
适用场景:返回不可哈希对象(torch.nn.Module,sklearn.pipeline.Pipeline,sqlalchemy.engine.Engine)的函数,且初始化开销巨大。
# components/model_loader.py import streamlit as st import torch from transformers import AutoModelForSequenceClassification, AutoTokenizer @st.cache_resource # 注意:无 ttl 参数!资源缓存永不过期,直到应用重启 def load_sentiment_model(): """ 加载预训练情感分析模型(Hugging Face) 注意:首次运行会下载 ~1GB 模型权重,请确保网络通畅 """ model_name = "cardiffnlp/twitter-roberta-base-sentiment-latest" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name) model.eval() # 设为评估模式,禁用 dropout/batchnorm 更新 return tokenizer, model # 在 pages/3_📈_Model_Dashboard.py 中 tokenizer, model = load_sentiment_model() # 全局只加载一次,所有页面共享注意:
st.cache_resource没有ttl参数,因为资源对象(如模型)本身不随时间“过期”,但若需热更新(如模型权重更新),必须重启 Streamlit 进程(Ctrl+C →streamlit run main.py)。
3.3 处理st.file_uploader:当缓存遇上不可哈希对象
st.file_uploader返回的UploadedFile对象不可哈希,直接传给@st.cache_data会报错TypeError: unhashable type: 'UploadedFile'。正确解法是提取其可哈希属性(如文件名、字节内容哈希)作为缓存键:
# components/file_handler.py import streamlit as st import hashlib import io @st.cache_data def _hash_uploaded_file(uploaded_file) -> str: """计算上传文件内容的 MD5 哈希值(用于缓存键)""" if uploaded_file is None: return "" # 读取全部内容并计算哈希(小文件安全,大文件需分块) file_content = uploaded_file.getvalue() return hashlib.md5(file_content).hexdigest() @st.cache_data def load_uploaded_csv(file_hash: str, file_name: str) -> pd.DataFrame: """根据文件哈希和名称加载 CSV(实际加载逻辑)""" # 此处模拟:根据 hash 查找已缓存的 df,或重新解析 # 真实项目中,可将解析结果存入 st.session_state 或磁盘缓存 uploaded_file = st.session_state.get("_uploaded_file_obj") if uploaded_file is None: raise ValueError("No uploaded file found") return pd.read_csv(io.BytesIO(uploaded_file.getvalue())) # 在页面中使用 uploaded_file = st.file_uploader("上传 CSV 文件", type=["csv"]) if uploaded_file is not None: file_hash = _hash_uploaded_file(uploaded_file) # 将文件对象暂存到 session_state(绕过缓存限制) st.session_state["_uploaded_file_obj"] = uploaded_file df = load_uploaded_csv(file_hash, uploaded_file.name) st.dataframe(df.head())逻辑说明:
- 将
st.file_uploader的返回值不直接传入缓存函数,而是先用_hash_uploaded_file提取其内容哈希(可哈希),再将哈希作为缓存键。 - 用
st.session_state临时存储uploaded_file对象,供load_uploaded_csv内部读取。 - 此方案保证:同一文件多次上传 → 复用缓存;不同文件 → 新建缓存。这是处理用户上传文件的工业级标准做法。
4. 避坑指南:Streamlit 多页应用中 5 个血泪经验换来的高频问题排查
Streamlit 的简洁性背后藏着不少“玄学”陷阱。以下问题均来自某跨平台系统落地过程中的真实翻车现场,每一条都附带可复现的最小案例和根治方案。
4.1 现象:页面切换后st.session_state中的自定义类实例消失,报AttributeError
原因:Streamlit 的序列化机制(基于cloudpickle)无法序列化某些自定义类(尤其含 lambda、嵌套函数、C 扩展对象的类)。当页面重载时,st.session_state试图序列化/反序列化整个字典,失败则丢弃该 key。
复现代码:
# 在 pages/1_🏠_Home.py 中 class DataProcessor: def __init__(self): self.cache = {} if 'processor' not in st.session_state: st.session_state.processor = DataProcessor() # 此行在页面切换后失效解决:
- ✅方案一(推荐):将自定义类实例拆解为纯数据(
dict,list),用@st.cache_resource管理类工厂函数:@st.cache_resource def get_processor(): return DataProcessor() # 全局单例,永不序列化 st.session_state.processor = get_processor() - ❌ 避免:在
st.session_state中存threading.Lock,socket.socket,pandas.DataFrame(虽可序列化但极慢)等重型对象。
4.2 现象:st.cache_data函数内修改了传入的pd.DataFrame,导致后续调用结果异常
原因:@st.cache_data默认启用allow_output_mutation=False,若函数内部修改了输入参数(in-place mutation),缓存系统无法检测变化,返回旧结果。
复现代码:
@st.cache_data def process_df(df: pd.DataFrame) -> pd.DataFrame: df["new_col"] = df["old_col"] * 2 # 错误!原地修改输入 df return df df_in = pd.DataFrame({"old_col": [1, 2, 3]}) result = process_df(df_in) # 第一次:[2,4,6] df_in["old_col"] = [10,20,30] result2 = process_df(df_in) # 第二次:仍返回 [2,4,6]!因为缓存键未变解决:
- ✅强制深拷贝输入:
df_copy = df.copy()再操作; - ✅启用允许输出变异:
@st.cache_data(allow_output_mutation=True),但仅当明确知道副作用可控时使用; - ✅最佳实践:函数内绝不修改输入参数,所有变换用
df.assign()、df.pipe()等函数式方法。
4.3 现象:st.navigation导航栏不显示图标,或显示为方块
原因:Material Symbols 图标需 Streamlit ≥ 1.35 且浏览器支持font-display: swap。旧版 Streamlit 或禁用字体的浏览器(如某些企业内网 IE 兼容模式)会回退为 Unicode 字符,显示异常。
解决:
- ✅降级兼容:在
st.Page()中省略icon=参数,改用文字前缀:st.Page("pages/1_🏠_Home.py", title="🏠 首页") # title 中包含 emoji - ✅强制加载字体:在
assets/style.css中添加:
并在@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200');main.py中注入:with open("assets/style.css") as f: st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
4.4 现象:st.file_uploader上传大文件(>100MB)时卡死或报413 Request Entity Too Large
原因:Streamlit 内置 Tornado 服务器默认client_max_size=200*1024*1024(200MB),但前端 JavaScript 上传库有额外限制。
解决:
- ✅启动时指定大小:
streamlit run main.py --server.maxUploadSize=1024(单位 MB); - ✅前端分片上传:对超大文件,改用
st.file_uploader的accept_multiple_files=True+ 后端合并,或集成streamlit-upload第三方组件; - ✅生产部署时:Nginx/Apache 需同步调整
client_max_body_size。
4.5 现象:本地运行正常,但打包成.exe(PyInstaller)后st.navigation报ModuleNotFoundError
原因:PyInstaller 打包时未自动收集pages/目录下的.py文件,st.navigation运行时找不到模块。
解决:
- ✅显式添加数据文件:在 PyInstaller spec 文件中加入:
a = Analysis( ... datas=[('pages', 'pages')], # 将 pages/ 目录复制到打包后根目录 ... ) - ✅启动脚本中动态添加 pages 路径:
import sys if getattr(sys, 'frozen', False): # 打包后,pages 在 _MEIPASS 下 import os sys.path.append(os.path.join(sys._MEIPASS, 'pages'))
5. 真正的工程化:用st.fragment+ 自定义组件实现局部重运行与 UI 复用
Streamlit 默认“全脚本重运行”机制,在复杂页面中会导致不必要的计算浪费(如:只改一个滑块,却重跑整个 EDA 流程)。st.fragment是 Streamlit 1.26+ 引入的革命性特性,它允许你将 UI 区域标记为独立重运行单元,实现类似 React 的局部更新。
5.1st.fragment:告别“点哪刷哪”,实现精准重绘
传统写法(低效):
# pages/2_📊_Data_Explorer.py(问题版) st.title("数据探索") df = load_sample_data("titanic") # 每次交互都重跑! col1, col2 = st.columns(2) with col1: numeric_col = st.selectbox("选择数值列", df.select_dtypes(include='number').columns) with col2: bins = st.slider("直方图分箱数", 5, 100, 30) # 下面所有图表都会因任意控件改变而重绘 st.subheader(f"{numeric_col} 分布") st.histogram(df[numeric_col], bins=bins) # 重绘 st.subheader("相关性热力图") st.heatmap(df.corr()) # 也重绘!但其实不需要优化后(高效):
# pages/2_📊_Data_Explorer.py(优化版) st.title("数据探索") df = load_sample_data("titanic") # 全局只加载一次 # === Fragment 1:直方图区域 === @st.fragment def render_histogram(): col1, col2 = st.columns(2) with col1: numeric_col = st.selectbox("选择数值列", df.select_dtypes(include='number').columns, key="hist_col") with col2: bins = st.slider("直方图分箱数", 5, 100, 30, key="hist_bins") st.subheader(f"{numeric_col} 分布") st.histogram(df[numeric_col], bins=bins) # === Fragment 2:热力图区域 === @st.fragment def render_heatmap(): st.subheader("相关性热力图") st.heatmap(df.corr()) # 调用两个独立 fragment render_histogram() render_heatmap()关键逻辑:
@st.fragment装饰的函数,其内部st.*组件形成一个隔离的重运行域。key参数必须唯一(即使同名控件在不同 fragment 中也要加不同 key),否则状态冲突。- 当用户调整
hist_col时,只有render_histogram重运行,render_heatmap完全不动,节省 80%+ 渲染时间。 st.fragment可嵌套,但建议单层,避免状态混乱。
5.2 封装可复用组件:components/目录实战
将高频 UI 模块抽象为函数,存入components/目录,实现“一次编写,全站复用”。
# components/permission_banner.py import streamlit as st def show_permission_banner(required_role: str = "admin"): """ 权限提示横幅:当用户角色不足时显示警告 Args: required_role: 所需最低角色("guest", "user", "admin") """ user_role = st.session_state.get("user_role", "guest") role_levels = {"guest": 0, "user": 1, "admin": 2} if role_levels.get(user_role, 0) < role_levels.get(required_role, 0): st.warning( f"⚠️ 当前权限不足:需要 `{required_role}` 角色才能访问此功能。\n" f"当前角色:`{user_role}`。请联系管理员升级权限。", icon="🔒" ) st.stop() # 阻止后续代码执行 # 在 pages/4_⚙️_Settings.py 中调用 show_permission_banner("admin") # 若非 admin,直接终止页面渲染 st.title("⚙️ 系统设置(仅管理员可见)")# components/data_summary.py import streamlit as st import pandas as pd def data_summary(df: pd.DataFrame, title: str = "数据概览"): """ 通用数据摘要组件:显示形状、缺失值、内存占用 Args: df: 输入 DataFrame title: 横幅标题 """ st.subheader(title) col1, col2, col3 = st.columns(3) with col1: st.metric("行数", df.shape[0]) with col2: st.metric("列数", df.shape[1]) with col3: st.metric("内存占用", f"{df.memory_usage(deep=True).sum() / 1024**2:.1f} MB") st.caption("缺失值统计(前 10 列)") missing_df = df.isnull().sum().head(10).to_frame("缺失数量") missing_df["缺失比例"] = (missing_df["缺失数量"] / len(df) * 100).round(2) st.dataframe(missing_df, use_container_width=True) # 在 pages/2_📊_Data_Explorer.py 中 data_summary(df, "上传数据概览")提示:组件函数应保持无副作用(不修改
st.session_state除非明确设计)、高内聚(一个函数只做一件事)、可测试(输入df,输出 UI,无外部依赖)。
5.3 最后一公里:离线部署与启动脚本固化
一个真正能交付的 Streamlit 集合,必须让用户双击即用,无需命令行。
步骤 1:生成requirements.txt(精确到补丁号)
# 在项目根目录执行(确保虚拟环境激活) pip install streamlit==1.35.0 pandas==2.2.2 scikit-learn==1.4.2 pip freeze > requirements.txt为什么锁版本?某次
streamlit升级 1.34→1.35 导致st.navigationAPI 微调,未锁版本的requirements.txt会让用户安装失败。
步骤 2:编写start.bat(Windows)与start.sh(macOS/Linux)
:: start.bat @echo off echo 启动 Streamlit 应用集合... echo 正在检查 Python 环境... where python >nul 2>&1 if %errorlevel% neq 0 ( echo 错误:未找到 Python,请先安装 Python 3.9+ pause exit /b 1 ) echo 正在安装依赖... pip install -r requirements.txt --quiet echo 启动中...(请稍候 5 秒) start "" http://localhost:8501 streamlit run main.py --server.port=8501 --server.address=127.0.0.1#!/bin/bash # start.sh echo "启动 Streamlit 应用集合..." if ! command -v python3 &> /dev/null; then echo "错误:未找到 python3,请先安装 Python 3.9+" exit 1 fi echo "正在安装依赖..." pip3 install -r requirements.txt --quiet echo "启动中...(请稍候 5 秒)" open "http://localhost:8501" streamlit run main.py --server.port=8501 --server.address=127.0.0.1步骤 3:终极验证 —— 断网测试
- 关闭 Wi-Fi/网线;
- 双击
start.bat或运行./start.sh; - 确认浏览器打开
http://localhost:8501,所有页面、图表、交互均正常; - 上传本地 CSV,验证
st.file_uploader与st.cache_data仍工作。
只有通过断网测试,才证明这是一个真正离线、自包含、可交付的数据应用集合。我曾因忽略此步,在客户现场演示时因防火墙拦截 Hugging Face 模型下载而尴尬收场——那之后,所有st.cache_resource加载的远程资源,我都提前下载到models/目录并改用本地路径。
希望帮到你。
本文还有配套的精品资源,点击获取