昨天說明了營運指標如何計算,今天說明營運報表如何寄送。客戶的營運主管不見得每天登入看板 (dashboard),許多人需要的是每天早上準時出現在信箱裡的報表。負責產出 PDF 與寄信的,是我們的內部服務 — Pigeon。
在 Pigeon 之前,這份報表經歷過一段以 xlsx 樣板產出的時期。
三年多前,排程寄送報表的功能上線:後端定時計算數字、套用 xlsx 樣板,再由 Amazon SES (Simple Email Service,AWS 的寄信服務) 以附件寄出。
後來多門市報表日漸複雜,欄位排列、儲存格樣式、數值色階等格式設定,大量寫在 Python 程式碼裡。PM 要調整欄位順序或顏色,流程是開單、工程師修改樣板、程式碼審查、部署。報表格式又經常需要微調,每一次都要由工程師處理。
Day 18 介紹的 BI 動態看板遷移到 Superset 後,調整圖表比修改 xlsx 樣板快得多。因此我們決定,信箱裡的報表直接沿用看板的版面。
2024 年 11 月,Pigeon 上線接手這項工作。整體流程如下:瀏覽器自動化工具 Selenium 控制 headless Chrome (不開視窗執行的 Chrome),登入 Superset、套用日期與門市篩選,再把看板轉成圖片。Python 端以 Pillow 將圖片封裝成 PDF、存進 S3,寄送時再由 SES 附上檔案。何時產生、寄給誰,由後端的排程決定。
Pigeon 的 API 與瀏覽器分開部署。API 透過 Selenium Hub (Selenium Grid 中負責分派瀏覽器 session 的節點),把報表的瀏覽器工作分派到 Chromium Node;登入 Superset、套用篩選與載入頁面,都在 Node 裡執行。

直接對看板截圖會遇到一個問題:看板上的表格高度固定,超出的列在表格內捲動。使用者在網頁上可以往下捲,截圖只會取得目前可見的範圍,因此每張表只留下開頭幾列。
所以 Pigeon 截圖前會依看板進行前置處理,例如把表格的顯示列數設為 All。等內容載入後,再找出含有表格的區塊,解除高度、寬度與 overflow 的限制,讓內容完整展開。DOM 操作簡化後大致如下:
document.querySelectorAll(".dragdroppable-row").forEach((el) => {
if (!(el.querySelector("tr") && el.querySelector("td"))) return;
[el, ...el.querySelectorAll("div, table")].forEach((node) => {
node.style.height = "auto";
node.style.width = "auto";
node.style.overflow = "unset";
});
});
.dragdroppable-row 是 Superset 看板中每一列圖表的外層容器,同時含有 tr 與 td 的列才視為表格。
實際產生圖片時,Pigeon 會在頁面中注入 html-to-image,把看板的 DOM 轉成 JPEG,再交給 Pillow。這條路徑輸出的 PDF,內容是一張很長的圖片。
這套做法使用一年多後,遇到了大型報表。
html-to-image 的做法是把 DOM 包進 SVG 的 foreignObject,再轉成圖片。報表超過約一百家店、一千五百個儲存格後,會超過一個沒有文件記載的 foreignObject 尺寸上限,轉圖因此失敗。
為了解決這個問題,2026 年 4 月我們加入 Chrome DevTools Protocol (CDP) 的 Page.printToPDF,直接使用 Chrome 的列印功能產出 PDF,省去把整份看板先轉成 JPEG 的步驟。
列印前仍然要等圖表載入、展開表格,printToPDF 不會自動補上尚未載入的資料。另外,列印樣式會影響顏色,因此我們同時設定 Emulation.setEmulatedMedia 的 { media: "screen" } 與 printBackground: true,保留畫面上的表頭與儲存格底色,並依看板尺寸設定紙張大小。
現在兩條路徑並存:不超過 100 家店的報表走 html-to-image,超過 100 家的走 CDP。兩條路徑最後都產出 PDF,後續的儲存與寄送流程共用。
報表格式移到 Superset 後,Pigeon 負責把看板轉成 PDF:以 Selenium 控制 headless Chrome 登入、套用篩選並展開表格,100 家店以內的報表用 html-to-image 轉圖,更大的報表用 CDP 直接列印。同一份看板同時供網頁與信箱使用,PM 在看板裡調整欄位、顏色與圖表,下一次產出的 PDF 就會跟著更新,不需要為這些調整重新部署 Pigeon,工程師也不必再維護另一份 xlsx 樣板。
我們也開始在內部的 Superset 環境接上 MCP,搭配撰寫好的 skills,讓 AI 協助 PM 調整看板,加快修改的速度。
明天的 Day 23 回到門市,說明如何監控掛在廚房牆上的即時看板螢幕。
<foreignObject>
Page.printToPDF
Emulation.setEmulatedMedia
本系列由 Berry AI 工程團隊出品。更多工程實戰紀錄都在 Berry AI 技術部落格。