網路研討會分析器
一個能將原始網路研討會匯出資料轉換為兩種答案的工具:
- 單場研討會 — 誰來了、待了多久、何時離開、他們是誰、他們說了什麼。
- 跨場次分析 — 參與人數是否增長、哪個時段和形式最有效、誰持續回訪、以及哪些讚美和抱怨反覆出現。
此技能是堆疊中立的。它定義了資料契約、驗證規則、計算方式和圖表語義。你可以在任何你使用的 Web 堆疊中實作(Flask/Django/FastAPI + Postgres + 任何繪圖庫;Streamlit;筆記本;BI 工具)。這些說明所提取的參考實作是一個 Flask + PostgreSQL + ApexCharts 應用程式。
建立任何東西之前:導入對話
不要預設 Zoom。不要預設 YouTube。 此工具最大的失敗模式就是硬編碼一個平台的匯出格式。使用者可能在 Teams、GoToWebinar、Livestorm、Demio、Webex、Hopin、Riverside、StreamYard、Crowdcast、BigMarker 或自訂堆疊上舉辦網路研討會,並且可能將重播託管在 Vimeo、Wistia、自己的 CDN 上,或根本沒有託管。
在撰寫程式碼之前執行以下順序:
步驟 1 — 詢問他們使用的平台
以簡短的多選題方式提問:
- 您使用哪個平台舉辦即時網路研討會? (Zoom Webinars / Zoom Meetings / Microsoft Teams / GoToWebinar / Livestorm / Demio / Webex / Hopin / BigMarker / Crowdcast / 其他)
- 會後問卷存放在哪裡? (內建於研討會平台 / Typeform / Google Forms / SurveyMonkey / Hubspot / 無)
- 重播存放在哪裡(如果有的話)? (YouTube / Vimeo / Wistia / 自架 / 需要授權的登陸頁面 / 無重播)
- 註冊資料與參與資料是分開的嗎? (一個合併的匯出檔 / 兩個分開的匯出檔 / 僅有註冊資料 / 僅有參與資料)
步驟 2 — 在設計任何內容之前,要求提供一個真實的匯出檔
直接說:「請上傳來自每個來源(參與者、問卷、重播)的一份真實匯出檔,且個人資料已移除。我會讀取實際的欄位標題而不是猜測,並告訴您您的資料可以支援儀表板的哪些部分。」
堅持這麼做。一個真實的檔案就能消除所有假設。如果使用者無法分享真實資料,請他們貼上標題列以及 2-3 行完全虛構的資料。
步驟 3 — 對上傳的檔案執行隱私檢查
請參閱**隱私檢查**。如果檔案包含未經處理的個人資料,請拒絕該檔案。在收到乾淨的檔案之前,不要繼續進行對應。
步驟 4 — 將他們的欄位對應到正規化綱要
向使用者顯示一個明確的對應表(他們的欄位 → 正規化欄位 → 解鎖的功能),並說明缺少了什麼。請參閱**欄位需求**和 references/platform-exports.md。
步驟 5 — 告訴他們會得到哪些圖表
在建立之前列印一份功能可用性報告:
✅ 您的資料可用的功能
觀看時間分佈、同時觀看者時間線、離開偵測、
參與漏斗、月增長率、新訪客與回訪訪客。
⚠️ 部分可用
人口統計 — 您有「國家」但沒有「職稱」,因此資深度圓餅圖將被隱藏。
❌ 不可用
重播分析 — 無重播匯出檔。在您新增重播資料之前(或透過 CSV 範本手動輸入數據),「重播」分頁將被隱藏。
絕對不要建立一個其輸入資料不存在的圖表。隱藏整個面板,並顯示一行說明何種資料可以啟用它。
隱私檢查(強制)
每個上傳的檔案在解析、儲存、分析之前都必須通過一項資料清理檢查。如果發現個人資料,拒絕整個檔案,指出違規的欄位名稱和行號,並解釋如何清理。絕不部分匯入。絕不無聲地自動匿名化。
什麼是個人資料
| 類別 | 偵測方式 |
|---|---|
| 電子郵件地址 | 任何儲存格中包含 [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,} |
| 電話號碼 | (?:\+?\d[\s\-().]{0,2}){7,15} 且 ≥7 位數字,排除純時間戳/ID |
| 郵寄地址 | 儲存格包含街道類型關鍵詞(`street |
| 郵遞區號 / ZIP | \b\d{5}(-\d{4})?\b 或 [A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2} 且欄位名稱暗示為位置 |
| IP 地址 | IPv4/IPv6 字面值 |
| 政府 / 支付 ID | \b\d{3}-\d{2}-\d{4}\b(類似 SSN 格式),通過 Luhn 檢查的 \b(?:\d[ -]*?){13,16}\b |
| 可疑的欄位名稱 | 標題符合 `email |
規則
- 先掃描標題。 任何名稱符合可疑清單的欄位,即使取樣的儲存格看起來是空的也會被標記 — 今天空的欄位明天可能就會被填入資料。
- 掃描每一列的每個儲存格。 不是取樣。檔案很小(數千列);完整掃描成本很低,而取樣掃描會給出虛假的安全感。
- 自由文字形式的問卷答案也要掃描。 人們會在回饋欄位中輸入「請寄 email 到 [email protected]」。這是最容易被忽略的資料外洩點。
- 發現第一個類別就拒絕,但回報所有類別。 完成掃描並一次呈現所有問題,讓使用者一次性修復檔案。
- 絕不將被拒絕的檔案寫入磁碟或資料庫。 保留在記憶體中,拒絕,然後丟棄。
- 記錄拒絕事件,但不記錄違規的值。 僅記錄欄位名稱和行號 — 絕對不要記錄偵測到的 email/電話本身。
允許的識別碼(這些不會被拒絕)
- 穩定的假名化人員 ID — 一個在檔案間一致的雜湊值、UUID 或順序整數。這是跨研討會分析所必需的(見下文)。
- 國家/地區名稱、組織名稱、職稱、資深度等級。
- 時間戳、持續時間、網路研討會 ID、主題字串。
拒絕訊息範本
❌ 上傳被拒絕 — 在 attendees_march.csv 中偵測到個人資料
欄位 "Email" → 412 個電子郵件地址(第 2–413 行)
欄位 "Contact Number" → 88 個電話號碼(第 2, 5, 9, … 行)
欄位 "What can we improve?" → 自由文字中有 3 個電子郵件地址
(第 47, 112, 288 行)
未匯入任何資料,且檔案已被丟棄。
修正方式:
1. 將 Email 欄位取代為穩定的假名化 ID — 例如
小寫 email 的 SHA-256 雜湊值,截斷為 16 個字元。
對所有檔案使用相同的雜湊方法,以便能跨研討會識別同一人。
2. 完全刪除 Contact Number 欄位 — 此欄位永遠不會使用。
3. 清除自由文字答案中的電子郵件地址,保留評論的其餘部分。
清理後重新上傳。請參閱 references/anonymisation.md 以取得現成的清理腳本。
為什麼穩定的假名化 ID 是不可妥協的
此工具一半的價值在於跨研討會分析:新訪客與回訪訪客、留存同期群、重複註冊者、每人出席率。所有這些都需要在不了解他們是誰的情況下,跨檔案識別同一人。在導入時明確告知使用者 — 如果他們將電子郵件清理為空白或每個檔案隨機產生的數值,則每個跨研討會的圖表都會在無聲中變得毫無意義。
建議:person_id = sha256(lower(strip(email)) + fixed_salt)[:16],並將鹽值儲存在資料集之外。references/anonymisation.md 包含一個可直接使用的腳本。
欄位需求
儀表板運行在一個正規化綱要上。使用者的匯出欄位會被對應到這個綱要上。任何對應無法填補的項目都會導致該功能被隱藏。
正規化實體:webinar(每個場次一行)
| 欄位 | 必要? | 類型 | 解鎖功能 | 典型的來源欄位 |
|---|---|---|---|---|
external_id | 必要 | text, unique | 全部 — 跨參與者/問卷/重播檔案的連接鍵 | Webinar ID, Meeting ID, Event ID, Session ID |
topic | 必要 | text | 所有標籤;重複場次的活動分組 | Topic, Title, Event name |
start_time | 必要 | timestamp | 每個基於時間的圖表;月增長率;一天中的時段;並發時間線 | Actual Start Time, Start, Date |
duration_minutes | 強烈建議 | int | 並發時間線 x 軸;觀看百分比 | Actual Duration, Duration |
num_registrants | 建議 | int | 漏斗、出席率(可由計算行數得出) | # Registrants, Registered |
unique_viewers | 建議 | int | 一天中的時段圖表;觸及人數(可推導) | Unique Viewers, Attendees |
max_concurrent | 選填 | int | 最高同時觀眾人數統計 | Max Concurrent Views |
時區規則: 決定一次儲存的時間戳是 UTC 還是固定的本地時區,將其寫入綱要註解中,並且永遠不要臨時轉換。按月分組直接讀取此欄位。對於針對其他時區的觀眾並在當地午夜之後運行的場次,提供一個手動月份覆蓋機制(請參閱月份覆蓋)。
正規化實體:attendee_session(每人每次加入一行)
一個離開後重新加入的人會產生多行。在匯入時永遠不要進行去重 — 在分析時刻意進行去重。
| 欄位 | 必要? | 類型 | 解鎖功能 | 典型的來源欄位 |
|---|---|---|---|---|
webinar_id | 必要 | FK | 全部 | — |
person_id | 必要(假名化) | text | 新訪客與回訪訪客、留存、重複註冊者、每人去重 | Hashed Email, Attendee ID |
attended | 必要 | bool | 出席/缺席分佈、漏斗、出席率 | Attended (Yes/No) |
is_registrant | 建議 | bool | 缺席同期群;漏斗 | 根據 Registration Time 是否存在推導 |
join_time | 建議 | timestamp | 並發時間線、離開偵測 | Join Time |
leave_time | 建議 | timestamp | 並發時間線、離開偵測 | Leave Time |
minutes | 建議 | int | 觀看時間直方圖、平均觀看時間 | Time in Session (minutes) |
country / region | 選填 | text | 地理圓餅圖、區域時序分析 | Country/Region |
organization | 選填 | text | 帳戶層級檢視 | Organization, Company |
job_title | 選填 | text | 資深度圓餅圖(推導) | Job Title, Title, Role |
source_name | 選填 | text | 獲取管道圓餅圖 | Source Name, UTM source, Referrer |
registration_time | 選填 | timestamp | 註冊曲線分析 | Registration Time |
custom_answers | 選填 | JSON | 每個註冊問題一個圓餅圖 | 任何無法識別的欄位 |
無法識別欄位規則: 參與者檔案中任何不屬於上述已知欄位的欄位,都會被視為註冊問卷調查問題,並以 {question: answer} 的形式儲存在 custom_answers 中。每個此類問題會自動生成自己的圓餅圖。這就是讓此工具能夠在您從未見過的平台上運作的原因。
推導欄位 — seniority: 透過關鍵詞將 job_title 分組為 C-Level / 創辦人 (ceo, cto, cfo, cmo, coo, chief, founder, owner, president)、VP / 總監 (vp, vice president, head of, director)、經理 / 主管 (manager, lead, principal)、資深 IC (senior, sr.)、初階 (junior, jr., intern, associate, assistant),否則為 個人貢獻者。當沒有任何一行有職稱時,完全隱藏資深度圖表。
正規化實體:feedback_response(每個問卷提交一行)
| 欄位 | 必要? | 類型 | 解鎖功能 | 典型的來源欄位 |
|---|---|---|---|---|
webinar_id | 必要 | FK | 將回饋連接到場次 | Meeting/Webinar ID |
person_id | 選填(假名化) | text | 將回饋連結到參與行為 | Hashed Email Address |
submitted_at | 選填 | timestamp | 排序、新近度 | Submitted Date and Time |
score | 建議 | int 1–5 | 滿意度分佈 + 平均數 | 「您覺得…有多有用?」 |
liked_text | 建議 | text | 讚美主題聚類 | 「您喜歡…什麼?」 |
improve_text | 建議 | text | 關注點主題聚類 | 「我們可以如何改進…」 |
other_answers | 選填 | JSON | 其他所有內容,逐字保留 | 其餘問題欄位 |
欄位偵測: 透過對標題進行關鍵詞比對來找到評分/喜歡/改進的欄位(useful/rate/score;like;improve),不區分大小寫,第一個匹配者勝出。向使用者顯示偵測到的對應結果,並讓他們修正 — 問卷問題措辭在各公司都不同。
如果問卷存在於研討會平台之外(Typeform, Google Forms),它將不會攜帶網路研討會 ID。那麼需要要求使用者新增一個隱藏欄位來攜帶研討會 ID,或在上傳時在 UI 中選擇目標研討會。
正規化實體:replay_stats(每個網路研討會一行)
重播是所有資料中平台差異最大的。支援三種匯入路徑,按優先順序排列:
- 連接到重播主機的 API(YouTube Data/Analytics API, Vimeo, Wistia)— 僅在使用者擁有並提供憑證時使用。
- 來自重播主機的原生分析 CSV 匯出檔,對應到正規化欄位。
- 手動 CSV 範本 — 始終可用,始終是備用方案。提供一個可下載的範本,帶有標題列、解釋每個指標的註解區塊以及一個範例行。
| 欄位 | 必要? | 類型 | 解鎖功能 |
|---|---|---|---|
webinar_id | 必要 | 連接鍵 | 所有與重播相關的功能 |
video_id / video_url | 選填 | text | 連結到託管的影片;API 同步 |
views_day1 | 選填 | int | 早期需求圖表 |
views_day7 | 選填 | int | 持續需求圖表 |
total_view_hours | 選填 | numeric | 總重播觀看時間圖表 |
avg_view_duration_sec | 選填 | int | 重播參與度圖表 |
avg_view_percentage | 選填 | numeric | 重播留存率圖表 |
likes, comments, shares, subscribers_gained | 選填 | int | 反應統計 |
每個重播欄位都是選填。對於空值儲存 NULL,永遠不要儲存 0 — 一個未測量的指標和一個測量為零的指標在圖表中看起來截然不同。
月份覆蓋
問題: 一個網路研討會於 4 月 1 日凌晨 00:30 在歐洲舉行,但其主要觀眾位於美國西岸,對他們來說仍是 3 月 31 日晚上。按儲存的 start_time 進行月份分組會將此場次歸入 4 月,即使該場次的商業脈絡(市場推廣活動、季節性)屬於 3 月。
解決方案: 在 webinar 綱要上提供一個選填的 override_month 欄位。
| 欄位 | 類型 | 語意 |
|---|---|---|
override_month | date (YYYY-MM-01) 或 null | 此研討會應被歸入的月份。為 null 時,使用 start_time 推導的月份。 |
終端使用者機制: 在儀表板設定中,顯示一個簡單的清單,列出所有研討會及其推導月份,並附上一個「調整」按鈕。按下的使用者會看到一個日期選取器,設定為目前推導的月份,並帶有一個明確的「重置為預設」按鈕。override_month 與研討會記錄一起儲存,並影響所有按月分組的圖表。
(註:原始檔案結尾處有截斷,此處忠於原文。)