📊 【開源解析】基于Python+Qt打造智能應用時長統計工具 - 你的數字生活分析師
🌈 個人主頁:創客白澤 - CSDN博客
🔥 系列專欄:🐍《Python開源項目實戰》
💡 熱愛不止于代碼,熱情源自每一個靈感閃現的夜晚。愿以開源之火,點亮前行之路。
🐋 希望大家多多支持,我們一起進步!
👍 🎉如果文章對你有幫助的話,歡迎 點贊 👍🏻 評論 💬 收藏 ?? 加關注+💗分享給更多人哦
🌟 概述:數字時代的時間管理者
在數字化生活日益普及的今天,我們每天與各種應用程序的交互時間越來越長。但你是否真正了解自己的數字生活習慣?哪些應用占用了你最多的時間?時間都花在了哪里?
今天我將介紹一款自主研發的應用時長統計工具,它能夠:
- 實時監控應用使用情況
- 可視化展示時間分配
- 分析每周使用趨勢
- 最小化到系統托盤后臺運行
這款工具采用Python+Qt開發,結合matplotlib實現專業級數據可視化,是程序員和普通用戶都能輕松使用的效率工具。
🛠? 功能全景圖
核心功能矩陣
功能模塊 | 技術實現 | 數據維度 |
---|---|---|
實時監控 | psutil+win32api跨平臺采集 | 秒級精度 |
數據持久化 | JSON序列化+按日期存儲 | 歷史數據可追溯 |
可視化分析 | Matplotlib+Qt5嵌入式圖表 | 多維數據呈現 |
系統托盤 | QSystemTrayIcon | 無感后臺運行 |
周趨勢分析 | 時間序列聚合+堆疊柱狀圖 | 七日對比 |
技術棧亮點
- 應用識別:Windows/MacOS/Linux三平臺兼容
- 性能優化:定時器精準控制1秒采集間隔
- 數據安全:雙重備份機制(實時+每日)
- 交互設計:標簽頁自動刷新+表格高亮交互
🖥? 效果展示
1. 實時數據看板
特點說明:
- 當前運行應用藍色高亮
- 使用時長TOP3金色標識
- 實時刷新排名變化
2. 時長占比分析
智能處理:
- 自動合并<5%的小項為"其他"
- 中心顯示總時長
- 響應式標簽防重疊
3. 周趨勢圖譜
分析維度:
- Top5應用每日對比
- 小時為單位直觀顯示
- 顏色編碼區分應用
🛠? 部署與使用指南
環境準備
# 基礎依賴
pip install pyqt5 psutil matplotlib# 平臺特定依賴
# Windows
pip install pywin32
使用教程
python app_usage_tracker.py
-
最小化到托盤:關閉窗口自動后臺運行
-
數據查看:
- 實時數據頁:查看當前會話統計
- 圖表頁:點擊標簽自動刷新
-
數據存儲:
app_usage_data/
目錄下查看歷史數據- 每日數據自動歸檔
自定義配置
# 修改監控頻率(毫秒)
self.timer.start(1000) # 默認1秒# 修改數據存儲路徑
self.data_dir = "custom_data_path"
🔍 核心代碼解析
1. 應用進程監控引擎
def get_current_app(self):"""跨平臺應用識別核心方法"""try:if platform.system() == "Windows":import win32guiwindow = win32gui.GetForegroundWindow()_, pid = win32gui.GetWindowThreadProcessId(window)return psutil.Process(pid).name()# 其他平臺處理...except Exception as e:print(f"應用識別異常: {e}")return "Unknown"
技術要點:
- Windows使用win32gui獲取前臺窗口
- Linux依賴xdotool工具鏈
- MacOS通過AppKit接口
2. 數據可視化引擎
def update_weekly_chart(self):"""周趨勢圖生成邏輯"""# 1. 數據準備dates = [datetime.now().date() - timedelta(days=i) for i in range(6, -1, -1)]# 2. Top5應用篩選app_total_time = defaultdict(float)for date in dates:daily_data = self.load_daily_data(date.strftime("%Y-%m-%d"))for app, secs in (daily_data or {}).items():app_total_time[app] += secs# 3. 柱狀圖繪制fig, ax = plt.subplots(figsize=(12,7))for i, (app, _) in enumerate(sorted(app_total_time.items(), key=lambda x: x[1], reverse=True)[:5]):daily_usage = [self.load_daily_data(d.strftime("%Y-%m-%d")).get(app, 0)/3600 for d in dates]ax.bar(x + i*width, daily_usage, width, label=app)# 4. 圖表美化...
設計亮點:
- 動態數據聚合
- 自動響應式布局
- 視覺層次分明
3. 系統托盤集成
def init_system_tray(self):"""托盤圖標管理系統"""self.tray_icon = QSystemTrayIcon(self)menu = QMenu()menu.addAction("顯示主界面", self.show)menu.addAction("退出", self.close_app)self.tray_icon.setContextMenu(menu)self.tray_icon.show()
交互設計:
- 右鍵菜單快速操作
- 雙擊恢復窗口
- 氣泡消息提示
📥 源碼下載
import sys
import time
from datetime import datetime, timedelta
import json
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QWidget, QTableWidget, QTableWidgetItem, QSystemTrayIcon, QMenu, QAction, QMessageBox, QTabWidget, QHeaderView,QLabel)
from PyQt5.QtCore import QTimer, Qt
from PyQt5.QtGui import QIcon, QFont, QColor
import psutil
import platform
import os
import numpy as np
from matplotlib import cm
from matplotlib.font_manager import FontProperties# 設置matplotlib支持中文顯示
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # 使用微軟雅黑
plt.rcParams['axes.unicode_minus'] = False # 用來正常顯示負號class AppUsageTracker(QMainWindow):def __init__(self):super().__init__()# 初始化數據self.app_data = {}self.current_app = Noneself.last_update_time = time.time()self.data_dir = "app_usage_data"self.data_file = os.path.join(self.data_dir, "current_usage.json")# 創建數據目錄os.makedirs(self.data_dir, exist_ok=True)# 加載歷史數據self.load_data()# 初始化UIself.init_ui()# 設置系統托盤self.init_system_tray()# 設置定時器self.timer = QTimer()self.timer.timeout.connect(self.update_app_usage)self.timer.start(1000) # 每秒更新一次# 每周數據定時保存self.weekly_save_timer = QTimer()self.weekly_save_timer.timeout.connect(self.save_weekly_data)self.weekly_save_timer.start(3600000) # 每小時檢查一次def init_ui(self):"""初始化用戶界面"""self.setWindowTitle("📊 應用使用時長統計")self.setWindowIcon(QIcon(self.get_emoji_icon("📊")))self.setGeometry(100, 100, 1200, 800) # 增大窗口尺寸# 主布局main_widget = QWidget()self.setCentralWidget(main_widget)layout = QVBoxLayout(main_widget)# 創建標簽頁self.tabs = QTabWidget()self.tabs.currentChanged.connect(self.on_tab_changed)layout.addWidget(self.tabs)# 初始化各標簽頁self.init_realtime_tab()self.init_bar_chart_tab()self.init_pie_chart_tab()self.init_weekly_chart_tab() # 修改為周統計圖表頁# 添加工具欄self.init_toolbar()def on_tab_changed(self, index):"""標簽頁切換時自動刷新圖表"""if index == 1: # 使用時長排行標簽頁self.update_bar_chart()elif index == 2: # 使用時長占比標簽頁self.update_pie_chart()elif index == 3: # 周統計數據標簽頁self.update_weekly_chart()def init_realtime_tab(self):"""初始化實時數據標簽頁"""self.realtime_tab = QWidget()self.tabs.addTab(self.realtime_tab, "?? 實時數據")layout = QVBoxLayout(self.realtime_tab)# 添加標題title_label = QLabel("應用使用時長實時統計")title_label.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))title_label.setAlignment(Qt.AlignCenter)title_label.setStyleSheet("padding: 10px;")layout.addWidget(title_label)# 創建表格self.table = QTableWidget()self.table.setColumnCount(3)self.table.setHorizontalHeaderLabels(["排名", "應用名稱", "使用時長"])# 設置表格樣式self.table.setStyleSheet("QTableWidget { border: 1px solid #e0e0e0; }""QTableWidget::item { padding: 5px; }""QHeaderView::section { background-color: #f0f0f0; padding: 5px; }")self.table.setColumnWidth(0, 60)self.table.setColumnWidth(1, 300)self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)self.table.setEditTriggers(QTableWidget.NoEditTriggers)self.table.setSortingEnabled(False)self.table.setAlternatingRowColors(True)layout.addWidget(self.table)def init_bar_chart_tab(self):"""初始化條形圖標簽頁"""self.bar_chart_tab = QWidget()self.tabs.addTab(self.bar_chart_tab, "📈 使用時長排行")layout = QVBoxLayout(self.bar_chart_tab)# 添加標題title_label = QLabel("應用使用時長排行榜")title_label.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))title_label.setAlignment(Qt.AlignCenter)title_label.setStyleSheet("padding: 10px;")layout.addWidget(title_label)# 創建圖表self.bar_figure = plt.figure(figsize=(10, 6), dpi=100, facecolor='white')self.bar_canvas = FigureCanvas(self.bar_figure)layout.addWidget(self.bar_canvas)def init_pie_chart_tab(self):"""初始化餅圖標簽頁"""self.pie_chart_tab = QWidget()self.tabs.addTab(self.pie_chart_tab, "🍰 使用時長占比")layout = QVBoxLayout(self.pie_chart_tab)# 添加標題title_label = QLabel("應用使用時長占比")title_label.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))title_label.setAlignment(Qt.AlignCenter)title_label.setStyleSheet("padding: 10px;")layout.addWidget(title_label)# 創建圖表self.pie_figure = plt.figure(figsize=(10, 6), dpi=100, facecolor='white')self.pie_canvas = FigureCanvas(self.pie_figure)layout.addWidget(self.pie_canvas)def init_weekly_chart_tab(self):"""初始化周統計圖表標簽頁"""self.weekly_chart_tab = QWidget()self.tabs.addTab(self.weekly_chart_tab, "📅 周使用趨勢")layout = QVBoxLayout(self.weekly_chart_tab)# 添加標題title_label = QLabel("近七日應用使用時長趨勢")title_label.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))title_label.setAlignment(Qt.AlignCenter)title_label.setStyleSheet("padding: 10px;")layout.addWidget(title_label)# 創建圖表self.weekly_figure = plt.figure(figsize=(12, 7), dpi=100, facecolor='white')self.weekly_canvas = FigureCanvas(self.weekly_figure)layout.addWidget(self.weekly_canvas)def init_toolbar(self):"""初始化工具欄"""self.refresh_button = QAction("🔄 刷新圖表", self)self.refresh_button.triggered.connect(self.update_all_charts)self.toolbar = self.addToolBar("工具欄")self.toolbar.addAction(self.refresh_button)def init_system_tray(self):"""初始化系統托盤"""self.tray_icon = QSystemTrayIcon(self)self.tray_icon.setIcon(QIcon(self.get_emoji_icon("??")))tray_menu = QMenu()show_action = QAction("👀 顯示窗口", self)show_action.triggered.connect(self.show)tray_menu.addAction(show_action)exit_action = QAction("🚪 退出", self)exit_action.triggered.connect(self.close_app)tray_menu.addAction(exit_action)self.tray_icon.setContextMenu(tray_menu)self.tray_icon.show()self.tray_icon.activated.connect(self.tray_icon_clicked)def tray_icon_clicked(self, reason):"""托盤圖標點擊事件處理"""if reason == QSystemTrayIcon.DoubleClick:self.show()self.activateWindow()def close_app(self):"""退出應用程序"""self.save_data()self.tray_icon.hide()QApplication.quit()def closeEvent(self, event):"""重寫關閉事件,最小化到托盤"""event.ignore()self.hide()self.tray_icon.showMessage("應用使用時長統計","程序已最小化到系統托盤",QSystemTrayIcon.Information,2000)def get_emoji_icon(self, emoji):"""將emoji轉換為圖標"""return emojidef get_current_app(self):"""獲取當前活動窗口的應用"""try:current_app = "Unknown"if platform.system() == "Windows":import win32guiimport win32processwindow = win32gui.GetForegroundWindow()_, pid = win32process.GetWindowThreadProcessId(window)try:process = psutil.Process(pid)current_app = process.name()current_app = os.path.splitext(current_app)[0]except (psutil.NoSuchProcess, psutil.AccessDenied):current_app = "Unknown"elif platform.system() == "Darwin":from AppKit import NSWorkspacecurrent_app = NSWorkspace.sharedWorkspace().frontmostApplication().localizedName()else: # Linuximport subprocesstry:window_id = subprocess.check_output(["xdotool", "getactivewindow"]).decode().strip()pid = subprocess.check_output(["xdotool", "getwindowpid", window_id]).decode().strip()process = psutil.Process(int(pid))current_app = process.name()current_app = os.path.splitext(current_app)[0]except:current_app = "Unknown"# 清理應用名稱for suffix in ['.exe', '.bin', '.app']:if current_app.endswith(suffix):current_app = current_app[:-len(suffix)]return current_appexcept Exception as e:print(f"獲取應用名稱出錯: {e}")return "Unknown"def update_app_usage(self):"""更新應用使用時長"""current_app = self.get_current_app()current_time = time.time()time_elapsed = current_time - self.last_update_timeif self.current_app is not None:if self.current_app in self.app_data:self.app_data[self.current_app] += time_elapsedelse:self.app_data[self.current_app] = time_elapsedself.current_app = current_appself.last_update_time = current_timeself.update_table()if current_time % 3600 < 1: # 大約每小時保存一次self.save_data()def update_table(self):"""更新表格數據"""sorted_data = sorted(self.app_data.items(), key=lambda x: x[1], reverse=True)self.table.setRowCount(len(sorted_data))for row, (app, seconds) in enumerate(sorted_data):# 排名rank_item = QTableWidgetItem(str(row + 1))rank_item.setTextAlignment(Qt.AlignCenter)# 應用名稱app_item = QTableWidgetItem(app)# 使用時長time_item = QTableWidgetItem(self.format_time(seconds))time_item.setTextAlignment(Qt.AlignRight)# 設置數據for item in [rank_item, app_item, time_item]:item.setData(Qt.UserRole, seconds)self.table.setItem(row, 0, rank_item)self.table.setItem(row, 1, app_item)self.table.setItem(row, 2, time_item)# 高亮顯示當前運行應用if app == self.current_app:for col in range(3):item = self.table.item(row, col)item.setBackground(QColor(220, 240, 255))item.setFont(QFont("Microsoft YaHei", 9, QFont.Bold))# 設置前三名樣式if row < 3:for col in range(3):item = self.table.item(row, col)item.setBackground(QColor(255, 240, 200))item.setFont(QFont("Microsoft YaHei", 9, QFont.Bold))def update_all_charts(self):"""更新所有圖表"""self.update_bar_chart()self.update_pie_chart()self.update_weekly_chart()def update_bar_chart(self):"""更新條形圖"""self.bar_figure.clear()ax = self.bar_figure.add_subplot(111)if not self.app_data:ax.text(0.5, 0.5, "暫無數據", ha='center', va='center', fontsize=12)self.bar_canvas.draw()return# 準備數據sorted_data = sorted(self.app_data.items(), key=lambda x: x[1], reverse=True)apps = [app[:15] + '...' if len(app) > 15 else app for app, _ in sorted_data]times = [secs / 3600 for _, secs in sorted_data]# 創建條形圖colors = cm.viridis(np.linspace(0.2, 0.8, len(apps)))bars = ax.barh(np.arange(len(apps)), times, color=colors, edgecolor='none')# 設置標簽ax.set_yticks(np.arange(len(apps)))ax.set_yticklabels(apps)# 添加數據標簽for bar in bars:width = bar.get_width()ax.text(width, bar.get_y() + bar.get_height()/2,f' {width:.1f}h', va='center', ha='left', fontsize=9)# 美化圖表ax.set_xlabel('使用時長 (小時)', fontsize=11)ax.set_title('應用使用時長統計', fontsize=13, pad=15)ax.spines['top'].set_visible(False)ax.spines['right'].set_visible(False)ax.grid(True, axis='x', color='#e0e0e0', linestyle='--', alpha=0.7)ax.invert_yaxis()self.bar_figure.tight_layout()self.bar_canvas.draw()def update_pie_chart(self):"""更新餅圖"""self.pie_figure.clear()ax = self.pie_figure.add_subplot(111)if not self.app_data:ax.text(0.5, 0.5, "暫無數據", ha='center', va='center', fontsize=12)self.pie_canvas.draw()return# 準備數據sorted_data = sorted(self.app_data.items(), key=lambda x: x[1], reverse=True)total_time = sum(secs for _, secs in sorted_data)app_percentages = [(app, (secs/total_time)*100) for app, secs in sorted_data]# 分離主要應用和其他應用main_apps = [item for item in app_percentages if item[1] >= 5]other_apps = [item for item in app_percentages if item[1] < 5]other_time = sum(secs for _, secs in sorted_data if (secs/total_time)*100 < 5)if other_apps:labels = [app for app, _ in main_apps] + ["其他"]sizes = [secs for _, secs in sorted_data if (secs/total_time)*100 >= 5] + [other_time]else:labels = [app for app, _ in main_apps]sizes = [secs for _, secs in sorted_data]# 限制標簽長度labels = [label[:12] + '...' if len(label) > 12 else label for label in labels]percentages = [(size/total_time)*100 for size in sizes]# 創建餅圖colors = cm.plasma(np.linspace(0.2, 0.8, len(labels)))font = FontProperties(size=9)wedges, texts, autotexts = ax.pie(sizes,labels=labels,colors=colors,autopct=lambda p: f'{p:.1f}%' if p >= 5 else '',startangle=90,wedgeprops={'linewidth': 1, 'edgecolor': 'white'},textprops={'fontproperties': font},pctdistance=0.85,labeldistance=1.05)# 添加中心總時長ax.text(0, 0, f"總時長\n{self.format_time(total_time)}", ha='center', va='center', fontsize=11)# 添加圖例和標題ax.set_title('應用使用時長占比 (≥5%顯示)', fontsize=13, pad=15)legend_labels = [f"{label} ({p:.1f}%)" for label, p in zip(labels, percentages)]ax.legend(wedges, legend_labels,title="應用列表",loc="center left",bbox_to_anchor=(1, 0, 0.5, 1),frameon=False,prop=font)self.pie_figure.tight_layout()self.pie_canvas.draw()def update_weekly_chart(self):"""更新周統計柱狀圖"""self.weekly_figure.clear()ax = self.weekly_figure.add_subplot(111)# 獲取最近7天的日期today = datetime.now().date()dates = [(today - timedelta(days=i)).strftime("%m-%d") for i in range(6, -1, -1)]full_dates = [(today - timedelta(days=i)).strftime("%Y-%m-%d") for i in range(6, -1, -1)]# 收集所有應用名稱all_apps = set()weekly_data = {}for date in full_dates:daily_data = self.load_daily_data(date)if daily_data:weekly_data[date] = daily_dataall_apps.update(daily_data.keys())if not all_apps:ax.text(0.5, 0.5, "暫無周數據", ha='center', va='center', fontsize=12)self.weekly_canvas.draw()return# 選擇前5個最常用的應用app_total_time = {app: 0 for app in all_apps}for date in full_dates:if date in weekly_data:for app, secs in weekly_data[date].items():app_total_time[app] += secstop_apps = sorted(app_total_time.items(), key=lambda x: x[1], reverse=True)[:5]top_apps = [app for app, _ in top_apps]# 準備柱狀圖數據bar_width = 0.15x = np.arange(len(dates))# 創建顏色映射colors = cm.viridis(np.linspace(0.2, 0.8, len(top_apps)))# 繪制每個應用的柱狀圖for i, app in enumerate(top_apps):app_times = []for date in full_dates:if date in weekly_data and app in weekly_data[date]:app_times.append(weekly_data[date][app] / 3600) # 轉換為小時else:app_times.append(0)ax.bar(x + i*bar_width, app_times, bar_width, label=app[:12] + '...' if len(app) > 12 else app,color=colors[i])# 設置x軸標簽ax.set_xticks(x + bar_width * (len(top_apps)-1)/2)ax.set_xticklabels(dates)# 美化圖表ax.set_xlabel('日期', fontsize=11)ax.set_ylabel('使用時長 (小時)', fontsize=11)ax.set_title('近七日應用使用時長趨勢 (Top 5應用)', fontsize=13, pad=15)ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')ax.grid(True, axis='y', color='#e0e0e0', linestyle='--', alpha=0.7)self.weekly_figure.tight_layout()self.weekly_canvas.draw()def format_time(self, seconds):"""將秒數格式化為 HH:MM:SS"""hours = int(seconds // 3600)minutes = int((seconds % 3600) // 60)seconds = int(seconds % 60)return f"{hours:02d}:{minutes:02d}:{seconds:02d}"def load_data(self):"""加載保存的數據"""try:if os.path.exists(self.data_file):with open(self.data_file, "r", encoding='utf-8') as f:self.app_data = {k: float(v) for k, v in json.load(f).items()}except (FileNotFoundError, json.JSONDecodeError, ValueError) as e:print(f"加載數據出錯: {e}")self.app_data = {}def save_data(self):"""保存當前數據"""try:with open(self.data_file, "w", encoding='utf-8') as f:json.dump(self.app_data, f, ensure_ascii=False, indent=2)except Exception as e:print(f"保存數據出錯: {e}")def load_daily_data(self, date):"""加載指定日期的數據"""filename = os.path.join(self.data_dir, f"app_usage_{date}.json")try:if os.path.exists(filename):with open(filename, "r", encoding='utf-8') as f:return json.load(f)except (FileNotFoundError, json.JSONDecodeError) as e:print(f"加載每日數據出錯: {e}")return Nonedef save_weekly_data(self):"""每周數據保存"""today = datetime.now().date().strftime("%Y-%m-%d")filename = os.path.join(self.data_dir, f"app_usage_{today}.json")try:with open(filename, "w", encoding='utf-8') as f:json.dump(self.app_data, f, ensure_ascii=False, indent=2)except Exception as e:print(f"保存每周數據出錯: {e}")self.app_data = {}self.current_app = self.get_current_app()self.last_update_time = time.time()if __name__ == "__main__":app = QApplication(sys.argv)app.setApplicationName("應用使用時長統計")app.setApplicationDisplayName("📊 應用使用時長統計")tracker = AppUsageTracker()tracker.show()sys.exit(app.exec_())
## 🎯 深度優化建議### 性能提升方向1. **數據壓縮**:對長期存儲的JSON數據進行gzip壓縮
2. **增量更新**:改用SQLite替代JSON文件存儲
3. **采樣優化**:動態調整監控頻率(活躍時高頻,閑置時低頻)### 功能擴展### 用戶體驗改進- 添加主題切換功能
- 支持導出CSV/PDF報告
- 增加數據篩選功能## 💡 總結與展望這款應用時長統計工具通過:1. **精準監控**:秒級采集精度
2. **直觀可視化**:專業級圖表呈現
3. **無感運行**:完善的托盤管理
4. **數據持久化**:雙重備份機制實現了對數字生活習慣的全方位分析。特別適合:- 自由職業者時間管理
- 家長監控兒童設備使用
- 程序員分析開發效率**未來演進路線**:- 移動端配套應用
- 瀏覽器插件集成
- AI使用建議功能------> 📌 **版權聲明**:本文代碼采用MIT開源協議,轉載請注明出處。如需商業使用請聯系作者授權。
>
> **互動區**:你的數字生活時間分配合理嗎?歡迎在評論區分享你的使用體驗和改進建議!