社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Streamlit,一个神奇的 Python 库,多页面,数据库、文件操作

程序员老朱 • 4 月前 • 223 次点击  

streamlit 这几年在数据科学圈子里火得很快。不用学前端,不用折腾路由,纯 Python 就能把数据分析脚本变成像模像样的 Web 应用。但真要拿它做点正事 —— 比如搭一个多页面的数据看板,接上数据库,处理用户上传的文件 —— 光看官方文档的入门示例是不够的。

这篇文章是我在实际项目中反复用到的模式总结。不讲花活,只写真正用得上的东西:多页面怎么拆、数据库怎么连、文件怎么读写。每个章节都配了可运行的代码,拿过去改改就能用。

一、多页面应用

单个脚本塞下所有逻辑,调试时翻半天找不着北 —— 这是每个 Streamlit 新手都会踩的坑。好在框架本身提供了两种拆分页面的方式,按需选用就行。

多页面不是什么新概念,但它确实是组织复杂应用最直接的手段:把不同的功能模块分开,用户导航清晰,你自己维护起来也省心。

1.1 使用Session State实现多页面交互

这是最原始也最灵活的做法 —— 页面切换逻辑完全由你自己控制,所有代码堆在一个文件里,靠 st.session_state 记住当前在哪个页面。

「思路很简单:」 拿一个变量(比如 session_state.page)记下当前选中的页面名,侧边栏放个 radio 或 selectbox 当导航用,根据变量值走到对应的渲染函数。

拆开来看就三块:

  • 导航栏:用于切换页面的导航栏,可以是按钮、链接或下拉菜单等形式。

  • 页面内容:不同页面的具体内容和功能模块,可以通过导航栏进行切换。

  • 状态管理:保持和管理不同页面之间的状态数据,确保用户在切换页面时数据不会丢失。

以下示例实现了两个页面的切换 —— 主页和导航页,导航栏放在侧边栏,切换时只改 session_state['page'] 的值就行:

import streamlit as st

HOME_PAGE_TITLE = "主页"
ABOUT_PAGE_TILE = "导航"
def page_home():
    st.title(HOME_PAGE_TITLE + "页面")

def page_about():
    st.title(ABOUT_PAGE_TILE + "页面")

def main():
    # 设置初始页面为Home
    session_state = st.session_state
    if'page'notin session_state:
        session_state['page'] = HOME_PAGE_TITLE

    # 导航栏
    page = st.sidebar.radio('导航', [HOME_PAGE_TITLE, ABOUT_PAGE_TILE])

    if page == HOME_PAGE_TITLE:
        page_home()
    elif page == ABOUT_PAGE_TILE:
        page_about()

if __name__ == '__main__':
    main()


这段代码跑起来是这样的:首次加载时 session_state 里还没有 page 这个键,于是默认切到 "主页";侧边栏渲染一个 radio,用户点哪个选项,page 就变成哪个值,if/elif 分支决定执行哪个页面函数。从头到尾都在一个脚本里完成,没什么黑魔法。

效果:


适合页面不多、页面之间需要频繁传参的场景 —— 函数调用传参比跨文件共享状态直观多了。

优点:

  • 简单易用:不同页面之间传递和保存状态数据变得非常简单。只需使用字典来存储和访问状态数据,无需复杂的配置或额外的库。
  • 跨页面数据共享:整个生命周期中保存和访问特定于会话的变量。多个页面之间共享数据,可以传递复杂的数据结构,如字典、列表等。
  • 可扩展性:应用程序中添加新的页面,并在不同页面之间保持数据的一致性。易于管理和维护,可以随着需求的增长进行扩展和修改。
  • 状态持久化:刷新应用程序或重新运行应用程序时保留状态数据。这对于用户来说非常方便,他们可以在应用程序的不同会话之间保持他们的工作状态。

缺点:

  • 内存消耗:数据存储在应用程序的内存中,当应用程序的状态数据变得庞大时,内存消耗较大。
  • 无持久化方案:数据存储在应用程序的内存中,仅在应用程序运行期间存在。应用程序停止运行或重新启动时数据将丢失。
  • 难以调试:在应用程序的多个页面之间传递和共享数据,当出现问题时,追踪和调试可能会比较困难。特别是在复杂的多页面应用程序中,正确地处理和管理Session State变量可能会变得复杂。

1.2 Streamlit内置多页面方案

从 1.10 版本开始,Streamlit 把多页面做进了框架内部。你只管往 pages/ 目录扔 .py 文件,剩下的 —— 页面发现、导航生成、URL 映射 —— 框架全包了。

路由逻辑零代码,但代价是灵活性受限。后面会详细讲。

my_app/
├── 首页.py              # 入口文件(通常作为首页)"streamlit run" 命令来运行的文件
└── pages/
    ├── 01_页面A.py
    ├── 02_页面B.py
    ├── 03_页面C.py

每个页面对应一个独立的 Python 脚本,页面切换时前端不刷新,体验比传统的后端渲染流畅不少。

「首页」 —— 入口文件,用 st.page_link 给各子页面提供快捷跳转:




    
# 首页.py

import streamlit as st

st.set_page_config(page_title="首页", page_icon="🏠")
st.title("🏠 首页")
st.page_link("pages/01_页面A.py", label="→ 页面 A")
st.page_link("pages/02_页面B.py", label="→ 页面 B")
st.page_link("pages/03_页面C.py", label="→ 页面 C")

「页面 A」 —— 随手生成一组随机数画折线图,展示数据可视化的基本用法。

# 01_页面A.py
import streamlit as st
import pandas as pd
import numpy as np

st.set_page_config(page_title="页面A", page_icon="📊")
st.title("📊 图表展示")
df = pd.DataFrame(np.random.randn(20, 3), columns=["A", "B", "C"])
st.line_chart(df)

「页面 B」 —— 一个输入框加一句问候,几行代码演示交互逻辑。

# 01_页面B.py

import streamlit as st

st.set_page_config(page_title="页面B", page_icon="📋")
st.title("📋 输入组件")
name = st.text_input("你的名字")
if name:
    st.success(f"你好,{name}!")

「页面 C」 —— 一个按钮触发 loading 和气球,展示 Streamlit 自带的 UI 反馈效果。

# 01_页面C.py

import streamlit as st
import time

st.set_page_config(page_title="页面C", page_icon="⚙️")
st.title("⚙️ 状态演示")
if st.button("点我"):
    with st.spinner("加载中..."):
        time.sleep(1)
    st.balloons()

四个文件凑在一起就是一个完整的多页面骨架了,实际用的时候把里面的假数据换成真实查询就行。

效果:

动态路由与分配机制

内置方案有两层:「静态发现」是默认行为,「动态分配」给了你更大的控制权。

1. 静态发现(文件扫描)

启动时框架扫描 pages/ 目录,按文件名规则解析出每个页面的标题和排序:

pages/
├── 01_📊_数据可视化.py   →  排序: 1, 标题: "数据可视化", 图标: 📊
├── 02_📋_数据表格.py     →  排序: 2, 标题: "数据表格",   图标: 📋
└── 03_📝_表单.py         →  排序: 3, 标题: "表单",      图标: 📝

规则不复杂:{序号}_{图标/英文}_{标题}.py,序号管排序,剩余部分去掉下划线就是页面名。

2. 动态分配(st.navigation)

1.36 版本之后,可以用 st.navigation() 和 st.Page 在代码里显式声明页面列表。不用再依赖文件扫描,导航结构完全由你编程控制。

「最大的好处:」 可以根据条件决定显示哪些页面。比如管理员才看得到后台入口,普通用户根本不知道有这个页面存在。

# 首页.py
import streamlit as st

# 定义页面函数(或引用外部模块)
def page_dashboard():
    st.title("仪表盘")
    st.metric("在线用户", 128)

def page_settings():
    st.title("设置")
    st.toggle("深色模式")

def page_admin():
    st.title("管理后台")
    st.write("仅管理员可见")

# ---- 动态构建导航 ----
# 根据用户角色决定哪些页面可用
is_admin = st.session_state.get("is_admin", False)

nav_pages = [
    st.Page(page_dashboard, title="仪表盘", icon="📊"),
    st.Page(page_settings,   title="设置",   icon="⚙️"),
]

# 管理员才显示后台页面
if is_admin:
    nav_pages.append(st.Page(page_admin, title="管理后台", icon="🔐"))

# 通过 st.navigation 注册动态路由
pg = st.navigation(nav_pages, position="sidebar")
pg.run()
3. 引用外部文件作为 Page

st.Page 除了接收函数,也可以直接指向文件路径或外部模块:

import streamlit as st

# 方式一:引用文件路径
data_page = st.Page("pages/01_📊_数据可视化.py", title="数据看板", icon="📊")

# 方式二:引用模块中的函数
from my_pages import analytics, reports

pg = st.navigation([
    data_page,
    st.Page(analytics.main, title="分析", icon="📈"),
    st.Page(reports.main,   title="报表", icon="📑"),
])
pg.run()
4. 路由分配原理

st.navigation() 内部做了四件事:

  1. 「收集」:接收 st.Page 对象列表,每个对象包含目标(函数或文件路径)、标题、图标。
  2. 「渲染导航」:根据 position 参数在侧边栏或顶部渲染导航组件。
  3. 「匹配路由」:解析当前 URL hash,匹配到对应 Page 后执行其函数或脚本。
  4. 「动态更新」:导航列表每次 rerun 都会重建,所以 st.session_state 一变化,菜单立刻跟着变。
# 一个完整的登录/注销路由示例
import streamlit as st

def login():
    st.title("登录")
    user = st.text_input("用户名")
    if st.button("登录"):
        st.session_state.logged_in = True
        st.session_state.username = user
        st.rerun()

def home():
    st.title(f"欢迎, {st.session_state.username}")

def logout():
    st.title("已退出")
    if st.button("重新登录"):
        st.session_state.logged_in = False
        st.rerun()

# ---- 动态路由分配 ----
ifnot st.session_state.get("logged_in"):
    pg = st.navigation([st.Page(login, title="登录", icon="🔑")])
else:
    pg = st.navigation([
        st.Page(home,   title="首页", icon="🏠"),
        st.Page(logout, title="退出", icon="🚪"),
    ])
pg.run()

效果很直接:没登录时只有登录页,登录后变成首页和退出。页面里不需要写任何权限判断,路由层面就拦住了。

5. 静态发现 vs 动态分配对比
特性
静态发现(文件扫描)
动态分配(st.navigation)
页面定义
文件系统自动
代码显式声明
条件性显示
不支持,所有文件可见
支持,按条件构建列表
页面分组
不支持
支持 section 分组
导航位置
仅侧边栏
侧边栏 / 顶部可选
适用场景
简单固定结构
权限控制、动态菜单

优点:

  • 「零代码路由」:只需将 .py 文件放入 pages/ 目录即可自动注册为页面,侧边栏导航自动生成,无需手动编写路由逻辑。添加或删除页面只需操作文件,维护成本极低。

  • 「按需加载,性能更好」:每个页面是独立脚本,Streamlit 仅在用户点击时加载目标页面,不会一次性加载所有页面代码。页面切换无需重新加载前端,速度极快。

  • 「独立配置,互不干扰」:每个页面可以独立调用 st.set_page_config(),拥有自己的标题、icon、布局模式(wide/centered)和侧边栏状态。

  • 「URL 天然隔离」:每个页面对应独立的 URL 路径(如 /首页、/pages/01_页面A),用户可以直接通过 URL 访问特定页面,也方便分享链接。

  • 「降低耦合」:各页面代码位于不同文件,团队协作时可以并行开发不同页面,冲突更少。单个页面出错不会影响其他页面的渲染。

  • 「文件名即文档」:通过编号前缀和文件名就能快速了解应用的页面结构和流程,无需查看代码即可理解导航层级。

缺点:

  • 「跨页面通信隐式且脆弱」:页面之间没有显式的数据传递机制,完全依赖 st.session_state 共享数据。页面 A 写入的 key,页面 B 必须"魔法字符串"般精确匹配,缺乏类型检查和契约约束,重构时容易遗漏。

  • 「无法直接传递参数」:内置方案不支持页面间路由参数(如 /page?id=123)。如果页面 B 需要页面 A 的某个值,只能通过 session_state 中转,无法从 URL 直接获取。

  • 「不支持嵌套页面」:pages/ 目录仅支持一级,不能创建子目录实现层级导航。对于复杂应用(如"系统管理 → 用户管理 → 用户详情"),只能通过数字前缀扁平排列,导航结构不够直观。

  • 「文件名影响运行时行为」:页面标题和排序由文件名决定,修改文件名意味着改变 URL 和导航栏显示。文件名既要兼顾可读性又要控制排序,中英文混用或 emoji 的使用受限于操作系统兼容性。

  • 「无页面级守卫/中间件」:不能在页面加载前插入统一的鉴权、重定向或日志逻辑。如果某个页面需要登录才能访问,只能在每个页面脚本内重复编写校验代码。

  • 「Session State 的"幽灵依赖"」:页面之间通过共享状态产生隐式耦合。排查问题时难以追踪哪个页面在何时修改了某个状态变量,调试跨页面 Bug 需要手动梳理数据流。

  • 「冷启动慢」:虽然单页面加载快,但每个页面是独立的 Python 模块,首次加载仍有解释开销。页面数量多时,尽管不会一次加载,但用户遍历多个页面时的累计等待时间不可忽略。

二、数据库与数据源连接

数据应用离不开数据库。Streamlit 用 st.connection() 一个 API 统一了各类数据源的接入方式,自带连接缓存和 secrets 凭据注入,不用每次查询都重新建立连接。

支持数据库如下:


下面以最常用的 MySQL 为例,先建好测试表:

CREATE DATABASE pets;

USE pets;

CREATE TABLE mytable (
    name varchar(80),
    pet varchar(80)
);

INSERT INTO mytable VALUES ('Mary', 'dog'), ('John', 'cat'), ('Robert', 'bird');

上面建了一个 pets 库和  mytable 表,塞了三条数据进去。

依赖安装:

pip install sqlalchemy
pip install mysqlclient

2.1 st.connection

st.connection 是目前推荐的连接方式,核心思路很简单:「一次定义,全局复用」。框架会自动缓存连接对象,并且优先从 secrets 文件里取配置。

基本用法 —— 指定连接名、类型和数据库 URL,然后就可以执行参数化查询:

import streamlit as st
conn = st.connection("mysql", type="sql", url="mysql://root:123456@localhost:3306/pets")
df = conn.query("SELECT * FROM mytable WHERE pet != :pet", params={"pet": "bird"})
st.dataframe(df)

效果:


换其他数据库也一样,改 URL scheme 就行。st.connection 底层走 SQLAlchemy,所以理论上支持所有它兼容的数据库。下面是 SQLite 和 PostgreSQL 的例子,顺便演示了如何用底层 session 执行写入操作:

import streamlit as st
# SQLite(内置支持,直接用)
conn = st.connection("my_db", type="sql", url="sqlite:///data.db")

# PostgreSQL(需要 pip install psycopg2)
conn = st.connection("pg", type="sql", url="postgresql://user:pass@localhost:5432/mydb")

# 查询
df = conn.query("SELECT * FROM users WHERE age > :min_age", params={"min_age": 18})
st.dataframe(df)

# 写入(使用底层连接)
with conn.session as s:
    s.execute("INSERT INTO logs (msg) VALUES (:msg)", {"msg": "hello"})
    s.commit()

2.2 Secrets 管理

密码和 API Key 写死在代码里是给自己埋雷。Streamlit 的做法是把敏感信息放到 .streamlit/secrets.toml 里,运行时通过 st.secrets 字典读取。更省事的是,st.connection 会自动查找  [connections.xxx] 下的配置并注入,你连手动传参都省了。

「文件位置:」

# 项目根目录下 .streamlit/secrets.toml
# .streamlit/secrets.toml
[connections.mysql]
url = "mysql://root:123456@localhost:3306/pets"

[api_keys]
openai = "sk-xxxxxxxx"


代码里两种读取方式:手动取和自动注入。前者适合临时用一下,后者配合 st.connection 最省事。

import streamlit as st
# 读取连接配置(嵌套结构)
mysql_url = st.secrets["connections"]["mysql"]["url"]

# 读取简单键值
openai_key = st.secrets["api_keys"]["openai"]

# 也可以用点号访问(st.secrets 本质是 TOML 的字典映射)
mysql_url = st.secrets.connections.mysql.url
openai_key = st.secrets.api_keys.openai

# ============================================================
# 方式二:st.connection() 自动从 secrets 读取
# ============================================================

# st.connection 会自动去 secrets.toml 里找 [connections.mysql]
# 无需手动传 url,框架替你读取并注入
conn = st.connection("mysql", type="sql")

# 查询示例
df = conn.query("SELECT * FROM mytable LIMIT 10")

两种方式各有适用场景:手动读取适合需要临时使用配置值的场合,自动注入适合标准化连接管理,代码更简洁。

2.3 自定义连接

内置的 sql 连接能搞定大多数关系型数据库,但 Redis、MongoDB 这类非关系型数据源就得自己动手了。做法不复杂:继承 BaseConnection,实现 _connect() 方法,框架就会自动帮你处理缓存和 secrets 注入。

「步骤一:」 在 secrets.toml 里配好 Redis 的连接信息。_connect() 方法里的 self._secrets 就是这个配置块的字典,直接解包传进去就行。

# .streamlit/secrets.toml
[connections.redis]
host = "localhost"
port = 6379
protocol = 2



    
# redis_connection.py

import streamlit as st
from streamlit.connections import BaseConnection
import redis
import json


class RedisConnection(BaseConnection):
    """Streamlit Redis 连接封装"""

    def _connect(self, **kwargs):
        return redis.Redis(**self._secrets, **kwargs)

    # ---- 字符串操作 ----
    def get(self, key: str) -> str | None:
        val = self._instance.get(key)
        return val.decode() if val elseNone

    def set(self, key: str, value: str, ex: int = None) -> bool:
        return self._instance.set(key, value, ex=ex)

    def delete(self, *keys: str) -> int:
        return self._instance.delete(*keys)

    def exists(self, key: str) -> bool:
        return bool(self._instance.exists(key))

    def expire(self, key: str, seconds: int) -> bool:
        return self._instance.expire(key, seconds)

    def ttl(self, key: str) -> int:
        return self._instance.ttl(key)

    def keys(self, pattern: str = "*") -> list:
        return [k.decode() for k in self._instance.keys(pattern)]

    # ---- 计数器 ----
    def incr(self, key: str, amount: int = 1) -> int:
        return self._instance.incr(key, amount)

    def decr(self, key: str, amount: int = 1) -> int:
        return self._instance.decr(key, amount)

    # ---- JSON 序列化 ----
    def set_json(self, key: str, data: dict, ex: int = None) -> bool:
        return self._instance.set(key, json.dumps(data), ex=ex)

    def get_json(self, key: str)  -> dict | None:
        val = self._instance.get(key)
        return json.loads(val) if val elseNone

    # ---- 列表操作 ----
    def lpush(self, key: str, *values) -> int:
        return self._instance.lpush(key, *values)

    def lrange(self, key: str, start: int = 0, end: int = -1) -> list:
        return [v.decode() for v in self._instance.lrange(key, start, end)]

    def llen(self, key: str) -> int:
        return self._instance.llen(key)

    # ---- 哈希操作 ----
    def hset(self, name: str, key: str, value: str) -> int:
        return self._instance.hset(name, key, value)

    def hget(self, name: str, key: str) -> str | None:
        val = self._instance.hget(name, key)
        return val.decode() if val elseNone

    def hgetall(self, name: str) -> dict:
        return {k.decode(): v.decode() for k, v in self._instance.hgetall(name).items()}

「步骤二:」 导入自定义连接,拿来就用。下面是一个完整的 Redis 可视化管理界面示例 —— 侧边栏搜 key 和新增,主区域用表格展示所有 key 的类型、值预览、大小和 TTL,选中后可以编辑、删除、改 TTL、重命名。

调用示例:

import streamlit as st
from redis_connection import RedisConnection

st.set_page_config(page_title="Redis 管理工具", page_icon="🔴", layout="wide")

conn = st.connection("redis", type=RedisConnection, ttl=0)


def value_preview(key, key_type):
    """返回值的简短预览"""
    if key_type == "string":
        v = conn.get(key) or""
        return v[:50] + "..."if len(v) > 50else v
    elif key_type == "hash":
        returnf"{{...}} {conn._instance.hlen(key)} fields"
    elif key_type == "list":
        returnf"[...] {conn._instance.llen(key)} items"
    return"-"


def key_size(key, key_type):
    """获取 key 大小"""
    if key_type == "string":
        return conn._instance.strlen(key)
    elif key_type == "hash":
        return conn._instance.hlen(key)
    elif key_type == "list":
        return conn._instance.llen(key)
    return0


def ttl_display(key):
    t = conn.ttl(key)
    if t == -1:
        return"永不过期"
    elif t == -2:
        return"已过期"
    returnf"{t}s"


# ---- 主区域 ----
keys = st.session_state.get("keys", conn.keys("*"))

# 构建表格数据
rows = []
for k in sorted(keys)[:200]:  # 限制 200 条防止性能问题
    kt = conn.type(k)
    rows.append({
        "Key": k,
        "类型": kt.upper(),
        "值预览": value_preview(k, kt),
        "大小": key_size(k, kt),
        "TTL": ttl_display(k),
    })

# ---- 表格 ----
edited = st.data_editor(
    rows,
    use_container_width=True,
    hide_index=True,
    column_config={
        "Key": st.column_config.TextColumn("🔑 Key", width="medium"),
        "类型": st.column_config.TextColumn("📦 类型", width="small"),
        "值预览": st.column_config.TextColumn("💬 值预览", width="large"),
        "大小": st.column_config.NumberColumn("📏 大小", width="small"),
        "TTL": st.column_config.TextColumn("⏳ TTL", width="small"),
    },
    disabled=[ "Key", "类型", "值预览", "大小", "TTL"],
    key="data_editor",
)


几个要点:st.data_editor 做表格浏览,侧边栏搜 key 加新增,操作栏集中了编辑、删除、TTL 和重命名。



三、文件操作

Streamlit 内置了文件上传、下载和常见媒体格式的展示组件,覆盖了日常数据应用里的大多数文件处理场景。

3.1 st.file_uploader:文件上传

st.file_uploader 是 Streamlit 最常用的文件输入组件,支持拖拽或点击上传,可限制文件类型和数量。上传后返回 UploadedFile 对象,可直接读取为 bytes 或传递给 Pandas、PIL 等库处理。

import streamlit as st
import pandas as pd

# 单文件上传
uploaded = st.file_uploader("选择文件", type=["csv", "xlsx", "png", "jpg"])

if uploaded isnotNone:
    # 读取为 DataFrame
    if uploaded.name.endswith(".csv"):
        df = pd.read_csv(uploaded)
        st.dataframe(df)
    elif uploaded.name.endswith((".xlsx", ".xls")):
        df = pd.read_excel(uploaded)
        st.dataframe(df)
    else:
        # 图片直接展示
        st.image(uploaded)

# 多文件上传
files = st.file_uploader(
    "批量上传", type=["csv"], accept_multiple_files=True
)
for f in files or []:
    df = pd.read_csv(f)
    st.write(f"**{f.name}** — {len(df)} 行")

效果:

3.2 st.download_button:文件下载

配合 st.download_button,可以将 DataFrame、图片、文本等任意二进制数据提供给用户下载。

需要excel 操作库openpyxl

pip install openpyxl
import streamlit as st
import pandas as pd
from io import BytesIO

# 下载 CSV
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
csv = df.to_csv(index=False).encode("utf-8")
st.download_button("📥 下载 CSV", csv, "data.csv", "text/csv")

# 下载 Excel
buf = BytesIO()
with pd.ExcelWriter(buf, engine="openpyxl") as w:
    df.to_excel(w, index=False)
st.download_button(
    "📥 下载 Excel", buf.getvalue(),
    "data.xlsx",
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)

# 下载图片
with open("image.png", "rb") as f:
    st.download_button("📥 下载图片", f, "image.png", "image/png")

效果:

3.3 st.image:图片展示

st.image 支持从文件路径、PIL Image 对象、bytes 或 URL 加载图片,可调节尺寸和添加标题。




    
import streamlit as st
from PIL import Image

# 方式一:本地文件
image = Image.open("sunrise.jpg")
st.image(image, caption="山间日出", width=400)

# 方式二:URL
st.image("https://streamlit.io/images/brand/streamlit-logo.png", width=200)

# 方式三:上传后直接展示(已在上传章节演示)

3.4 st.audio:音频播放

支持播放本地音频文件和通过代码生成的音频数据。传入 bytes 对象时需指定 format,传入 NumPy 数组时需指定 sample_rate。

import streamlit as st
import numpy as np

# 播放音频文件
with open("music.mp3", "rb") as f:
    st.audio(f.read(), format="audio/mp3")

# 播放生成的音频(440Hz 正弦波,持续 2 秒)
sample_rate = 44100
t = np.linspace(0, 2, 2 * sample_rate, False)
wave = np.sin(440 * t * 2 * np.pi)
st.audio(wave, sample_rate=sample_rate)

3.5 st.video:视频播放

支持本地视频文件和在线视频 URL(如 YouTube)。本地视频需以 bytes 传入。

import streamlit as st

# 播放本地视频
with open("demo.mp4", "rb") as f:
    st.video(f.read())

# 播放在线视频 URL
st.video("https://www.youtube.com/watch?v=your_video_id")

3.6 PDF 文档展示

Streamlit 没有内置 PDF 查看器,但可以通过以下方式实现:

import streamlit as st

# 方式一:提供下载链接
with open("report.pdf", "rb") as f:
    st.download_button("📄 下载 PDF", f, "report.pdf",  "application/pdf")

# 方式二:用 iframe 嵌入在线 PDF
st.markdown(
    '
    'width="100%" height="600">',
    unsafe_allow_html=True
)

# 方式三:用 PyPDF2 读取文本内容展示
from PyPDF2 import PdfReader
uploaded = st.file_uploader("上传 PDF", type="pdf")
if uploaded:
    reader = PdfReader(uploaded)
    for i, page in enumerate(reader.pages):
        st.text(f"第 {i+1} 页:\n{page.extract_text()[:500]}")

3.7 Excel / CSV 读写

与 Pandas 结合,Streamlit 可以轻松实现表格数据的导入导出与分析。

import streamlit as st
import pandas as pd
from io import BytesIO

# ---- 导入 ----
uploaded = st.file_uploader("上传表格", type=["csv", "xlsx", "xls"])
if uploaded:
    df = pd.read_csv(uploaded) if uploaded.name.endswith("csv") else pd.read_excel(uploaded)
    st.dataframe(df)

# ---- 导出 ----
@st.cache_data
def to_excel(df):
    buf = BytesIO()
    with pd.ExcelWriter(buf, engine="openpyxl") as w:
        df.to_excel(w, index=False, sheet_name="Sheet1")
    return buf.getvalue()

df_sample = pd.DataFrame({"Name": ["Alice", "Bob"], "Score": [90, 85]})
st.download_button("📥 导出 Excel", to_excel(df_sample), "output.xlsx")

3.8 远程 URL 文件获取

结合 requests 或 urllib,可以从远程 URL 下载文件并直接在 Streamlit 中展示或处理。

import streamlit as st
import  requests
from PIL import Image

url = st.text_input("输入图片 URL")
if url:
    try:
        resp = requests.get(url, stream=True)
        resp.raise_for_status()

        # 根据 Content-Type 判断处理方式
        content_type = resp.headers.get("Content-Type", "")
        if"image"in content_type:
            st.image(resp.content)
        elif"csv"in content_type:
            import pandas as pd
            from io import StringIO
            df = pd.read_csv(StringIO(resp.text))
            st.dataframe(df)
        else:
            st.write(f"文件大小: {len(resp.content) / 1024:.1f} KB")
    except Exception as e:
        st.error(f"获取失败: {e}")

3.9 文件操作速查表

组件
功能
常用参数
st.file_uploader
上传文件
type
, accept_multiple_files
st.download_button
下载文件
data
, file_name, mime
st.image
展示图片
caption
, width, use_container_width
st.audio
播放音频
format
, sample_rate, start_time
st.video
播放视频
format
, start_time
PdfReader
 (PyPDF2)
读取 PDF
pages
, extract_text()
pd.read_csv/Excel
读取表格
sheet_name
, encoding

结语

这篇文章把 Streamlit 最常用的三块内容串了一遍:多页面怎么组织、数据库怎么连、文件怎么处理。多页面部分对比了手动路由和内置方案各自适合什么场景,数据库部分从 st.connection 讲到 secrets 管理再到自定义连接类,文件部分涵盖了图片、音视频、PDF、表格和远程 URL 的全流程。

这些东西掌握了,用纯 Python 搭一个像样的数据应用基本没啥问题了。剩下的就是动手写,踩几个坑就熟了。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/198130