Python技术迷

Python + Steamlit 快速开发可视化 web 页面!

浏览器里能不能先把东西跑出来,我一般一眼就看这件事。很多 Python 项目不是死在算法,也不是死在接口,死在“给别人看”这一步:命令行能跑,日志也有,结果老板、产品、测试一打开电脑,全体失去战斗力。

这种时候我基本不太想上来就搭 Django、Flask 再补前端。页面没做出来之前,讨论再多都虚。要的是先把数据、筛选、图表、上传下载这几件事按住,能点,能看,能改。Streamlit 干的就是这个活。

我第一次用 Streamlit,不是奔着“学新框架”去的。

是因为手里有个 Python 脚本,跑出来一堆 CSV,业务同事天天问三句话:

  • 这个数据能不能筛一下?
  • 这个结果能不能画个图?
  • 这个文件能不能传上去直接看?

问到第三次,我就知道命令行这条路已经走不通了。你再发 .py 文件给别人,人家也不会陪你 pip install。这事得换个壳。

Streamlit 最大的好处,不是多强,而是省事。 它有点像什么?像你把一个 Python 脚本,硬生生拍成一个可交互页面。按钮、表格、图表、上传文件、侧边栏参数,基本都现成。尤其适合下面这几类活:

  • 数据分析结果展示
  • 内部工具后台
  • 小型报表页面
  • AI demo 页面
  • 文件处理工具
  • 临时运营看板

说白了,凡是“先出来一个能用的页面再说”的活,它都合适。

先来最小安装:

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 文件。")

这个页面没什么花活,但很实用。 你会发现它已经具备一个内部工具该有的基本骨架了。

我自己做这类页面时,第一版通常就这几个东西:

  1. 文件入口
  2. 参数区
  3. 表格结果
  4. 图表
  5. 导出按钮

先把闭环跑通。别一开始就纠结样式,那个阶段大多没意义。

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 起手。 因为它不是最重的,也不是最花的,但“把能力摆到页面上”这个动作,它干得很利索。

部署这块,也比很多人想的轻

本地开发跑起来之后,最常见的部署方式无非几种:

  • 直接丢服务器跑
  • 用 Docker 包一下
  • 挂到内网机器上
  • 放云平台

一个非常朴素的启动方式:

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 很适合:

  • 内部小工具
  • 数据分析展示
  • 原型验证
  • AI demo
  • 轻量后台
  • 文件处理页面

但你要说用它做一个复杂权限系统、多角色后台、重交互业务平台,那我通常不建议。

因为它的长处本来就不是那套。

别让一个擅长“快”的工具,去扛一个擅长“重”的需求。 这跟你拿 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 程序员手里一把特别顺手的扳手: 不用造页面体系,不用拉前端会战,先把数据和功能摆出来,先解决问题。

很多时候,一个能上传、能筛选、能画图、能导出的页面,比一份写得很优雅但没人会用的脚本值钱得多。

页面先跑起来,后面很多事才有资格谈。