每次刷 X 或 Hacker News,總能看到開發(fā)者在爭論 AI 生態(tài)的兩極敘事。一邊堅信我們正處在一個永久的超級周期里,另一邊則警告說,巨額的 GPU 資本開支若沒有成比例的軟件收入做支撐,必將引爆一場類似互聯(lián)網(wǎng)泡沫的崩盤。與其在社交平臺上選邊站,我決定用一個理智的工程師方式解決問題——自己搭一個輕量級、實時的指標儀表盤,用數(shù)據(jù)來追蹤這一切。于是就有了 AI Stock Bubble Index。
這篇文章會完整拆解整個項目的架構決策、客戶端性能優(yōu)化技巧,以及數(shù)據(jù)管道中遭遇的難題——比如通過本地代理爬取 Google News RSS 還不會被限流——以及為什么在面對一個數(shù)據(jù)密集型的工具站點時,最終選擇了完全零后端、純 JAMstack 的路線。
![]()
從一開始,我就給自己定下了一套嚴格的架構紀律。整個技術棧看起來異常簡潔:Google News 與財經(jīng)信息源作為數(shù)據(jù)源頭,經(jīng) Node.js ESM 刮刀配合代理抓取,再通過術語匹配與權重計算,產(chǎn)出 JSON 靜態(tài)資產(chǎn),最終由 React hydration 渲染成交互式儀表盤。每一步都盡量向靜態(tài)資產(chǎn)靠攏,避免任何運行中的服務端邏輯。
第一個真正的技術挑戰(zhàn),是獲取外部信息流而不讓瓶頸或代理故障毀掉數(shù)據(jù)采集。平臺的核心能力之一,就是自動發(fā)現(xiàn)關于 AI 資本開支、估值、營收和模型發(fā)布的相關頭條。如果你曾嘗試用 Node.js 去抓取 Google News 的 RSS(https://news.google.com/rss/search?q=...),就會知道那是個雷區(qū)。尤其是當你用 Node 18 以上的版本,試圖使用環(huán)境變量中的 HTTPS_PROXY 配合原生的 fetch() 方法時,它一定會拋出 UND_ERR_CONNECT_TIMEOUT。
這背后的原因在于,Node 的全局 fetch 是基于 undici 實現(xiàn)的,而 undici 為了嚴格遵循 Web API 規(guī)范,會刻意忽略系統(tǒng)環(huán)境代理設定。要解決這個問題,必須顯式傳入 dispatcher,或者直接在請求邏輯里使用 ProxyAgent 來指定代理。于是我在代碼里明確導入了 node:fs、fast-xml-parser 和 undici,并初始化了一個指向本地代理 127.0.0.1:7890 的 ProxyAgent 實例。在 fetch 調(diào)用中,將這個 agent 作為 dispatcher 傳入,讓 undici 能夠從受限網(wǎng)絡下通過本地隧道完成請求。
這還只是抓取階段的第一步。Google News RSS 本身對請求頻率也有隱式的限制,過快地連續(xù)請求會觸發(fā)驗證碼或直接拒絕連接。為了避免被列入黑名單,我在每次請求之間加入了基于指數(shù)退避的隨機延遲,并為不同的搜索關鍵詞分批拉取。把搜索詞切分成更小的批次,每批之間留出足夠的冷卻時間,這樣既保證了數(shù)據(jù)覆蓋率,又沒有觸發(fā)風控。
第二個挑戰(zhàn),是如何從海量的新聞標題中,提取出真正與“AI 泡沫”相關的那一小部分,而不是把所有提到 AI 的報道都收進來。我在管道里設計了一個術語匹配與權重引擎。首先定義了一套種子關鍵詞,像“資本開支”、“GPU 集群”、“估值泡沫”、“營收不及預期”、“模型變現(xiàn)”等,并為每個詞賦予不同的權重。當一篇文章的標題或摘要中標中多個關鍵詞時,權重會疊加,達到閾值的才被認定是相關信號。為了減少誤報,還加入了一些否定詞表,比如“游戲顯卡”、“AI 繪畫課程”這類明顯不相關的噪聲。
這個權重引擎是整個項目中少數(shù)需要一點“后端智能”的地方,但它依舊被設計成在構建時預計算。整個語料抓取下來之后,在 Node 腳本里跑完匹配和打分,結(jié)果直接寫入靜態(tài) JSON 文件。也就是說,網(wǎng)站上線后,所有數(shù)據(jù)的“思考”過程都已經(jīng)在構建階段完成,用戶訪問時只是一個純粹的靜態(tài)資源服務,沒有任何運行時的計算開銷。
這也直接帶出了第三個核心決策:完全的 JAMstack 與客戶端性能優(yōu)化。為了避免任何后端服務器維護,我把生產(chǎn)出的 JSON 數(shù)據(jù)推到一個 CDN 上,前端使用 React 寫成單頁應用,通過 fetch 直接從 CDN 加載最近一次構建的指數(shù)數(shù)據(jù)。這樣一來,儀表盤的刷新并不依賴實時的服務端查詢,而是靠定時的靜態(tài)構建來驅(qū)動。為此,我設定了一個 GitHub Actions 工作流,每隔幾個鐘頭自動運行一次抓取和權重計算腳本,把新的 JSON 資產(chǎn)部署到 CDN,并觸發(fā)前端頁面的 stale-while-revalidate 策略。
在前端性能上,為了不讓體積龐大的 JSON 堵塞首屏,我做了兩步處理。首先,將指數(shù)數(shù)據(jù)按日期分片,首屏只加載最近七天的聚合結(jié)果,其余的歷史數(shù)據(jù)在用戶主動切換時間范圍時才按需拉取。其次,利用 React 的 hydration 機制確保初始 HTML 是預渲染的,用戶在 JavaScript 完全加載之前就已經(jīng)能看到上一次構建留下的靜態(tài)骨架,幾乎沒有白屏時間。
這樣的設計也并非沒有妥協(xié)。因為完全依賴靜態(tài)構建,數(shù)據(jù)存在最多幾個小時的延遲,但對于追蹤宏觀的資本開支、估值趨勢這類信號來說,幾小時的滯后完全可以接受。相比之下,零數(shù)據(jù)庫、零服務器運維、甚至零托管成本換來的穩(wěn)定與省心,才是這個項目最核心的價值。
回顧整個構建過程,最深刻的教訓可能不是技術細節(jié),而是工程取舍的直覺。當你面對一個看起來需要實時流處理、復雜后端管道的儀表盤需求時,回歸靜態(tài)文件和定時構建常常是更穩(wěn)健的起點。把“推送”換成“拉取”,把“運行時計算”換成“構建時計算”,不僅降低了系統(tǒng)復雜度,也極大地縮小了故障面。對于獨立開發(fā)者或小團隊的數(shù)據(jù)觀察工具來說,這或許是一個被嚴重低估的范式。
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.