浏览器里能不能先把东西跑出来,我一般一眼就看这件事。很多 Python 项目不是死在算法,也不是死在接口,死在“给别人看”这一步:命令行能跑,日志也有,结果老板、产品、测试一打开电脑,全体失去战斗力。
这种时候我基本不太想上来就搭 Django、Flask 再补前端。页面没做出来之前,讨论再多都虚。要的是先把数据、筛选、图表、上传下载这几件事按住,能点,能看,能改。Streamlit 干的就是这个活。
我第一次用 Streamlit,不是奔着“学新框架”去的。
是因为手里有个 Python 脚本,跑出来一堆 CSV,业务同事天天问三句话:
问到第三次,我就知道命令行这条路已经走不通了。你再发 .py 文件给别人,人家也不会陪你 pip install。这事得换个壳。
Streamlit 最大的好处,不是多强,而是省事。 它有点像什么?像你把一个 Python 脚本,硬生生拍成一个可交互页面。按钮、表格、图表、上传文件、侧边栏参数,基本都现成。尤其适合下面这几类活:
说白了,凡是“先出来一个能用的页面再说”的活,它都合适。
先来最小安装:
pip install streamlit pandas matplotlib
启动一个最简单的页面:
streamlit run app.py
app.py 就先写成这样:
import streamlit as st
st.title("第一个 Streamlit 页面")
st.write("页面先跑起来,后面再慢慢加东西。")
你会发现浏览器直接开了。 这一点非常重要。
很多人学框架,第一步就栽在环境、路由、模板、静态资源。Streamlit 这地方比较讨喜,先把页面拉起来再说,心理负担小很多。
拿一个很常见的场景来说: 你有一份销售数据 CSV,想做一个页面,支持:
这个就很像公司里真实会用到的小工具了。
直接上代码。
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
st.set_page_config(page_title="销售数据看板", layout="wide")
st.title("销售数据看板")
uploaded_file = st.file_uploader("上传销售 CSV 文件", type=["csv"])
if uploaded_file isnotNone:
df = pd.read_csv(uploaded_file)
st.subheader("原始数据")
st.dataframe(df, use_container_width=True)
# 这里我一般会先做一层容错,别让页面一上来就炸
required_cols = ["order_date", "city", "amount"]
missing_cols = [col for col in required_cols if col notin df.columns]
if missing_cols:
st.error(f"缺少必要字段: {missing_cols}")
st.stop()
df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce")
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")
df = df.dropna(subset=["order_date", "amount"])
city_list = ["全部"] + sorted(df["city"].dropna().unique().tolist())
selected_city = st.sidebar.selectbox("选择城市", city_list)
if selected_city != "全部":
df = df[df["city"] == selected_city]
st.subheader("筛选后数据")
st.dataframe(df, use_container_width=True)
st.metric("订单数", len(df))
st.metric("销售总额", f"{df['amount'].sum():,.2f}")
trend_df = (
df.groupby(df["order_date"].dt.date)["amount"]
.sum()
.reset_index()
.sort_values("order_date")
)
st.subheader("销售趋势图")
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(trend_df["order_date"], trend_df["amount"])
ax.set_xlabel("日期")
ax.set_ylabel("销售额")
ax.set_title("每日销售额趋势")
plt.xticks(rotation=45)
st.pyplot(fig)
csv_data = df.to_csv(index=False).encode("utf-8-sig")
st.download_button(
label="下载筛选结果",
data=csv_data,
file_name="filtered_sales.csv",
mime="text/csv"
)
else:
st.info("先上传一个 CSV 文件。")
这个页面没什么花活,但很实用。 你会发现它已经具备一个内部工具该有的基本骨架了。
我自己做这类页面时,第一版通常就这几个东西:
先把闭环跑通。别一开始就纠结样式,那个阶段大多没意义。
Streamlit 为什么适合 Python 程序员
这地方得说句实在话。
很多 Python 程序员不是不会做页面,是不想被前端那套东西绊住。你让他改数据逻辑、清洗字段、对接模型、调 SQL,他很快。你让他写一堆 HTML、CSS、JS,再研究状态同步,速度立马掉下来。
Streamlit 的好处是:
页面和逻辑写在一起。
比如一个按钮:
if st.button("开始处理"):
st.success("处理完成")
一个下拉框:
model_name = st.selectbox("选择模型", ["模型A", "模型B", "模型C"])
一个文本输入框:
keyword = st.text_input("输入关键词")
一个表格:
st.dataframe(df)
没有模板文件,没有前后端分离,没有接口联调。 你写 Python,就能把交互页做出来。
这个效率在做内部工具时非常值钱。
我现在只要做筛选条件,十有八九丢侧边栏。因为人眼习惯就是左边选,右边看。
比如做个日志分析页面:
import streamlit as st
import pandas as pd
st.title("日志分析工具")
log_level = st.sidebar.multiselect(
"选择日志级别",
["INFO", "WARN", "ERROR"],
default=["ERROR"]
)
keyword = st.sidebar.text_input("关键字过滤")
limit = st.sidebar.slider("展示条数", 10, 500, 100)
df = pd.read_csv("app.log.csv")
if log_level:
df = df[df["level"].isin(log_level)]
if keyword:
df = df[df["message"].str.contains(keyword, case=False, na=False)]
st.write(f"当前展示 {min(len(df), limit)} 条日志")
st.dataframe(df.head(limit), use_container_width=True)
这一类页面,运维、测试、开发都能直接拿来用。 而且你会发现,很多原本要反复 grep、awk、Excel 筛半天的活,页面一套就顺了。
做数据页面时,我一般会先防这几个坑
Streamlit 上手很快,但也别把它当魔法。
1)别把重逻辑反复跑
很多人第一次写,习惯是页面上每改一个参数,整个脚本从头执行一遍。 然后数据一大,页面就卡得要死。
这不是它有问题,是你没做缓存。
比如读取大文件、请求接口、加载模型,这些都该缓存:
import streamlit as st
import pandas as pd
@st.cache_data
defload_data(path: str) -> pd.DataFrame:
return pd.read_csv(path)
df = load_data("big_sales.csv")
st.dataframe(df.head())
如果是模型、数据库连接这类资源对象,通常用:
@st.cache_resource
defload_model():
# 比如加载一个很重的模型
return"mock_model"
我排这种慢页面,第一眼先看是不是每次点击都把大文件重新读了一遍。这个地方非常常见。
2)上传文件别想当然
业务传上来的 Excel、CSV,字段名经常不老实。
你以为叫 order_date,人家给你来个 订单日期。 你以为金额列都是数字,结果里面混了空字符串和 -。
所以我一般不会直接拿上传结果往下算,而是先做字段校验和类型清洗。
defnormalize_columns(df: pd.DataFrame) -> pd.DataFrame:
df.columns = [str(col).strip().lower() for col in df.columns]
return df
defvalidate_columns(df: pd.DataFrame, required_cols: list[str]) -> None:
missing = [col for col in required_cols if col notin df.columns]
if missing:
raise ValueError(f"缺少字段: {missing}")
然后页面里接住异常:
try:
df = pd.read_csv(uploaded_file)
df = normalize_columns(df)
validate_columns(df, ["order_date", "city", "amount"])
except Exception as e:
st.error(f"文件解析失败: {e}")
st.stop()
别小看这几行。没有这个,页面看着是活的,实际一碰就死。
3)图表先够用,不要一上来追求炫
内部工具不是比赛。 很多页面上来就要地图、联动、动效、渐变色,我一般第一反应是不太信。
先看问题是不是非得这么复杂。 大多数场景,折线图、柱状图、表格,足够了。
Streamlit 自带就能出一些简单图:
st.line_chart(trend_df.set_index("order_date")["amount"])
或者你自己接 matplotlib、plotly 也行。
但早期版本,我更建议朴素点。因为页面是拿来解决问题的,不是拿来参加年会汇报的。
做一个接口调试页,也很顺
除了数据展示,Streamlit 还有个很常见的活:做接口测试小后台。
比如给运营或测试一个页面,填参数,点一下,请求接口,看结果。
import streamlit as st
import requests
import json
st.title("接口调试页")
url = st.text_input("接口地址", "http://127.0.0.1:8000/predict")
user_id = st.text_input("用户ID", "10001")
content = st.text_area("请求内容", "这是一段待分析的文本")
if st.button("发送请求"):
payload = {
"user_id": user_id,
"content": content
}
try:
resp = requests.post(url, json=payload, timeout=10)
st.write("状态码:", resp.status_code)
try:
st.json(resp.json())
except Exception:
st.code(resp.text)
except Exception as e:
st.error(f"请求失败: {e}")
这种东西在联调阶段很顶用。 你不用再让别人拿 Postman 配环境、设 Header、导 JSON。能点开网页,就能试。
和 AI 结合,Streamlit 更顺手
这两年 Streamlit 最常见的另一个用途,就是包 AI demo。
你本地有个模型服务,或者调一个大模型接口,页面上加几个输入框、一个按钮、一个输出区域,立马就是个能演示的产品。
像这样:
import streamlit as st
import requests
st.title("文本摘要工具")
text = st.text_area("输入原文", height=200)
if st.button("生成摘要"):
ifnot text.strip():
st.warning("先输入点内容。")
st.stop()
with st.spinner("模型处理中..."):
resp = requests.post(
"http://127.0.0.1:8000/summary",
json={"text": text},
timeout=30
)
result = resp.json()
st.subheader("摘要结果")
st.write(result["summary"])
这个阶段你就会明白,为什么很多 AI 原型都爱拿 Streamlit 起手。 因为它不是最重的,也不是最花的,但“把能力摆到页面上”这个动作,它干得很利索。
部署这块,也比很多人想的轻
本地开发跑起来之后,最常见的部署方式无非几种:
一个非常朴素的启动方式:
streamlit run app.py --server.port 8501 --server.address 0.0.0.0
如果你要 Docker 化,也不复杂:
FROM python:3.11-slim
WORKDIR /app
COPY . /app
RUN pip install -r requirements.txt
EXPOSE8501
CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
requirements.txt 列上依赖就行:
streamlit
pandas
matplotlib
requests
这里有个经验话我顺手提一句: 别把所有实验依赖都塞进去,镜像会臃肿得很难看。尤其你本地装过一堆 notebook、爬虫、可视化包,真没必要全带上。
它适合什么,不适合什么
我对技术选型一直不太爱说“万能”。
Streamlit 很适合:
但你要说用它做一个复杂权限系统、多角色后台、重交互业务平台,那我通常不建议。
因为它的长处本来就不是那套。
别让一个擅长“快”的工具,去扛一个擅长“重”的需求。 这跟你拿 shell 脚本凑配置中心差不多,前面觉得自己聪明,后面大概率补债。
最后给一个我更常用的项目骨架
真到要落地时,我一般不会把所有代码全塞一个 app.py。 早期能这么干,后面一多就乱。
我更喜欢这样拆:
streamlit-demo/
├── app.py
├── requirements.txt
├── services/
│ ├── data_loader.py
│ └── api_client.py
├── utils/
│ ├── cleaner.py
│ └── validator.py
└── data/
└── demo.csv
app.py 只管页面流程:
import streamlit as st
from services.data_loader import load_csv
from utils.validator import validate_sales_df
st.title("销售分析工具")
uploaded_file = st.file_uploader("上传文件", type=["csv"])
if uploaded_file:
df = load_csv(uploaded_file)
validate_sales_df(df)
st.dataframe(df, use_container_width=True)
services/data_loader.py 管读取:
import pandas as pd
defload_csv(file) -> pd.DataFrame:
return pd.read_csv(file)
utils/validator.py 管校验:
defvalidate_sales_df(df):
required = ["order_date", "city", "amount"]
missing = [col for col in required if col notin df.columns]
if missing:
raise ValueError(f"缺少字段: {missing}")
这样后面页面再加东西,不至于改一处炸三处。
我对 Streamlit 的判断就一句
它不是拿来替代所有 Web 框架的。
它更像是 Python 程序员手里一把特别顺手的扳手: 不用造页面体系,不用拉前端会战,先把数据和功能摆出来,先解决问题。
很多时候,一个能上传、能筛选、能画图、能导出的页面,比一份写得很优雅但没人会用的脚本值钱得多。
页面先跑起来,后面很多事才有资格谈。