尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Streamlit实战指南:从PyCharm配置到WebView部署的完整教程

Streamlit实战指南:从PyCharm配置到WebView部署的完整教程 Streamlit 这几年在数据圈子里火得不行几乎成了 Python 数据分析师和算法工程师做 Demo 的标配工具。它最吸引人的地方在于你只要写纯 Python 脚本不用碰任何前端代码就能把数据应用、模型演示、报表看板直接跑成网页。很多朋友第一次在 PyCharm 里跑通streamlit run app.py看到浏览器弹出界面的时候都会有一种原来做 Web 应用可以这么简单的感叹。这篇教程我打算从零开始把 Streamlit 的核心概念、常用组件、状态管理、性能优化以及最近群里问得特别多的两个问题——WebView 加载 Streamlit 地址白屏、PyCharm 里怎么配置项目——一次性讲透。内容偏实战每一步我都会说清楚为什么这么写适合刚入门的新手也适合想系统梳理一遍的进阶用户。1. 整体设计与思路拆解Streamlit 到底解决了什么问题1.1 为什么选 Streamlit 而不是 Flask 或 Django聊 Streamlit 之前先说说它和传统 Web 框架的区别。用 Flask 写一个数据展示页面你得懂 HTML 模板、路由、请求处理还得把 DataFrame 序列化成 JSON 传到前端再用 JavaScript 渲染。这一套流程对纯做数据分析的人来说学习成本实在太高了。Streamlit 的核心理念是脚本即应用你从上到下写 Python 代码Streamlit 自动帮你把代码执行结果渲染到网页上每次交互触发重跑整个页面自动更新。这背后其实是一个每次交互全量重跑脚本的执行模型。这个设计思路和 Flask 完全不同。Flask 是传统的请求-响应模型每个 URL 对应一个视图函数页面之间靠路由跳转Streamlit 则像一个带状态的脚本执行器用户在页面上点击按钮、滑动滑块、输入文本这些操作会作为新的输入参数重新从上到下执行一遍你的脚本然后生成新的 UI。这种模型牺牲了一部分性能但换来了极低的开发门槛——你不需要理解前后端分离不需要写一行 JavaScript。从我的实际使用体验来看Streamlit 最适合这几类场景数据分析报告的交互化展示、机器学习模型的快速 Demo、内部工具和数据看板、给非技术同事用的数据查询界面。如果你的目标是给客户交付一个生产级、需要复杂权限控制的商用系统那 Streamlit 可能不是最优解它更偏向于快速验证和内部效率工具。1.2 核心执行模型自上而下、遇交互则重跑理解 Streamlit 的第一步是接受它脚本重跑的执行机制。你每次和页面交互Streamlit 就把整个脚本从第一行到最后一行重新执行一遍然后基于新的状态渲染出最新界面。这个机制导致了一个经典坑如果脚本里有耗时操作比如加载大文件、训练模型每次点按钮都会重复执行一次页面会卡住。解决办法就是把耗时操作尽量放到缓存里。Streamlit 提供了st.cache_data和st.cache_resource两个装饰器前者缓存数据加载和计算密集型函数的结果后者缓存全局资源比如数据库连接、模型实例。加了这两个装饰器之后同样的参数只会真正执行一次函数后续直接命中缓存速度提升非常明显。这个设计我后文会专门展开讲因为它是 Streamlit 应用中快和慢的分水岭。还有一个初学者容易困惑的点页面上多个组件之间是怎么通信的答案是 session_state。Streamlit 每次重跑脚本时本地变量都会重新赋值如果想让某个值跨重跑保留就必须放进st.session_state里。这个字典对象是跨脚本重跑持久化的相当于前端浏览器的 localStorage用起来非常顺手。1.3 开发体验的取舍适合谁、不适合谁我不建议无脑吹 Streamlit。它确实把 Web 开发的门槛拉低了但代价是自定义能力受限。如果你需要高度定制化的页面样式、复杂的交互动效Streamlit 的组件层会比较难扩展。好在社区生态已经比较成熟有大量的st.*组件和第三方插件比如streamlit-aggrid做表格筛选、streamlit-echarts做图表可以弥补不少缺口。说到底选不选 Streamlit 取决于项目诉求。我的经验是开发周期在两三天内、核心价值在数据和逻辑本身、用户量在百人级别以内的工具类应用用 Streamlit 最合适。超过这个规模再考虑 React FastAPI 这种前后端分离方案。2. 环境准备与 PyCharm 配置实战2.1 安装 Streamlit 与环境隔离安装很简单但我强烈建议先把 Python 虚拟环境建好。很多同学在 PyCharm 里装了一堆包之后某个依赖版本冲突导致 Streamlit 启动报错排查半天最后发现是全局环境被搞乱了。这一步我个人的习惯是先用python -m venv或者 conda 创建独立环境再装依赖。# 创建虚拟环境Windows 和 macOS/Linux 命令略有差异 python -m venv streamlit_env # 激活环境 # Windows: streamlit_env\Scripts\activate # macOS/Linux: source streamlit_env/bin/activate # 安装 Streamlit pip install streamlit # 验证版本 streamlit version装完之后跑一下自带的 Hello 示例验证环境是否正常streamlit hello如果浏览器弹出官方示例界面说明环境没问题。这一步要是界面上有报错多半是端口被占用或者依赖包版本冲突后面我会给出排查思路。2.2 PyCharm 中配置 Streamlit 运行方式在 PyCharm 里运行 Streamlit很多新手会直接点右上角 Run 按钮然后发现终端里报错 No module named streamlit 或者直接没反应。这是因为 Streamlit 应用不是常规的 Python 文件执行方式它需要通过streamlit run命令启动而不是python app.py。配置方法其实很简单我推荐两种方式。第一种是配置 Run Configuration打开 PyCharm 菜单栏 Run - Edit Configurations。点击左上角加号选择 Python。Name 随意填比如Streamlit App。Script path 选择你虚拟环境里的 streamlit 命令行入口通常在streamlit_env/Scripts/streamlit.exeWindows或者streamlit_env/bin/streamlitmacOS/Linux。如果你不确定入口路径可以在终端执行which streamlit查看。Parameters 填run app.py假设你的主文件叫 app.py。Working directory 设置为项目根目录。第二种方式更省事直接在 PyCharm 的 Terminal 里手动激活环境再启动streamlit_env\Scripts\activate streamlit run app.py我个人比较喜欢第二种因为配置一次之后后续只需要重复两条命令而且能看到完整的日志输出。再搭配 PyCharm 的 Auto Import 和代码补全开发体验其实蛮舒服的。这里有一个很多人踩过的坑如果你在 PyCharm 的项目解释器里已经选好了虚拟环境但点击运行按钮还是报找不到模块大概率是 Run Configuration 用的 Python 解释器和实际环境的解释器不一致。检查一下 Settings - Project - Python Interpreter 是否指向streamlit_env再把 Run Configuration 的 Python Interpreter 也改对问题就解决了。2.3 端口与启动参数说明Streamlit 启动时默认监听 8501 端口。如果端口被占用页面会一直打不开终端里也会提示。可以通过参数指定端口streamlit run app.py --server.port 8502另外几个常用参数也顺便提一下--server.address 0.0.0.0允许局域网访问同一 Wi-Fi 下的手机或同事电脑可以直接通过你的 IP 访问应用。--server.headless true禁止自动打开浏览器适合服务器环境部署时使用。--server.fileWatcherType auto文件变更自动重载脚本开发时非常方便。我在开发的时候习惯把文件监视器开着这样改完代码保存浏览器页面会自动刷新不用手动重启服务效率提升很明显。3. 核心 API 详解与第一个完整应用3.1 文本、数据、图表三件套Streamlit 最常用的就三个系列文本展示、数据展示、图表展示。文本方面有st.title、st.header、st.subheader、st.write其中st.write是万金油能自动识别传入对象类型并选择合适的渲染方式。数据方面是st.dataframe和st.table前者支持排序、滚动、列宽自适应交互更丰富后者就是静态表格。图表方面和 Pandas 配合最丝滑。直接用st.line_chart(df)、st.bar_chart(df)、st.area_chart(df)就能出图本质上底层用的是 Altair。如果要做更复杂的可视化可以用 Plotlyimport streamlit as st import pandas as pd import plotly.express as px # 模拟一份销售数据 df pd.DataFrame({ 月份: [1月, 2月, 3月, 4月, 5月], 销售额: [120, 190, 170, 240, 310], 成本: [80, 120, 110, 150, 180], }) st.title(销售数据看板) st.write(这是月度销售情况概览数据为示例数据。) col1, col2, col3 st.columns(3) col1.metric(总销售额, f{df[销售额].sum()} 万) col2.metric(平均销售额, f{df[销售额].mean():.1f} 万) col3.metric(最高月销售额, f{df[销售额].max()} 万) st.dataframe(df) fig px.bar(df, x月份, y[销售额, 成本], barmodegroup) st.plotly_chart(fig)把这段代码保存为app.py然后运行streamlit run app.py几秒钟就能看到一个带指标卡、表格和交互式图表的页面。这套组合几乎覆盖了 80% 的数据展示需求。3.2 常用交互组件的正确打开方式交互组件是 Streamlit 的灵魂。你只需要关心用户输入了什么不用管怎么传递和响应因为脚本重跑机制已经替你解决了。常用的组件包括st.button按钮返回 True/False每次点击触发一次重跑。st.text_input单行文本输入框。st.text_area多行文本输入框。st.selectbox下拉选择框。st.multiselect多选下拉框。st.slider滑动条。st.radio单选按钮。st.checkbox复选框。st.file_uploader文件上传。st.date_input、st.time_input日期和时间选择。组件的返回值基本都是用户在界面上的当前值直接赋值给变量使用即可。举一个带筛选功能的例子import streamlit as st import pandas as pd df pd.read_csv(sales_data.csv) st.title(订单筛选器) # 侧边栏放筛选控件 with st.sidebar: category st.selectbox(选择品类, df[品类].unique()) min_amount st.slider(最低金额, 0, 1000, 100) filtered df[(df[品类] category) (df[金额] min_amount)] st.write(f共 {len(filtered)} 条订单符合条件) st.dataframe(filtered)筛选逻辑写在组件之后每次用户调整控件脚本重跑筛选结果自动更新。这种声明式的交互方式比传统事件驱动的写法要直观得多。3.3 st.write 与魔法命令Streamlit 的st.write可以接收几乎任何对象字符串、DataFrame、matplotlib 图表、甚至自定义组件。在脚本顶层直接写变量名或表达式也会被自动渲染到页面上这个叫魔法命令。比如import pandas as pd df pd.DataFrame({a: [1, 2, 3], b: [4, 5, 6]}) # 魔法命令直接输出 df页面会直接渲染出一个表格。这种写法特别适合在交互式开发阶段快速查看数据比我一开始用st.write(df)还省事。当然正式代码里还是建议显式调用st.write避免可读性下降。4. 页面布局、状态管理与缓存进阶4.1 侧边栏、多列、标签页布局方案真实的工具类应用不可能只是一块竖着的长条内容Streamlit 也提供了一套布局组件。最常用的是st.sidebar把筛选条件放左边主区域放内容这是数据看板最常见的布局形态。import streamlit as st st.sidebar.title(控制面板) option st.sidebar.selectbox(选择视图, [概览, 明细, 图表]) threshold st.sidebar.slider(阈值, 0, 100, 50) st.title(f当前视图{option}) st.write(f当前阈值{threshold})除了侧边栏st.columns可以把内容排成多列st.tabs做标签页切换st.expander做折叠面板st.container做内容容器。多级页面则用st.navigation和st.Page新版 API来管理。对于复杂布局我建议多用st.container分区它保证同一容器内的元素在逻辑上是一个整体配合st.columns可以让不同区块之间互不干扰这在动态渲染内容时特别重要。4.2 session_state跨重跑保留数据的核心机制前面说过Streamlit 每次交互都会重跑脚本普通变量会被重置。如果想让计数器累加、保存用户登录状态、缓存表单输入就必须借助st.session_state。import streamlit as st # 初始化计数 if count not in st.session_state: st.session_state.count 0 def increment(): st.session_state.count 1 st.button(加一, on_clickincrement) st.write(f当前计数{st.session_state.count})这里有一个细节值得注意on_click回调里的代码是在按钮点击时执行的而不是在脚本重跑时执行的。如果直接在脚本顶层写st.session_state.count 1会因为重跑逻辑导致计数被重复增加。所以需要修改状态时要么写成回调函数要么先执行修改再更新 UI思路要转过来。st.session_state还有一个常见用途是保持输入框的历史值或者实现上一步/下一步的分步表单功能。前者只要给组件加一个key参数Streamlit 会自动把当前值存到st.session_state[key]里面读取和修改都很方便。4.3 缓存机制从慢到快的分水岭之前提到的st.cache_data和st.cache_resource这里展开说说。st.cache_data会基于函数参数做哈希参数相同就返回缓存结果。对于耗时的数据加载、ETL、爬虫抓取缓存是立竿见影的优化手段。import streamlit as st import pandas as pd st.cache_data def load_data(file_path): # 模拟耗时读取 df pd.read_csv(file_path) return df df load_data(data.csv)第一遍执行时函数真的跑一次之后无论页面怎么重跑、用户怎么交互只要文件路径没变都会直接命中缓存页面响应速度会快好几个数量级。st.cache_resource则适合缓存那些不宜被序列化、需要保持全局唯一状态的资源比如sqlalchemy的数据库连接引擎、已训练好的机器学习模型对象。它的生命周期和缓存对象绑定直到脚本重启或缓存被主动清理。注意两个容易踩的坑一是缓存函数要保证同样的输入永远得到同样的输出如果你在函数内部用了随机数或时间函数缓存会导致结果不再更新二是如果数据源文件更新了缓存不会自动失效你可以调用load_data.clear()手动清理或者给函数加一个version参数变化时自然刷新。5. 数据处理、文件上传与实战案例整合5.1 文件上传与实时解析企业内部工具里最常出现的需求就是上传 Excel让我筛一筛、看一看。Streamlit 的st.file_uploader可以直接接收文件对象配合 Pandas 解析非常顺手。import streamlit as st import pandas as pd uploaded st.file_uploader(上传 Excel 或 CSV 文件, type[csv, xlsx]) if uploaded is not None: # 根据后缀选择解析方式 if uploaded.name.endswith(.csv): df pd.read_csv(uploaded) else: df pd.read_excel(uploaded) st.write(f文件共 {df.shape[0]} 行{df.shape[1]} 列) st.dataframe(df.head(100))需要注意的是st.file_uploader返回的是一个BytesIO对象不是文件路径。直接用 Pandas 读取没问题但如果你想用pd.read_excel读取.xlsx需要先安装openpyxl库否则会报错。5.2 实战一个订单数据快速查询工具把前面的知识点整合起来我做一个完整的工具上传订单文件侧边栏选择品类、金额区间和日期范围主区域展示筛选结果和汇总图表。import streamlit as st import pandas as pd import plotly.express as px st.set_page_config(page_title订单查询工具, layoutwide) st.cache_data def load_data(uploaded_file): if uploaded_file.name.endswith(.csv): return pd.read_csv(uploaded_file) else: return pd.read_excel(uploaded_file) st.title(订单数据快速查询工具) uploaded st.file_uploader(上传订单文件, type[csv, xlsx]) if uploaded is None: st.info(请先上传文件) st.stop() df load_data(uploaded) # 将日期列转为 datetime if 日期 in df.columns: df[日期] pd.to_datetime(df[日期]) with st.sidebar: st.header(筛选条件) categories st.multiselect(品类, df[品类].unique(), defaultdf[品类].unique()) amount_col st.selectbox(金额列, df.columns) min_val float(df[amount_col].min()) max_val float(df[amount_col].max()) amount_range st.slider(金额范围, min_val, max_val, (min_val, max_val)) mask df[品类].isin(categories) mask df[amount_col].between(*amount_range) if 日期 in df.columns: date_min df[日期].min().date() date_max df[日期].max().date() date_range st.sidebar.date_input(日期范围, [date_min, date_max]) mask (df[日期].dt.date date_range[0]) (df[日期].dt.date date_range[1]) filtered df[mask] c1, c2, c3 st.columns(3) c1.metric(订单数, len(filtered)) if len(filtered) 0: c2.metric(总金额, f{filtered[amount_col].sum():,.2f}) c3.metric(平均金额, f{filtered[amount_col].mean():,.2f}) st.dataframe(filtered, use_container_widthTrue) if len(filtered) 0 and 品类 in filtered.columns: fig px.bar(filtered.groupby(品类)[amount_col].sum().reset_index(), x品类, yamount_col, title各品类金额汇总) st.plotly_chart(fig, use_container_widthTrue)这个例子涉及文件上传、缓存、侧边栏联动、数据筛选、指标卡和图表的完整链路基本能代表 Streamlit 开发的一个标准节奏。5.3 下载结果与导出做工具的时候用户筛完数据往往要导出。Streamlit 提供了st.download_button可以把 DataFrame 转成 CSV 字符串或者 Excel bytes 供用户下载。import pandas as pd import streamlit as st csv_data filtered.to_csv(indexFalse).encode(utf-8-sig) st.download_button( label下载筛选结果 CSV, datacsv_data, file_namefiltered_result.csv, mimetext/csv, )加上utf-8-sig编码可以防止 Excel 打开 CSV 时中文乱码这是我在给同事交付工具时踩过的一个小坑。6. 常见问题与排查技巧实录6.1 WebView 加载 Streamlit 地址白屏问题最近群里问得最多的问题就是把 Streamlit 的 URL 放到 App 的 WebView 里打开一片白。这个问题我折腾过不少时间先说结论WebView 白屏的原因通常不是 Streamlit 本身坏了而是 WebView 默认没有开启 JavaScript或者 WebView 无法访问 Streamlit 的长轮询连接。Streamlit 的页面渲染依赖 WebSocket 和服务端通信。在浏览器里页面先加载 HTML然后通过 WebSocket 建立连接服务端推送脚本执行结果再由前端 JavaScript 渲染。WebView 加载 Streamlit 地址时如果JavaScriptEnabled是关闭的页面完全无法执行任何脚本自然就是白屏。解决办法因平台而异。在 Android 原生 WebView 里要这样设置WebView webView findViewById(R.id.webview); webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setDomStorageEnabled(true); webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); webView.loadUrl(http://你的服务器IP:8501);这里有几个关键设置setJavaScriptEnabled(true)必须开启否则 Streamlit 的前端脚本跑不起来。setDomStorageEnabled(true)Streamlit 会用到 localStorage 和 sessionStorage 管理状态关了也会出问题。MixedContentMode如果你的 Streamlit 服务是 HTTP 而 WebView 外层页面是 HTTPS不设置这个会拦截混合内容同样白屏。如果是 iOS 的 WKWebView对应的配置是要允许任意加载let webView WKWebView() webView.configuration.preferences.javaScriptEnabled true webView.load(URLRequest(url: URL(string: http://服务器IP:8501)!))还需要在 Info.plist 里配置NSAppTransportSecurity允许 HTTP 加载否则 iOS 默认会拦截所有非 HTTPS 请求。另外还有一个细节如果 WebView 的 UA 被设置成了移动端浏览器Streamlit 部分老版本对移动端布局支持不好也可能导致内容不显示。可以考虑给 WebView 设置一个桌面端 UA。6.2 PyCharm 中运行 Streamlit 的典型报错PyCharm 相关最常见的问题有三个第一个是运行后提示streamlit: command not found。这说明当前终端环境里没有 streamlit 命令通常是 PyCharm 默认的终端没有激活虚拟环境。解决方法是检查 PyCharm 的 Terminal 设置里的 Shell path或者手动在终端里先激活环境再运行。第二个是ModuleNotFoundError: No module named streamlit。这个基本确定是解释器选错了。在 Settings - Project - Python Interpreter 里确认当前项目用的是哪个环境把解释器切换到安装了 streamlit 的虚拟环境再重新运行。第三个是运行后浏览器打开 显示 connection refused。这种情况要么是服务没起来要么是端口被防火墙拦了。先在终端里看有没有 Streamlit 的启动日志如果日志正常就检查防火墙是否允许 8501 端口或者换一个端口试试。6.3 其他高频问题速查我把日常开发遇到的高频问题整理成一个速查表方便大家对照处理。问题现象可能原因解决办法页面一直转圈不显示WebSocket 被防火墙或代理拦截检查网络环境确认 8501 端口可达修改代码后页面不刷新文件监视器未生效确认--server.fileWatcherType配置或手动刷新浏览器多个用户同时使用数据错乱全局变量在重跑中互相干扰使用st.session_state而非模块级全局变量上传文件后每次交互卡顿没有使用缓存给文件读取函数加st.cache_data侧边栏控件状态总是重置组件未设置key导致无法定位状态为每个组件添加唯一的key参数部署到公网后样式丢失静态资源路径问题确认使用streamlit run而非直接托管静态文件这里想特别强调key参数。很多人在页面里写了两个st.selectbox偶尔发现切换其中一个时另一个也变了就是因为没指定keyStreamlit 内部按组件类型和顺序识别状态结构一变就容易串。给每个交互组件一个明确唯一的key是最省心的习惯。6.4 部署时容易忽视的细节本地跑通之后大家通常会想把应用分享出去。Streamlit 官方有 Community Cloud 可以直接部署绑定 GitHub 仓库就行。但国内访问速度不稳定我通常建议部署到自己的云服务器上。部署时注意几点先用pip freeze requirements.txt导出依赖服务器上建议也用虚拟环境然后用nohup streamlit run app.py --server.port 8501 --server.address 0.0.0.0 app.log 21 后台启动。如果想做更正规的进程管理可以用 systemd 或者 supervisor把 Streamlit 注册成系统服务崩溃后自动重启。公网部署还要考虑 HTTPS。如果通过 Nginx 反向代理需要配置 WebSocket 升级头否则页面能打开但交互无响应。在 Nginx 配置里要加上location / { proxy_pass http://127.0.0.1:8501; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; }这里Upgrade和Connection是关键WebSocket 长连接依赖这两个头才能正常建立。6.5 调试技巧st.write 是一个万能调试器最后分享一个我在开发中天天用的调试技巧遇到不知道组件返回了什么值、不确定 DataFrame 长什么样直接在代码里用st.write打印出来。因为它能自动渲染各种类型比print更适合 Streamlit 的交互式开发节奏。st.write(当前筛选结果前5行) st.write(filtered.head())配合页面上实时展示的输出你能立刻看到每组输入对应的中间结果定位问题比看日志快得多。等调试完了再把这些调试输出删掉或者放到st.expander折叠起来避免污染正式界面。我个人在实际操作中的体会是Streamlit 的上手曲线其实很短花一个下午看完常用 API再独立做一个小工具基本就能掌握大部分用法。真正拉开差距的是对执行模型的理解——什么时候重跑、什么该进缓存、什么状态该放 session_state、什么时候该用回调函数。把这些想明白了写出来的应用又快又稳。最后一个建议从一个小的真实需求开始练手比如把自己的日报数据做成一个筛选面板比照着教程抄十遍都管用。动手跑通第一个应用那种成就感会推着你继续往下深入。
返回列表