python键盘记录工具1.3.0

查看 68|回复 10
作者:那就这样吧duang   
更新1.1.0版本:
1.优化了退出逻辑,关闭的时候可以选择是否最后化托盘
2.优化了计数功能,加了延时器,保证数字的弹跳显示正常
更新1.3.0版本:
1.优化了获取的窗口句柄
2.更新记录逻辑,新增树状表格记录显示
3.新增导出,清空操作按钮
使用DeepSeek辅助制作的
功能:
1.记录当日总的键盘键入数量
2.表格展示不同软件中的键入数量,占比
3.导出数据


keyrecord.png (23.38 KB, 下载次数: 0)
下载附件
2025-8-11 15:30 上传

源码如下:
[Python] 纯文本查看 复制代码import keyboard
import time
from datetime import datetime
import psutil
import json
import os
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from threading import Thread, Event, Lock
from collections import defaultdict
import win32gui
import win32process
import win32event
import win32api
import win32con
import winerror
import sys
import pystray
from PIL import Image, ImageDraw
mutex = None
stop_event = Event()
class WindowsKeyLogger:
    def __init__(self):
        self.total_keystrokes = 0
        self.app_keystrokes = defaultdict(int)
        self.current_app = self.get_active_app()
        self.log_file = "keystroke_log.json"
        self.lock = Lock()
        self.running = False
        self.load_data()
        self.valid_keys = {
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'num 0', 'num 1', 'num 2', 'num 3', 'num 4',
            'num 5', 'num 6', 'num 7', 'num 8', 'num 9',
            'space', 'enter', 'tab', 'backspace', 'delete',
            ',', '.', '/', ';', "'", '[', ']', '\\', '-', '=', '`',
            '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+',
            ':', '"', '{', '}', '|', '~', '', '?'
        }
    def get_active_app(self):
        try:
            window = win32gui.GetForegroundWindow()
            _, pid = win32process.GetWindowThreadProcessId(window)
            process = psutil.Process(pid)
            name = process.name()
            if name == "ApplicationFrameHost.exe":
                child_window = win32gui.GetWindow(window, win32gui.GW_CHILD)
                _, child_pid = win32process.GetWindowThreadProcessId(child_window)
                child_process = psutil.Process(child_pid)
                name = child_process.name()
            name = name.replace(".exe", "").replace(".EXE", "")
            return name.split('.')[0] or "Unknown"
        except Exception:
            return "Unknown"
    def load_data(self):
        if os.path.exists(self.log_file):
            try:
                with open(self.log_file, 'r') as f:
                    data = json.load(f)
                    today = datetime.now().strftime("%Y-%m-%d")
                    if today in data:
                        self.total_keystrokes = data[today]["total"]
                        self.app_keystrokes = defaultdict(int, data[today]["apps"])
            except Exception:
                self.total_keystrokes = 0
                self.app_keystrokes = defaultdict(int)
    def save_data(self):
        today = datetime.now().strftime("%Y-%m-%d")
        data = {}
        if os.path.exists(self.log_file):
            with open(self.log_file, 'r') as f:
                try:
                    data = json.load(f)
                except:
                    data = {}
        data[today] = {
            "total": self.total_keystrokes,
            "apps": dict(self.app_keystrokes)
        }
        with open(self.log_file, 'w') as f:
            json.dump(data, f, indent=4)
    def export_data(self, filepath):
        with self.lock:
            export_dict = {
                "date": datetime.now().strftime("%Y-%m-%d"),
                "total_keystrokes": self.total_keystrokes,
                "app_keystrokes": dict(self.app_keystrokes)
            }
        with open(filepath, 'w', encoding='utf-8') as f:
            json.dump(export_dict, f, indent=4, ensure_ascii=False)
    def is_valid_key(self, event):
        if event.event_type != keyboard.KEY_DOWN:
            return False
        key_name = event.name.lower()
        if key_name in self.valid_keys:
            return True
        if len(key_name) == 1 and key_name.isprintable():
            return True
        return False
    def on_key_event(self, event):
        if not self.running:
            return
        if not self.is_valid_key(event):
            return
        new_app = self.get_active_app()
        with self.lock:
            if new_app != self.current_app:
                self.current_app = new_app
            self.total_keystrokes += 1
            self.app_keystrokes[self.current_app] += 1
    def start_logging(self):
        if not self.running:
            self.running = True
            keyboard.hook(self.on_key_event)
    def stop_logging(self):
        if self.running:
            self.running = False
            keyboard.unhook_all()
            with self.lock:
                self.save_data()
class KeyLoggerGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("Windows键盘敲击记录器")
        self.root.geometry("600x400")
        self.key_logger = WindowsKeyLogger()
        self.create_widgets()
        self.update_ui()
        self.root.protocol("WM_DELETE_WINDOW", self.on_window_close)
        self.icon = None
        self.create_tray_icon()
        self.is_hidden = False
    def create_widgets(self):
        frame = ttk.Frame(self.root, padding=10)
        frame.pack(fill=tk.BOTH, expand=True)
        btn_frame = ttk.Frame(frame)
        btn_frame.pack(fill=tk.X, pady=5)
        self.start_btn = ttk.Button(btn_frame, text="开始记录", command=self.start_logging)
        self.start_btn.pack(side=tk.LEFT, padx=5)
        self.stop_btn = ttk.Button(btn_frame, text="停止记录", command=self.stop_logging, state=tk.DISABLED)
        self.stop_btn.pack(side=tk.LEFT, padx=5)
        self.export_btn = ttk.Button(btn_frame, text="导出统计", command=self.export_stats)
        self.export_btn.pack(side=tk.LEFT, padx=5)
        total_frame = ttk.LabelFrame(frame, text="今日总敲击数")
        total_frame.pack(fill=tk.X, pady=5)
        self.total_label = ttk.Label(total_frame, text="0", font=("Arial", 24))
        self.total_label.pack()
        app_frame = ttk.LabelFrame(frame, text="按应用程序统计")
        app_frame.pack(fill=tk.BOTH, expand=True)
        self.tree = ttk.Treeview(app_frame, columns=("count", "percentage"), show='tree headings')
        self.tree.heading("#0", text="应用程序")
        self.tree.heading("count", text="敲击数")
        self.tree.heading("percentage", text="百分比")
        self.tree.column("#0", width=250, anchor=tk.W)
        self.tree.column("count", width=100, anchor=tk.CENTER)
        self.tree.column("percentage", width=100, anchor=tk.CENTER)
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar = ttk.Scrollbar(app_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    def update_ui(self):
        with self.key_logger.lock:
            total = self.key_logger.total_keystrokes
            apps = dict(self.key_logger.app_keystrokes)
        self.total_label.config(text=str(total))
        self.tree.delete(*self.tree.get_children())
        sorted_apps = sorted(apps.items(), key=lambda x: x[1], reverse=True)
        for app, count in sorted_apps:
            percentage = f"{(count / total) * 100:.1f}%" if total > 0 else "0%"
            self.tree.insert('', tk.END, iid=app, text=app, values=(count, percentage))
        self.root.after(100, self.update_ui)
    def start_logging(self):
        self.key_logger.start_logging()
        self.start_btn.config(state=tk.DISABLED)
        self.stop_btn.config(state=tk.NORMAL)
    def stop_logging(self):
        self.key_logger.stop_logging()
        self.start_btn.config(state=tk.NORMAL)
        self.stop_btn.config(state=tk.DISABLED)
    def export_stats(self):
        filepath = filedialog.asksaveasfilename(
            defaultextension=".json",
            filetypes=[("JSON 文件", "*.json"), ("所有文件", "*.*")],
            title="导出统计数据"
        )
        if filepath:
            try:
                self.key_logger.export_data(filepath)
                messagebox.showinfo("导出成功", f"统计数据已导出至:\n{filepath}")
            except Exception as e:
                messagebox.showerror("导出失败", f"导出统计数据失败:\n{e}")
    def on_window_close(self):
        answer = messagebox.askquestion("退出确认", "关闭窗口时要最小化到托盘吗?选择否则退出程序。", icon='question')
        if answer == 'yes':
            self.hide_window()
        else:
            self.quit_app()
    def hide_window(self):
        if not self.is_hidden:
            self.root.withdraw()
            self.is_hidden = True
    def show_window(self):
        if self.is_hidden:
            self.root.deiconify()
            self.is_hidden = False
    def quit_app(self, icon=None, item=None):
        self.key_logger.stop_logging()
        if self.icon:
            self.icon.stop()
        self.root.destroy()
        sys.exit(0)
    def create_image(self, width, height, color1, color2):
        image = Image.new('RGB', (width, height), color1)
        dc = ImageDraw.Draw(image)
        dc.rectangle(
            (width // 2, 0, width, height // 2),
            fill=color2)
        dc.rectangle(
            (0, height // 2, width // 2, height),
            fill=color2)
        return image
    def create_tray_icon(self):
        image = self.create_image(64, 64, 'black', 'white')
        menu = pystray.Menu(
            pystray.MenuItem('显示/隐藏窗口', self.toggle_window),
            pystray.MenuItem('退出', self.quit_app)
        )
        self.icon = pystray.Icon("keylogger", image, "键盘敲击记录器", menu)
        t = Thread(target=self.icon.run, daemon=True)
        t.start()
    def toggle_window(self, icon, item):
        if self.is_hidden:
            self.show_window()
        else:
            self.hide_window()
def create_mutex():
    global mutex
    mutex = win32event.CreateMutex(None, False, "KeyLoggerUniqueMutexName")
    last_error = win32api.GetLastError()
    if last_error == winerror.ERROR_ALREADY_EXISTS:
        return False
    return True
def main():
    if not create_mutex():
        tk.messagebox.showerror("错误", "程序已在运行!")
        sys.exit(0)
    root = tk.Tk()
    app = KeyLoggerGUI(root)
    root.mainloop()
if __name__ == "__main__":
    main()
成品:
百度云:https://pan.baidu.com/s/18Z17_fAWlkPvgm-yRxOjIw?pwd=xgmb 提取码: xgmb
蓝奏云:https://wwwt.lanzouw.com/iTwoK34fqedg
密码:fj2e

微软, 统计数据

qup   


xxkz 发表于 2025-8-11 15:55
有没有类似剪贴板工具,可以把近期使用频次较高的短语,词语给放进去,随时可以方便调出

推荐clibor
chinawolf2000   


那就这样吧duang 发表于 2025-8-11 15:58
这个得结合输入法了,输入法才能知道你实际上输入了什么内容,我想到的只是记录一下按键

不用结合输入法,监视剪切板即可,只是剪切板它啥东西都可以往里放,比较麻烦些。还有一个思路,就是把内容先输入好在软件里面(列表啥的),要用的时候再把它放到剪切板里,给用户放到其它的地方
那就这样吧duang
OP
  

有什么bug和优化的地方,欢迎大家指正
slkj   

测试下看看
xxkz   

有没有类似剪贴板工具,可以把近期使用频次较高的短语,词语给放进去,随时可以方便调出
那就这样吧duang
OP
  


xxkz 发表于 2025-8-11 15:55
有没有类似剪贴板工具,可以把近期使用频次较高的短语,词语给放进去,随时可以方便调出

这个得结合输入法了,输入法才能知道你实际上输入了什么内容,我想到的只是记录一下按键
Curry1987   

感谢分享 挺有意思的软件 虽然没什么大意义
那就这样吧duang
OP
  


Curry1987 发表于 2025-8-11 16:52
感谢分享 挺有意思的软件 虽然没什么大意义

有意义呀,可以记录上班一天按键的分布,比如摸鱼了一天,那就按键集中在weixin或者qq,不然就是分布在不同的工作软件里
xxkz   


chinawolf2000 发表于 2025-8-11 17:08
不用结合输入法,监视剪切板即可,只是剪切板它啥东西都可以往里放,比较麻烦些。还有一个思路,就是把内 ...

对 就是这个意思
您需要登录后才可以回帖 登录 | 立即注册

返回顶部