一Key打造電腦監(jiān)控小工具)
1. 從“能跑”到“好用”桌面監(jiān)控小工具的真實(shí)痛點(diǎn)用 Cursor 配合 Python PyQt5 寫一個電腦監(jiān)控小工具很多人第一次跑通時都挺興奮窗口彈出來時間在跳CPU 占用率在動。但真把它掛在桌面角落當(dāng)常駐工具用問題就來了。我自己踩過的坑是AI 生成的代碼里硬件數(shù)據(jù)讀取路徑寫死了換臺機(jī)器 CPU 溫度直接顯示 N/A進(jìn)程列表刷新太頻繁界面卡成幻燈片更麻煩的是后面想加一個“AI 解讀當(dāng)前資源占用”的功能每個腳本都要單獨(dú)配一套 Key散落在不同文件里改一次要翻半天。這篇就圍繞這個場景展開用 Cursor 輔助寫 Python PyQt5 桌面監(jiān)控程序把屏幕截圖、進(jìn)程列表、CPU/內(nèi)存/GPU 占用做成可視化面板同時用 TaoToken 統(tǒng)一 Key 和 API 通道接入 AI 能力。所謂“統(tǒng)一 Key”就是不管你有多少個腳本、多少個模型調(diào)用點(diǎn)都走同一個 API 地址和同一套密鑰配置集中在一個config.toml里換模型只改一行。適合已經(jīng)會用 Python 寫點(diǎn)小工具、想讓桌面監(jiān)控更實(shí)用、并且打算把 AI 能力接進(jìn)來的開發(fā)者。下面會給出可復(fù)制的config.toml配置骨架、PyQt5 界面與監(jiān)控腳本代碼以及運(yùn)行驗(yàn)證和報(bào)錯排查步驟。技術(shù)部分占大頭拿 Key 的部分放在前面快速帶過。2. TaoToken 前置統(tǒng)一 Key 與 API 通道怎么準(zhǔn)備TaoToken 在這里扮演的角色是“統(tǒng)一入口”。你的監(jiān)控腳本要調(diào)用 AI 做資源解讀、異常摘要不需要分別去對接不同廠商的 SDK只需要一個 API 地址和一把 Key。官網(wǎng)入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 這個地址不加 UTM 參數(shù)直接用于代碼里。操作路徑很直接進(jìn)控制臺創(chuàng)建 API Key然后把它寫進(jìn)本地配置文件??刂婆_地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite API Keys 管理頁在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。如果你后面要做長期編碼或 Agent 類任務(wù)可以了解 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。注意Key 只放在本地config.toml不要硬編碼進(jìn).py文件更不要提交到 Git。監(jiān)控工具經(jīng)常要分享給同事配置文件分離能避免泄露。配置骨架長這樣放在項(xiàng)目根目錄# config.toml [api] base_url https://taotoken.net/api api_key sk-你的Key model claude-sonnet-4-20250514 timeout 30 [monitor] refresh_interval_ms 1500 process_top_n 8 screenshot_enabled true screenshot_dir ./shots [ui] opacity 0.97 always_on_top true font_size 12讀取配置用 Python 標(biāo)準(zhǔn)庫tomllib3.11或tomliimport tomllib from pathlib import Path def load_config(pathconfig.toml): with open(path, rb) as f: return tomllib.load(f) CFG load_config() API_BASE CFG[api][base_url] API_KEY CFG[api][api_key] MODEL CFG[api][model]這樣你的監(jiān)控腳本、截圖腳本、AI 解讀腳本都從同一個CFG取配置換模型只改config.toml一行。3. 可復(fù)制配置PyQt5 監(jiān)控面板 截圖 進(jìn)程 AI 解讀先裝依賴pip install pyqt5 psutil requests pillow tomli主程序分三塊硬件數(shù)據(jù)采集、界面刷新、AI 解讀。硬件采集用psutil拿 CPU/內(nèi)存GPU 部分用pynvmlNVIDIA或降級為 N/A避免像很多 AI 生成代碼那樣寫死 OpenHardwareMonitor 的 8085 端口導(dǎo)致?lián)Q機(jī)就崩。# monitor_core.py import psutil from datetime import datetime def get_system_snapshot(): cpu_percent psutil.cpu_percent(intervalNone) mem psutil.virtual_memory() disk psutil.disk_usage(/) procs [] for p in sorted(psutil.process_iter([pid, name, cpu_percent, memory_percent]), keylambda x: x.info[cpu_percent] or 0, reverseTrue)[:8]: procs.append({ pid: p.info[pid], name: p.info[name], cpu: round(p.info[cpu_percent] or 0, 1), mem: round(p.info[memory_percent] or 0, 1), }) return { time: datetime.now().strftime(%Y-%m-%d %H:%M:%S), cpu: cpu_percent, mem: mem.percent, disk: disk.percent, procs: procs, }GPU 讀取單獨(dú)封裝讀不到就返回 None界面顯示 N/A不要讓整個程序崩# gpu_reader.py def get_gpu_info(): try: import pynvml pynvml.nvmlInit() handle pynvml.nvmlDeviceGetHandleByIndex(0) util pynvml.nvmlDeviceGetUtilizationRates(handle) mem pynvml.nvmlDeviceGetMemoryInfo(handle) return { gpu_util: util.gpu, gpu_mem: round(mem.used / mem.total * 100, 1), } except Exception: return {gpu_util: None, gpu_mem: None}截圖功能用Pillow的ImageGrab保存到config.toml里配置的目錄# screenshot.py import os from PIL import ImageGrab from datetime import datetime def capture_screen(save_dir./shots): os.makedirs(save_dir, exist_okTrue) path os.path.join(save_dir, fshot_{datetime.now():%Y%m%d_%H%M%S}.png) ImageGrab.grab().save(path) return pathAI 解讀部分走 TaoToken 的 API用requests發(fā)一個 chat completions 請求把當(dāng)前快照拼成 prompt# ai_advisor.py import requests from config_loader import API_BASE, API_KEY, MODEL def explain_snapshot(snapshot): prompt ( f當(dāng)前系統(tǒng)快照CPU {snapshot[cpu]}%內(nèi)存 {snapshot[mem]}% f磁盤 {snapshot[disk]}%。占用最高的進(jìn)程 .join(f{p[name]}(CPU {p[cpu]}%) for p in snapshot[procs][:5]) 。請用三句話說明是否存在異常并給一條優(yōu)化建議。 ) resp requests.post( f{API_BASE}/v1/chat/completions, headers{Authorization: fBearer {API_KEY}, Content-Type: application/json}, json{model: MODEL, messages: [{role: user, content: prompt}], max_tokens: 300}, timeout30, ) resp.raise_for_status() return resp.json()[choices][0][message][content]界面部分用 PyQt5 的QTimer定時刷新把上面的數(shù)據(jù)填進(jìn) QLabel 和 QTableWidget。關(guān)鍵點(diǎn)是刷新邏輯放在QThread或加鎖避免 AI 請求阻塞 UI# main_window.py from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QVBoxLayout, QTableWidget, QTableWidgetItem, QPushButton) from PyQt5.QtCore import QTimer from monitor_core import get_system_snapshot from gpu_reader import get_gpu_info from ai_advisor import explain_snapshot class MonitorWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle(系統(tǒng)監(jiān)控 AI 解讀) self.resize(420, 520) layout QVBoxLayout(self) self.label_time QLabel(--) self.label_cpu QLabel(CPU: --) self.label_mem QLabel(內(nèi)存: --) self.label_gpu QLabel(GPU: --) self.table QTableWidget(0, 4) self.table.setHorizontalHeaderLabels([PID, 進(jìn)程, CPU%, 內(nèi)存%]) self.btn_ai QPushButton(AI 解讀當(dāng)前狀態(tài)) self.label_ai QLabel(等待解讀...) self.label_ai.setWordWrap(True) for w in [self.label_time, self.label_cpu, self.label_mem, self.label_gpu, self.table, self.btn_ai, self.label_ai]: layout.addWidget(w) self.btn_ai.clicked.connect(self.run_ai) self.timer QTimer(self) self.timer.timeout.connect(self.refresh) self.timer.start(1500) self.snapshot {} def refresh(self): self.snapshot get_system_snapshot() gpu get_gpu_info() self.label_time.setText(self.snapshot[time]) self.label_cpu.setText(fCPU: {self.snapshot[cpu]}%) self.label_mem.setText(f內(nèi)存: {self.snapshot[mem]}%) self.label_gpu.setText(fGPU: {gpu[gpu_util] if gpu[gpu_util] is not None else N/A}%) self.table.setRowCount(len(self.snapshot[procs])) for i, p in enumerate(self.snapshot[procs]): for j, v in enumerate([p[pid], p[name], p[cpu], p[mem]]): self.table.setItem(i, j, QTableWidgetItem(str(v))) def run_ai(self): if not self.snapshot: return try: self.label_ai.setText(explain_snapshot(self.snapshot)) except Exception as e: self.label_ai.setText(fAI 調(diào)用失敗: {e}) if __name__ __main__: import sys app QApplication(sys.argv) w MonitorWindow() w.show() sys.exit(app.exec_())這套代碼和很多 AI 直接生成的版本相比最大的區(qū)別是配置外置、GPU 可降級、AI 調(diào)用不阻塞界面。Cursor 生成初版后你可以用自然語言讓它“把 AI 請求放到 QThread 里”它會幫你改。4. 驗(yàn)證請求跑起來看到什么才算成功先驗(yàn)證 API 通道是否通單獨(dú)跑一段最小請求import requests from config_loader import API_BASE, API_KEY, MODEL r requests.post( f{API_BASE}/v1/chat/completions, headers{Authorization: fBearer {API_KEY}}, json{model: MODEL, messages: [{role: user, content: 回復(fù) OK}]}, timeout15, ) print(r.status_code, r.json()[choices][0][message][content])預(yù)期輸出200 OK。如果這里就報(bào) 401說明 Key 寫錯或沒帶Bearer報(bào) 404檢查base_url是不是https://taotoken.net/api不要多加/v1之外的路徑。再跑主程序python main_window.py成功時你會看到時間每秒跳動CPU/內(nèi)存/GPU 數(shù)值刷新進(jìn)程表格按 CPU 排序點(diǎn)“AI 解讀當(dāng)前狀態(tài)”后下方出現(xiàn)一段中文分析。截圖功能可以單獨(dú)測from screenshot import capture_screen print(capture_screen())返回文件路徑且./shots下出現(xiàn) PNG 即成功。想快速對比不同模型對同一快照的解讀可以用模型對話頁面手動試https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 。5. 本篇常見錯排查報(bào)錯一ModuleNotFoundError: No module named tomllibPython 3.10 及以下沒有tomllib。裝tomli并改導(dǎo)入try: import tomllib except ModuleNotFoundError: import tomli as tomllib報(bào)錯二requests.exceptions.ConnectionError或超時先確認(rèn)網(wǎng)絡(luò)能訪問https://taotoken.net/api再檢查config.toml里base_url是否被誤寫成帶/v1/chat/completions的完整路徑。代碼里只寫根地址路徑由請求拼接。報(bào)錯三GPU 一直顯示 N/A沒有 NVIDIA 顯卡或沒裝pynvml時屬正常降級。想顯示 AMD/Intel 核顯需要額外庫建議先保持 N/A不要讓程序崩。Cursor 里可以直接問“幫我把 GPU 讀取改成可選依賴”它會加 try/except。報(bào)錯四界面卡頓、點(diǎn)擊無響應(yīng)AI 請求是同步的放在主線程會阻塞。把explain_snapshot放進(jìn)QThreadfrom PyQt5.QtCore import QThread, pyqtSignal class AIWorker(QThread): done pyqtSignal(str) def __init__(self, snapshot): super().__init__() self.snapshot snapshot def run(self): try: self.done.emit(explain_snapshot(self.snapshot)) except Exception as e: self.done.emit(f失敗: {e})報(bào)錯五401 UnauthorizedKey 失效或復(fù)制時帶了空格。重新在 API Keys 頁面生成https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。接入細(xì)節(jié)可查文檔https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。報(bào)錯六截圖保存失敗ImageGrab在多顯示器或權(quán)限受限環(huán)境可能報(bào)錯。先確認(rèn)screenshot_dir有寫權(quán)限Windows 下避免保存到系統(tǒng)盤根目錄。6. 把統(tǒng)一 Key 用在長期編碼與 Agent 任務(wù)上這套監(jiān)控工具只是起點(diǎn)。當(dāng)你開始用 Cursor 寫更多腳本、做自動化 Agent 時統(tǒng)一 Key 的價值會更明顯所有項(xiàng)目共用一份config.toml換模型、調(diào)超時、加備用通道都只改一處。如果你打算把監(jiān)控?cái)?shù)據(jù)接到長期運(yùn)行的編碼助手或 Agent 流程里可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。Claude Code 相關(guān)接入?yún)⒖糷ttps://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 。實(shí)際用下來最省事的做法是把config.toml放在用戶目錄項(xiàng)目里用軟鏈接或環(huán)境變量指向它這樣多個監(jiān)控腳本、截圖工具、AI 解讀模塊共享同一套配置不用每開一個新項(xiàng)目就重新配一遍 Key。