拆解 Streamlit 的神秘資料流

前言

最近公司在推行用 Streamlit 來建構專案的 prototype,讓資料科學家可以快速建立可互動的 UI。我自己試玩了一下覺得很不錯,只要寫點 Python code 就能快速生出漂亮的 UI 互動元件,自己刻可能都要花上一點時間。

在做 side project 的過程中,開始對它的架構產生興趣:在前端操作完 UI 元件後,後端是怎麼接收這個事件並做出回應的呢?打開 Debugger Tool 翻遍了所有 request 都沒看到疑似交換資料的 API call。

也沒看到 HTML 有被重新請求,那合理懷疑是走 WebSocket 了。後來問了 ChatGPT 才確認是 WebSocket 搭配 Protobuf 的實作。

眼見為憑,自己還是要動手驗證看看。

拿自己的 Streamlit Side Project 來實驗

快速寫一個 Streamlit 元件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# app.py
import streamlit as st

st.title("詐騙文案分析器")

text = st.text_area("請貼上詐騙文案:")

if st.button("分析詐騙文案"):
if text:
st.write(text)
st.write("分析中...")
print(f"後端待分析的文案:{text}")
else:
st.warning("請先貼上詐騙文案!")

用 Streamlit 啟動:

1
$ streamlit run app.py

可以立即得到一個精美的 Streamlit 網頁,同時打開 Dev Tool 觀測 WebSocket 的建立。

這邊可以看到有一個 HTTP 101 Upgrade 使用 WebSocket 的請求,代表瀏覽器已經和後端的伺服器建立了連線。

接著我們試著輸入一段文字送給後端,並觀察 WebSocket 傳送的訊息,可以得到一個 base64 編碼的二進位檔案。

補充:後來才知道現在瀏覽器已經可以直接查看 WebSocket 的訊息了,以前都不知道。手上版本的 Firefox 沒辦法這樣查看,後來就改成用 Chrome 來實驗。

Decode Protobuf 訊息

去找了目前版本的 Streamlit 原始碼中定義的 schema,自己轉檔成 Python。因為對 Protobuf 不夠熟,試了很久都失敗。

最後直接把 base64 字串貼到線上工具 protobufpal 解出如下圖的 JSON 格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
{
"subMesssage_11": {
"subMesssage_1": {},
"subMesssage_2": {
"subMesssage_1": {
"string_1": "$$ID-b2e0ce08522d671c761a53ba564ac075-None",
"bytes_6": {
"0": 72,
"1": 105,
"2": 32,
"3": 230,
"4": 136,
"5": 145,
"6": 230,
"7": 152,
"8": 175,
"9": 32,
"10": 74,
"11": 111,
"12": 101,
"13": 32,
"14": 232,
"15": 128,
"16": 129,
"17": 229,
"18": 184,
"19": 171,
"20": 229,
"21": 149,
"22": 166,
"23": 239,
"24": 188,
"25": 140,
"26": 230,
"27": 156,
"28": 128,
"29": 232,
"30": 191,
"31": 145,
"32": 233,
"33": 129,
"34": 142,
"35": 231,
"36": 154,
"37": 132,
"38": 229,
"39": 165,
"40": 189,
"41": 229,
"42": 151,
"43": 142,
"44": 239,
"45": 188,
"46": 159
}
}
},
"string_3": "3f41e546893dc64b71aaacad12cad815",
"subMesssage_4": {},
"subMesssage_5": {},
"subMesssage_8": {
"string_1": "Asia/Taipei",
"int_2": 18446744073709551136,
"string_3": "zh-TW",
"string_4": "http://localhost:8501/",
"int_5": 0,
"string_6": "dark"
}
}
}

拿到這串二進位資料後,用 Python 轉回 UTF-8 字串:

就得到當初我輸入進 Streamlit UI 的文字了!

結論

本來是想快速建立一個 Streamlit 專案來體驗看看,順便體驗一下 Cursor 和 Uvicorn 等沒用過的開發工具,順手做了實驗驗證一下 ChatGPT 給的答案。

  • Protobuf 的運作機制還不夠熟,也許以後可以自己實作玩一次。
  • 了解到 WebSocket 搭配 Protobuf 具有幾個優點:
    1. 瀏覽器和後端可以雙向溝通,透過 WebSocket 可以即時把資料送到後端。
    2. 可以從 Dev Tool 看到訊息的大小都是以 B 計算,非常輕量。

拆解 Streamlit 的神秘資料流
https://my-blog.pages.dev/2025-07-17/streamlit-dataflow-experiment/
Author
Kevin
Posted on
July 17, 2025
Licensed under