基于Python編寫windows電腦用戶操作記錄查看器
軟件功能
讀取系統(tǒng)現(xiàn)有的日志記錄:
Windows系統(tǒng)事件日志
最近訪問的文件記錄
程序安裝和執(zhí)行記錄
刷新日志、搜索記錄、刪除選中記錄
軟件截圖

核心源碼
import tkinter as tk
from tkinter import ttk, messagebox
import os
import winreg
from datetime import datetime
from win32com.shell import shell, shellcon
import win32api
import win32con
import win32evtlog
import win32evtlogutil
import win32file
import struct
from ctypes import *
import sys
import ctypes
def is_admin():
"""檢查是否具有管理員權(quán)限"""
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
class PCActivityMonitor:
def __init__(self, root):
self.root = root
self.root.title("用戶操作記錄查看器")
self.root.geometry("1200x800")
# 檢查管理員權(quán)限
if not is_admin():
messagebox.showwarning("警告",
"程序未以管理員權(quán)限運(yùn)行,某些功能可能受限。\n"
"建議以管理員身份重新運(yùn)行程序以獲取完整功能。")
# 創(chuàng)建主框架
self.main_frame = ttk.Frame(root)
self.main_frame.pack(expand=True, fill="both", padx=5, pady=5)
# 創(chuàng)建工具欄
self.create_toolbar()
# 創(chuàng)建Treeview
self.create_treeview()
# 初始加載數(shù)據(jù)
self.load_all_logs()
def create_toolbar(self):
"""創(chuàng)建工具欄"""
toolbar = ttk.Frame(self.main_frame)
toolbar.pack(fill="x", padx=5, pady=5)
# 刷新按鈕
ttk.Button(toolbar, text="刷新", command=self.load_all_logs).pack(side="left", padx=5)
# 刪除按鈕
ttk.Button(toolbar, text="刪除選中記錄", command=self.delete_selected).pack(side="left", padx=5)
# 導(dǎo)出按鈕
ttk.Button(toolbar, text="導(dǎo)出記錄", command=self.export_logs).pack(side="left", padx=5)
# 搜索框
ttk.Label(toolbar, text="搜索:").pack(side="left", padx=5)
self.search_var = tk.StringVar()
ttk.Entry(toolbar, textvariable=self.search_var, width=30).pack(side="left", padx=5)
ttk.Button(toolbar, text="搜索", command=self.search_logs).pack(side="left", padx=5)
ttk.Button(toolbar, text="清除搜索", command=self.clear_search).pack(side="left", padx=5)
def create_treeview(self):
"""創(chuàng)建Treeview控件"""
frame = ttk.Frame(self.main_frame)
frame.pack(expand=True, fill="both")
# 創(chuàng)建Treeview和滾動(dòng)條
self.tree = ttk.Treeview(frame, columns=("time", "action", "object", "path"), show="headings")
# 設(shè)置列標(biāo)題
self.tree.heading("time", text="操作時(shí)間", command=lambda: self.treeview_sort_column("time", False))
self.tree.heading("action", text="操作類型", command=lambda: self.treeview_sort_column("action", False))
self.tree.heading("object", text="操作對(duì)象", command=lambda: self.treeview_sort_column("object", False))
self.tree.heading("path", text="完整路徑", command=lambda: self.treeview_sort_column("path", False))
# 設(shè)置列寬
self.tree.column("time", width=150)
self.tree.column("action", width=100)
self.tree.column("object", width=250)
self.tree.column("path", width=400)
# 添加滾動(dòng)條
y_scrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview)
x_scrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview)
self.tree.configure(yscrollcommand=y_scrollbar.set, xscrollcommand=x_scrollbar.set)
# 布局
self.tree.pack(side="left", fill="both", expand=True)
y_scrollbar.pack(side="right", fill="y")
x_scrollbar.pack(side="bottom", fill="x")
# 綁定右鍵菜單
self.tree.bind("<Button-3>", self.show_context_menu)
# 綁定雙擊事件
self.tree.bind("<Double-1>", self.on_double_click)
def load_all_logs(self):
"""加載所有日志記錄"""
self.tree.delete(*self.tree.get_children())
# 創(chuàng)建臨時(shí)列表存儲(chǔ)所有記錄
all_records = []
# 獲取文件訪問記錄
all_records.extend(self.get_file_access_logs())
# 獲取程序執(zhí)行記錄
all_records.extend(self.get_program_execution_logs())
# 獲取應(yīng)用程序日志
all_records.extend(self.get_application_logs())
# 按時(shí)間倒序排序
all_records.sort(key=lambda x: x[0], reverse=True)
# 插入排序后的記錄
for record in all_records:
self.tree.insert("", "end", values=record)
def get_file_access_logs(self):
"""獲取文件訪問記錄"""
records = []
try:
# 讀取最近訪問文件
recent_path = os.path.join(os.environ['USERPROFILE'],
'AppData\\Roaming\\Microsoft\\Windows\\Recent')
for file in os.listdir(recent_path):
if file.endswith('.lnk'):
file_path = os.path.join(recent_path, file)
access_time = datetime.fromtimestamp(os.path.getatime(file_path))
try:
shortcut = shell.SHCreateItemFromParsingName(
file_path, None, shell.IID_IShellItem
)
target_path = shortcut.GetDisplayName(shellcon.SIGDN_FILESYSPATH)
records.append((
access_time.strftime("%Y-%m-%d %H:%M:%S"),
"文件訪問",
file[:-4],
target_path
))
except:
continue
# 讀取Office文檔記錄
office_path = os.path.join(os.environ['APPDATA'],
'Microsoft\\Office\\Recent')
if os.path.exists(office_path):
for file in os.listdir(office_path):
if file.endswith('.lnk'):
file_path = os.path.join(office_path, file)
access_time = datetime.fromtimestamp(os.path.getatime(file_path))
records.append((
access_time.strftime("%Y-%m-%d %H:%M:%S"),
"Office文檔",
file[:-4],
file_path
))
except Exception as e:
messagebox.showerror("錯(cuò)誤", f"讀取文件訪問記錄出錯(cuò): {str(e)}")
return records
def get_program_execution_logs(self):
"""獲取程序執(zhí)行記錄"""
records = []
try:
key_paths = [
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"),
(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall")
]
for hkey, key_path in key_paths:
try:
with winreg.OpenKey(hkey, key_path, 0,
winreg.KEY_READ | winreg.KEY_WOW64_64KEY) as key:
for i in range(winreg.QueryInfoKey(key)[0]):
try:
subkey_name = winreg.EnumKey(key, i)
with winreg.OpenKey(key, subkey_name) as subkey:
try:
display_name = winreg.QueryValueEx(subkey, "DisplayName")[0]
install_date = winreg.QueryValueEx(subkey, "InstallDate")[0]
install_location = winreg.QueryValueEx(subkey, "InstallLocation")[0]
date_obj = datetime.strptime(install_date, "%Y%m%d")
records.append((
date_obj.strftime("%Y-%m-%d %H:%M:%S"),
"軟件安裝",
display_name,
install_location
))
except:
continue
except:
continue
except:
continue
except Exception as e:
messagebox.showerror("錯(cuò)誤", f"讀取程序執(zhí)行記錄出錯(cuò): {str(e)}")
return records
def get_application_logs(self):
"""獲取應(yīng)用程序日志"""
records = []
try:
hand = win32evtlog.OpenEventLog(None, "Application")
flags = win32evtlog.EVENTLOG_BACKWARDS_READ | win32evtlog.EVENTLOG_SEQUENTIAL_READ
events = win32evtlog.ReadEventLog(hand, flags, 0)
for event in events:
if event.EventType in [win32evtlog.EVENTLOG_AUDIT_SUCCESS]:
records.append((
event.TimeGenerated.Format(),
"程序執(zhí)行",
event.SourceName,
"用戶操作記錄"
))
except:
pass
return records
def load_file_system_journal(self):
"""讀取文件系統(tǒng)日志"""
if not is_admin():
print("需要管理員權(quán)限才能讀取文件系統(tǒng)日志")
return
try:
drives = self.get_system_drives()
for drive in drives:
try:
# 檢查驅(qū)動(dòng)器類型
drive_type = win32file.GetDriveType(drive + "\\")
if drive_type != win32file.DRIVE_FIXED:
continue # 跳過非固定磁盤
# 嘗試啟用USN日志
self.enable_usn_journal(drive)
# 讀取USN日志
self.read_usn_journal(drive)
except Exception as e:
print(f"處理驅(qū)動(dòng)器 {drive} 時(shí)出錯(cuò): {str(e)}")
continue
except Exception as e:
print(f"讀取文件系統(tǒng)日志出錯(cuò): {str(e)}")
def get_system_drives(self):
"""獲取系統(tǒng)所有驅(qū)動(dòng)器"""
drives = []
bitmask = win32api.GetLogicalDrives()
for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
if bitmask & 1:
drives.append(f'{letter}:')
bitmask >>= 1
return drives
def read_usn_journal(self, drive):
"""讀取USN日志"""
try:
# 打開驅(qū)動(dòng)器
handle = win32file.CreateFile(
f"\\\\.\\{drive}",
win32con.GENERIC_READ | win32con.GENERIC_WRITE,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
None,
win32con.OPEN_EXISTING,
0,
None
)
# 獲取USN日志信息
if handle == win32file.INVALID_HANDLE_VALUE:
return
# 定義USN日志數(shù)據(jù)結(jié)構(gòu)
class USN_JOURNAL_DATA(Structure):
_fields_ = [
("UsnJournalID", c_ulonglong),
("FirstUsn", c_ulonglong),
("NextUsn", c_ulonglong),
("LowestValidUsn", c_ulonglong),
("MaxUsn", c_ulonglong),
("MaximumSize", c_ulonglong),
("AllocationDelta", c_ulonglong),
]
# 獲取USN日志信息
buf = win32file.DeviceIoControl(
handle,
win32file.FSCTL_QUERY_USN_JOURNAL,
None,
sizeof(USN_JOURNAL_DATA),
None
)
journal_data = USN_JOURNAL_DATA()
memmove(byref(journal_data), buf, sizeof(journal_data))
# 讀取最近的USN記錄
read_data = struct.pack("QQI", journal_data.FirstUsn, journal_data.NextUsn, 0)
data = win32file.DeviceIoControl(
handle,
win32file.FSCTL_READ_USN_JOURNAL,
read_data,
8192,
None
)
# 解析USN記錄
usn_records = self.parse_usn_records(data)
# 添加到界面
for record in usn_records:
if record['reason'] & win32file.USN_REASON_FILE_DELETE:
self.tree.insert("", 0, values=(
record['time'].strftime("%Y-%m-%d %H:%M:%S"),
"文件刪除",
record['filename'],
f"{drive}\\{record['filepath']}"
))
win32file.CloseHandle(handle)
except Exception as e:
print(f"讀取USN日志出錯(cuò) {drive}: {str(e)}")
def parse_usn_records(self, data):
"""解析USN記錄"""
records = []
offset = 0
while offset < len(data):
# 解析記錄頭
record_length = struct.unpack_from("I", data, offset)[0]
if record_length == 0:
break
# 解析文件名
filename_offset = struct.unpack_from("H", data, offset + 56)[0]
filename_length = struct.unpack_from("H", data, offset + 58)[0]
filename = data[offset + filename_offset:offset + filename_offset + filename_length].decode('utf-16')
# 獲取時(shí)間戳
timestamp = struct.unpack_from("Q", data, offset + 48)[0]
time = datetime.fromtimestamp((timestamp - 116444736000000000) // 10000000)
# 獲取原因
reason = struct.unpack_from("I", data, offset + 40)[0]
# 獲取文件引用號(hào)
file_ref = struct.unpack_from("Q", data, offset + 8)[0]
records.append({
'filename': filename,
'filepath': self.get_file_path(file_ref),
'time': time,
'reason': reason
})
offset += record_length
return records
def get_file_path(self, file_ref):
"""獲取文件路徑"""
try:
return "Unknown Path" # 實(shí)際實(shí)現(xiàn)需要更復(fù)雜的邏輯
except:
return "Unknown Path"
def treeview_sort_column(self, col, reverse):
"""排序表格列"""
l = [(self.tree.set(k, col), k) for k in self.tree.get_children('')]
l.sort(reverse=reverse)
# 重新排列項(xiàng)目
for index, (val, k) in enumerate(l):
self.tree.move(k, '', index)
# 切換排序方向
self.tree.heading(col, command=lambda: self.treeview_sort_column(col, not reverse))
def on_double_click(self, event):
"""雙擊打開文件或目錄"""
item = self.tree.selection()[0]
path = self.tree.item(item)['values'][3]
try:
os.startfile(path)
except:
pass
def export_logs(self):
"""導(dǎo)出日志記錄"""
try:
with open('user_activities.csv', 'w', encoding='utf-8') as f:
# 寫入表頭
f.write("操作時(shí)間,操作類型,操作對(duì)象,完整路徑\n")
# 寫入數(shù)據(jù)
for item in self.tree.get_children():
values = self.tree.item(item)['values']
f.write(f"{values[0]},{values[1]},{values[2]},{values[3]}\n")
messagebox.showinfo("成功", "日志已導(dǎo)出到 user_activities.csv")
except Exception as e:
messagebox.showerror("錯(cuò)誤", f"導(dǎo)出日志失敗: {str(e)}")
def clear_search(self):
"""清除搜索結(jié)果"""
self.search_var.set("")
self.tree.selection_remove(*self.tree.selection())
def delete_selected(self):
"""刪除選中的記錄"""
selected = self.tree.selection()
if not selected:
messagebox.showwarning("警告", "請(qǐng)先選擇要?jiǎng)h除的記錄")
return
if messagebox.askyesno("確認(rèn)", "確定要?jiǎng)h除選中的記錄嗎?"):
for item in selected:
self.tree.delete(item)
def search_logs(self):
"""搜索日志"""
search_text = self.search_var.get().lower()
if not search_text:
return
for item in self.tree.get_children():
values = self.tree.item(item)['values']
if any(search_text in str(value).lower() for value in values):
self.tree.selection_add(item)
else:
self.tree.selection_remove(item)
def show_context_menu(self, event):
"""顯示右鍵菜單"""
menu = tk.Menu(self.root, tearoff=0)
menu.add_command(label="復(fù)制", command=self.copy_selected)
menu.add_command(label="刪除", command=self.delete_selected)
menu.post(event.x_root, event.y_root)
def copy_selected(self):
"""復(fù)制選中的記錄到剪貼板"""
selected = self.tree.selection()
if not selected:
return
text = ""
for item in selected:
values = self.tree.item(item)['values']
text += "\t".join(str(v) for v in values) + "\n"
self.root.clipboard_clear()
self.root.clipboard_append(text)
def enable_usn_journal(self, drive):
"""啟用USN日志"""
try:
handle = win32file.CreateFile(
f"\\\\.\\{drive}",
win32con.GENERIC_READ | win32con.GENERIC_WRITE,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
None,
win32con.OPEN_EXISTING,
0,
None
)
if handle == win32file.INVALID_HANDLE_VALUE:
return False
try:
# 啟用USN日志
win32file.DeviceIoControl(
handle,
win32file.FSCTL_CREATE_USN_JOURNAL,
struct.pack("QQ", 0, 0),
None,
None
)
return True
finally:
win32file.CloseHandle(handle)
except:
return False
def main():
# 如果不是管理員權(quán)限,則請(qǐng)求提升權(quán)限
if not is_admin():
ctypes.windll.shell32.ShellExecuteW(
None, "runas", sys.executable, " ".join(sys.argv), None, 1)
sys.exit()
root = tk.Tk()
app = PCActivityMonitor(root)
root.mainloop()
if __name__ == "__main__":
main() 到此這篇關(guān)于基于Python編寫windows電腦用戶操作記錄查看器的文章就介紹到這了,更多相關(guān)Python電腦操作記錄查看器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python與Java進(jìn)行交互操作的方法與性能對(duì)比
這篇文章主要為大家詳細(xì)介紹了Python與Java進(jìn)行交互操作的相關(guān)方法,包括子進(jìn)程調(diào)用,JPype,Py4J和REST/gRPC,并進(jìn)行了性能對(duì)比,感興趣的小伙伴可以了解下2025-04-04
Python操作Oracle數(shù)據(jù)庫(kù)的簡(jiǎn)單方法和封裝類實(shí)例
這篇文章主要介紹了Python操作Oracle數(shù)據(jù)庫(kù)的簡(jiǎn)單方法和封裝類,結(jié)合實(shí)例形式分析了Python簡(jiǎn)單連接、查詢、關(guān)閉Oracle數(shù)據(jù)庫(kù)基本操作,并給出了一個(gè)Python針對(duì)Oracle各種操作的封裝類,需要的朋友可以參考下2018-05-05
pip?install?jupyterlab失敗的原因問題及探索
在學(xué)習(xí)Yolo模型時(shí),嘗試安裝JupyterLab但遇到錯(cuò)誤,錯(cuò)誤提示缺少Rust和Cargo編譯環(huán)境,因?yàn)閜ywinpty包需要它們來(lái)編譯,由于在conda環(huán)境下操作,Rust和Cargo已經(jīng)安裝,問題是pywinpty包丟失,安裝pywinpty包后,再次執(zhí)行pip?install?jupyterlab即可正常下載2025-02-02
Python輕松實(shí)現(xiàn)查找高亮Excel指定數(shù)據(jù)
在日常工作中,Excel表格無(wú)疑是我們處理數(shù)據(jù)最常用的工具之一,本文將深入探討如何利用 Free Spire.XLS for Python 免費(fèi)庫(kù)完成這一任務(wù),讓你的數(shù)據(jù)處理工作效率倍增,希望對(duì)大家有所幫助2025-11-11
Python實(shí)現(xiàn)自動(dòng)訪問網(wǎng)頁(yè)的例子
今天小編就為大家分享一篇Python實(shí)現(xiàn)自動(dòng)訪問網(wǎng)頁(yè)的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2020-02-02
Django和websocket實(shí)現(xiàn)簡(jiǎn)單的多人聊天的示例代碼
本文主要介紹了使用Django和WebSocket實(shí)現(xiàn)一個(gè)簡(jiǎn)單的多人聊天應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01
Python通過計(jì)算圖片MD5值實(shí)現(xiàn)圖片去重
這篇文章主要為大家詳細(xì)介紹了Python如何通過MD5實(shí)現(xiàn)圖片去重的3種計(jì)算方式并進(jìn)行了對(duì)比,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2026-05-05
Python 的lru_cache裝飾器使用簡(jiǎn)介
這篇文章主要介紹了Python 的lru_cache 裝飾器使用簡(jiǎn)介,幫助大家更好的理解和學(xué)習(xí)使用python,感興趣的朋友可以了解下2021-03-03

