工程资源 / 使用 ClickHouse 和 Streamlit 在 Python 中构建仪表板

使用 ClickHouse 和 Streamlit 在 Python 中构建仪表板

让我们面对现实 - 如果您正在处理数据,Python 很可能就是您的首选语言。它拥有您需要的所有工具:用于数据整理的 Pandas、用于闪电般快速处理的 Polars、用于数值计算的 NumPy,以及当您想要使用机器学习时使用的 scikit-learn。

但问题是 - 一旦您完成了所有出色的分析,您需要一种与他人分享它的方法。老实说,发送 Jupyter 笔记本并不是最优雅的解决方案。您真正需要的是一个人们可以实际使用的简洁仪表板。

这就是 Python 仪表板框架的用武之地。如今,您不需要前端技能来创建令人印象深刻的东西。低代码框架(如 Gradio、Streamlit、Dash 等)使将您的 Python 脚本转换为交互式仪表板或 数据应用变得容易,而无需大费周章。

在本指南中,我们将使用 Python 构建一个仪表板,该仪表板将 ClickHouse 的处理能力与 Streamlit 的用户友好界面相结合。虽然有很多很棒的 可视化库 - 如 Matplotlib、Seaborn、Bokeh 和 Altair - 但我们将使用 plot.ly 来绘制图表,因为它与 Streamlit 配合得非常好,并为我们提供了开箱即用的交互式可视化效果。无论您是刚开始使用 Python 仪表板还是正在寻找新的想法,我们都将逐步完成所有步骤,创建一个看起来很专业但不需要计算机科学学位就能构建的东西。

当我们完成时,您将拥有自己的自定义 dashboard.py,您可以向同事展示或用作您下一个项目的起点。

为了激发您的兴趣,这就是我们将要构建的内容

Dashboard image 1

数据介绍:探索 Bluesky 的社交网络

现在,每个好的仪表板都需要有趣的数据来可视化。虽然您可以导入自己的数据集,但我们将跳过数据准备的麻烦,直接进入有趣的部分 - 构建我们的仪表板。我们将使用来自社交媒体最新参与者之一的引人入胜的真实世界数据集:Bluesky。

如果您还没有听说过它,Bluesky 就像 X(以前的 Twitter),但有一个转折 - 它是完全开源和去中心化的。对于我们这些数据人员来说,特别有趣的是,它提供了对实时事件(如帖子和互动)的免费访问。这意味着我们可以分析实际的社交媒体数据,因为它在网络中流动。

我们已经为您完成了繁重的工作,从 Bluesky 的 Jetstream API 中获取数据并将其加载到 ClickHouse SQL playground 中。如果您对我们如何处理和构建此数据感到好奇,您可以查看我们在 “使用 ClickHouse 为 Bluesky JSON 数据构建 Medallion 架构” 中的详细演练。

准备好开始了吗?首先,让我们为您设置数据。您需要下载 ClickHouse并连接到 playground

1clickhouse client -m \
2  -h sql-clickhouse.clickhouse.com \
3  -u demo -d bluesky \
4  --secure

我们可以运行以下命令来获取表列表

1SHOW TABLES 
2WHERE engine != 'MaterializedView';
    ┌─name──────────────────────────────┐
 1. │ bluesky                           │
 2. │ bluesky_dedup                     │
 3. │ bluesky_dlq                       │
 4. │ bluesky_queue                     │
 5. │ bluesky_raw                       │
 6. │ bluesky_raw_v2                    │
 7. │ cid_to_text                       │
 8. │ displayName_per_user              │
 9. │ displayName_per_user_dict         │
10. │ events_per_hour_of_day            │
11. │ handle_per_user                   │
12. │ handle_per_user_dict              │
13. │ latest_partition                  │
14. │ likes_per_post                    │
15. │ likes_per_post_about_clickhouse   │
16. │ likes_per_user                    │
17. │ posts_per_language                │
18. │ reposts_per_post                  │
19. │ reposts_per_post_about_clickhouse │
20. │ reposts_per_user                  │
21. │ test_100                          │
22. │ test_100_2                        │
23. │ top_post_types                    │
    └───────────────────────────────────┘

bluesky 表包含所有消息,因此让我们编写一个查询来返回记录总数

1select count() AS messages
2FROM bluesky;
   ┌───messages─┐
1.1109828120-- 1.11 billion
   └────────────┘

在本文的其余部分,我们将使用其中几个表,因此让我们熟悉一下数据。

1SELECT name, type
2FROM system.columns
3WHERE (database = 'bluesky') AND (`table` = 'bluesky')
4FORMAT Vertical;
Row 1:
──────
name: data
type: JSON(max_dynamic_paths=100, SKIP `commit.record.reply.root.record`, SKIP `commit.record.value.value`)

Row 2:
──────
name: kind
type: LowCardinality(String)

Row 3:
──────
name: bluesky_ts
type: DateTime64(6)

Row 4:
──────
name: _rmt_partition_id
type: LowCardinality(String)

此表存储来自 BlueSky API 的原始 JSON。我们可以通过运行以下查询来查看单个记录

1SELECT *
2FROM bluesky
3LIMIT 1
4FORMAT Vertical
Row 1:
──────
data:              {"account":{"active":true,"did":"did:plc:mlodkcskzk6q7off7pnys6c3","seq":"2182925349","time":"2024-12-23T14:00:02.518Z"},"did":"did:plc:mlodkcskzk6q7off7pnys6c3","kind":"account","time_us":"1734962402817482"}
kind:              account
bluesky_ts:        2024-12-23 14:00:02.518000
_rmt_partition_id: 1734962400

有几种类型的消息,由 kind 属性指示。

   ┌─kind─────┬────count()─┐
1. │ account  │    21919882. │ identity │    19469953. │ commit   │ 1158651060 │
   └──────────┴────────────┘

让我们再看一下一个表 - events_per_hour_of_day,它完全符合名称的含义!它具有以下字段

Row 1:
──────
name: event
type: LowCardinality(String)

Row 2:
──────
name: hour_of_day
type: UInt8

Row 3:
──────
name: count
type: SimpleAggregateFunction(sum, UInt64)

我们可以通过编写以下查询来计算一天中第 12 小时生成的不同事件的数量

1SELECT event, sum(count)
2FROM events_per_hour_of_day
3WHERE hour_of_day = 12
4GROUP BY ALL
    ┌─event─────────┬─sum(count)─┐
 1. │ like          │   199789032. │ generator     │       17713. │ block         │     5847224. │ profile       │      909415. │ directMessage │         306. │ threadgate    │      220297. │ post          │    38717988. │ postgate      │      116869. │ listitem      │     17536010. │ repost        │    248791511. │ listblock     │       734612. │ follow        │   1125372313. │ starterpack   │       111314. │ ref           │          715. │ list          │       344416. │ direct        │         1017. │ share         │         10 │
    └───────────────┴────────────┘

使用 Streamlit 创建您的第一个 Python 仪表板

现在我们了解了我们的数据源,是时候开始有趣的部分了 - 构建我们的 Python 仪表板!我们将从简单的开始,并逐步添加更多功能。我们的仪表板最终将可视化 Bluesky 的社交数据,但首先,让我们将基本结构就位。

我们将创建两个文件:dashboard.pyqueries.py。这种分离有助于保持我们的代码井井有条 - dashboard.py 将处理可视化和界面组件,而 queries.py 将存储我们的查询(我们稍后会添加这些查询)。

让我们从 dashboard.py 中的最小设置开始

1import streamlit as st
2
3st.set_page_config(layout="wide")
4st.title("Python BlueSky dashboard with ClickHouse and Streamlit")

目前,我们将 queries.py 留空 - 一旦我们让基本仪表板结构工作,我们将使用我们的 ClickHouse 查询填充它。为了查看我们的仪表板的实际效果,我们将使用 uv 包管理器启动 Streamlit,根据我的经验,它比 pip 提供更快的包安装和更好的依赖管理

1uv run \
2--with streamlit \
3streamlit run dashboard.py
  You can now view your Streamlit app in your browser.

  Local URL: http://127.0.0.1:8501
  Network URL: http://192.168.86.23:8501
  External URL: http://82.35.72.115:8501

Web 浏览器将在 https://127.0.0.1:8501 打开一个页面,显示您的新仪表板。

Dashboard image 1

现在它非常简陋,但别担心 - 我们即将让它变得更加有趣!

我们将使用 clickhouse-connect 查询 ClickHouse,因此我们将终止之前运行的 Streamlit 命令,并将 clickhouse-connect 添加为依赖项,如下面的命令所示

接下来,我们需要设置与 ClickHouse 的连接。为此,我们将使用 clickhouse-connect,Python 的 ClickHouse 官方客户端库。

让我们更新我们的开发环境以包含此软件包。首先,停止当前正在运行的 Streamlit 服务器(在终端中按 Ctrl/Cmd+C)。然后,运行以下命令以使用安装的两个软件包重新启动 Streamlit

1uv run \
2--with clickhouse-connect \
3--with streamlit \
4streamlit run dashboard.py --server.headless True

关于命令的简要说明:--server.headless True 标志阻止 Streamlit 打开新的浏览器选项卡(因为我们之前已经打开了一个)。

现在,让我们通过添加一些关于 Bluesky 活动的实时指标,使我们的仪表板更加有趣。我们将创建一个简单但信息丰富的面板,显示平台上正在生成的事件数量,包括总数和最近时间段内的数量。

首先,让我们在 queries.py 中设置我们的查询

1all_messages = """
2SELECT count() AS messages
3FROM bluesky.bluesky
4"""
5
6last_24_hours = """
7SELECT
8    countIf(bluesky_ts > (now() - ((24 * 60) * 60))) AS last24Hours,
9    countIf(
10      (bluesky_ts <= (now() - ((24 * 60) * 60))) AND 
11      (bluesky_ts > (now() - ((48 * 60) * 60)))
12    ) AS previous24Hours
13FROM bluesky.bluesky
14"""

第一个查询很简单 - 它只是计算我们 Bluesky 数据集中的所有事件。但第二个查询才是事情变得有趣的地方。让我们分解一下

  • countIf(bluesky_ts > (now() - ((24 * 60) * 60))) 计算过去 24 小时内的事件
    • now() 为我们提供当前时间戳
    • 我们减去 24 小时的秒数 (24 * 60 * 60 = 86400 秒)
    • countIf 仅计算条件为真的行
  • 第二个 countIf 查看前一个 24 小时期间
    • 它计算 24 到 48 小时前的事件
    • 这为我们提供了用于计算趋势的比较期

通过比较这两个时期,我们可以看到活动是增加还是减少。Streamlit 将自动处理此比较的视觉呈现,并在我们的仪表板中使用向上或向下箭头。

然后让我们回到 dashboard.py 并在文件顶部添加以下导入

1import clickhouse_connect

接下来,我们将建立与 ClickHouse playground 的连接。这使我们可以只读访问 Bluesky 数据集

1client = clickhouse_connect.get_client(
2  host='sql-clickhouse.clickhouse.com', 
3  username='demo',
4  secure=True
5)

现在是激动人心的部分 - 让我们为我们的仪表板添加一些指标!我们将创建一个部分,显示平台总活动和近期趋势

1st.markdown("## How much are people using it?")
2st.markdown("How many messages have been generated so far?")
3
4all_messages = client.query_df(queries.all_messages)
5last_24_hours_messages = client.query_df(queries.last_24_hours)  
6
7left, right = st.columns(2)
8with left:
9  st.metric(label="Total events", value=f"{all_messages['messages'][0]:,}")
10
11with right:
12  delta = (
13    int(last_24_hours_messages['last24Hours'][0])-
14    int(last_24_hours_messages['previous24Hours'][0]
15  )
16  st.metric(
17    label="Events in the last 24 hours", 
18    value=f"{last_24_hours_messages['last24Hours'][0]:,}",
19    delta=f"{delta):,}"
20  )

此代码并排创建两个指标卡

  • 在左侧,我们显示有史以来记录的事件总数
  • 在右侧,我们显示过去 24 小时内的事件,以及显示与前一个 24 小时期间变化量的增量

增量指示器将自动显示绿色表示增加,红色表示减少,从而为我们提供关于平台增长趋势的即时视觉提示。

当您刷新浏览器时,您将看到您的仪表板已经通过真实数据 оживает!让我们看看我们创建了什么

Dashboard image 3

我们的仪表板现在显示两个关键指标

  • 事件总数:Bluesky 上已记录超过 11 亿个事件!这让我们了解了平台的整体规模。
  • 24 小时活动:我们可以看到过去一天大约有 5400 万个事件,红色箭头表示与前 24 小时相比,事件减少了大约 330 万个。

这只是我们的第一个可视化,但它已经告诉我们一个关于平台活动的有趣故事。时间段之间的比较有助于我们了解参与度是在增长还是在下降。

现在我们知道有多少人在使用 BlueSky,让我们深入了解他们最活跃的时间。我们将并排创建两个可视化效果,告诉我们故事的不同部分:每日模式和长期趋势。

首先,让我们将这些查询添加到我们的 queries.py 文件中

1time_of_day = """
2SELECT event, hour_of_day, sum(count) as count
3FROM bluesky.events_per_hour_of_day
4WHERE event in ['post', 'repost', 'like']
5GROUP BY event, hour_of_day
6ORDER BY hour_of_day
7"""
8
9events_by_day = """
10SELECT
11    toStartOfDay(bluesky_ts)::Date AS day, 
12    count() AS count
13FROM bluesky.bluesky
14GROUP BY ALL
15ORDER BY day ASC
16"""

这些查询将帮助我们理解

  • 不同类型参与(帖子、转发和点赞)的每小时活动模式
  • 每日事件总数,以发现随时间变化的趋势

现在让我们更新我们的仪表板,以使用 plot.ly 创建一些交互式可视化效果。

1st.markdown("## When do people use BlueSky?")
2st.markdown("What's the most popular time for people to like, post, and re-post?")
3
4left, right = st.columns(2)
5
6with left:
7  df = client.query_df(queries.time_of_day)
8  fig = px.bar(df, 
9    x="hour_of_day", y="count", color="event", 
10    labels={
11        "hour_of_day": "Hour of Day", 
12        "count": "Event Count", 
13        "event": "Event Type"
14    },
15    color_discrete_map={"like": "light blue", "post": "red", "repost": "green"}
16  )
17  fig.update_layout(
18      legend=dict(
19          orientation="h",
20          yanchor="bottom",
21          y=1.1,
22          xanchor="center",
23          x=0.5
24      )
25  )
26  st.plotly_chart(fig)
27
28with right:
29  df = client.query_df(queries.events_by_day)
30  st.plotly_chart(px.bar(df, 
31    x="day", y="count", 
32    labels={"day": "Day", "count": "Event Count"},
33  ))

让我们分解一下我们正在创建的内容

  1. 每小时活动图表(左侧)
    • 显示人们在一天中最活跃的时间
    • 按类型(帖子、转发和点赞)细分活动
    • 使用不同的颜色来区分活动类型
    • 顶部的水平图例,以提高可读性
  2. 每日趋势图表(右侧)
    • 显示每天的总活动
    • 帮助识别整体增长趋势
    • 揭示任何每周模式或特殊事件

Dashboard image 4

让我们分解一下这些可视化效果揭示的内容

每小时活动模式(左图)

  • 高峰活动发生在晚上(大约 15-22 时)
  • 凌晨(2-5 时)显示活动量最低
  • 点赞(蓝色)占互动的大部分
  • 帖子(红色)和转发(绿色)遵循类似的模式,但数量较少
  • 有一个明显的“唤醒”期,活动开始加速

每日趋势(右图)

  • 我们可以看到过去几周的活动水平
  • 每天的波动很大
  • 该平台在 1 月 5 日左右达到活动高峰
  • 最近几天显示相对稳定的参与度水平

其他示例和总结

代码存储库包含我们本指南中未涵盖的更多可视化效果和功能 - 包括帖子类型、最受欢迎的用户和按语言划分的帖子。虽然我们不会单独介绍这些示例,但您可以在代码中探索它们并根据自己的需求进行调整。

我们学到了什么

通过本指南,您学习了如何

  • 使用 Streamlit 从头开始构建交互式 Python 仪表板
  • 连接和查询来自 ClickHouse 的实时数据
  • 使用 plot.ly 创建引人入胜的可视化效果
  • 以直观的方式显示指标和趋势
  • 构建您的代码以实现可维护性(将查询与可视化逻辑分离)

dashboard.py 示例仅演示了将 Python、ClickHouse 和 Streamlit 结合使用时可能实现的功能的一小部分。您可以进一步扩展它,方法是

  • 添加用户过滤器和交互式控件
  • 创建更复杂的可视化效果
  • 实施实时更新
  • 添加身份验证
  • 将您的仪表板部署到生产环境

无论您是为社交媒体分析、业务指标还是任何其他数据集构建数据可视化工具,模式都保持不变:连接到您的数据源,编写清晰的查询,并创建直观的可视化效果来讲述您数据的故事。

准备好构建您自己的 Python 仪表板了吗?Fork 我们的存储库并开始根据您的需求进行自定义。如果您创建了一些有趣的东西,我们很乐意看到它!

分享此资源
关注我们
X imageSlack imageGitHub image
Telegram imageMeetup imageRss image
©2025ClickHouse, Inc. 总部位于加利福尼亚州湾区和荷兰阿姆斯特丹。