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

Python鼠标点击录制程序拦截事件且修复后崩溃求助

问题根源与修复方案

我仔细看了你的代码和问题描述,这两个异常都是pyHook的机制和你的代码逻辑冲突导致的,下面给你拆解问题并提供完整的修复方案:

核心问题分析

1. 录制时鼠标点击无响应

你在onclick和onpress回调里返回了0——在pyHook的规则里,返回0表示阻止当前事件传递给系统的后续处理程序。也就是说,你的程序把鼠标点击事件“吞”了,只记录坐标但不给系统发信号,所以任务栏、资源管理器这些自然接收不到你的点击操作。

2. 手动加click后程序彻底卡死

在鼠标钩子的回调函数里调用mouse_event模拟点击,会触发pyHook的鼠标钩子再次捕获这个模拟的点击事件,直接形成无限递归循环:钩子捕获点击→进入回调→模拟点击→钩子再捕获→再进入回调……加上PumpMessages()的消息循环被死死阻塞,进程会彻底锁死,甚至任务管理器都没法正常结束(因为钩子线程是在系统级别运行的)。

分步修复方案

1. 让鼠标事件正常传递给系统

把onclick和onpress函数的返回值从0改成True(或者1),这样pyHook就会把事件继续传递给系统,既不影响你记录坐标,也能正常用鼠标操作其他程序。

2. 避免钩子递归死锁

绝对不要在钩子回调里直接调用模拟输入的API!如果确实需要测试点击(其实录制模式根本不需要),得先临时卸载钩子,执行模拟操作后再重新挂载钩子。不过你的场景里录制只需要记录真实点击,所以这步只要避免在回调里加click就够了。

3. 优化线程退出逻辑

原来用windll.user32.PostQuitMessage(0)终止消息循环的方式不稳定,容易导致钩子残留。改用全局标志来控制线程的消息循环,确保线程能正常退出,钩子被正确卸载。

修复后的完整代码

from keyboard import wait
from threading import Thread
from pythoncom import PumpWaitingMessages
from ctypes import windll
from pyHook import HookManager
from os import listdir, makedirs
from os.path import exists
from win32api import mouse_event, SetCursorPos
from win32con import MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP
from time import sleep

coordsToClick = []
# 全局标志:控制录制线程的运行状态
recording_running = True

def sanitised_input(prompt, type_=None, min_=None, max_=None, range_=None, distinct_=False):
    if min_ is not None and max_ is not None and max_ < min_:
        raise ValueError("min_ must be less than or equal to max_.")
    while True:
        ui = input(prompt)
        if type_ is not None:
            try:
                ui = type_(ui)
            except ValueError:
                print(f"Input type must be {type_.__name__}.")
                continue
        if max_ is not None and ui > max_:
            print(f"Input must be less than or equal to {max_}.")
        elif min_ is not None and ui < min_:
            print(f"Input must be greater than or equal to {min_}.")
        elif range_ is not None and ui not in range_:
            if isinstance(range_, range):
                print(f"Input must be between {range_.start} and {range_.stop}.")
            else:
                if len(range_) == 1:
                    print(f"Input must be {range_[0]}.")
                else:
                    print(f"Input must be {', '.join(map(str, range_[:-1]))} or {range_[-1]}.")
        elif distinct_ and not len(ui) == len(set(ui)):
            print("Input should only contain unique characters!")
        else:
            return ui

def selectAndSavePoints():
    global coordsToClick, recording_running
    coordsToClick = []  # 每次录制前重置坐标列表
    recording_running = True
    
    # 自动创建保存目录,避免报错
    save_dir = "../Saves"
    if not exists(save_dir):
        makedirs(save_dir)
    
    print("Press the key 's' to start selecting points")
    print("Press the key 'p' to cancel last selected point (works multiple times)")
    print("Once finished, press 'f' to save your points")
    wait("s")
    
    def MonitorKeyPresses(_hookManager):
        global coordsToClick, recording_running
        def onpress(event):
            if event.Key == "P" and coordsToClick:
                del coordsToClick[-1]
                print(f"Removed last point. Remaining points: {len(coordsToClick)}")
            # 返回True,让键盘事件继续传递给系统
            return True
        _hookManager.SubscribeKeyDown(onpress)
        _hookManager.HookKeyboard()
        # 用全局标志控制消息循环,替代PostQuitMessage
        while recording_running:
            PumpWaitingMessages()
        _hookManager.UnhookKeyboard()
    
    def MonitorMouseClicks(_hookManager):
        global coordsToClick, recording_running
        def onclick(event):
            coordsToClick.append(event.Position)
            print(f"Recorded point: {event.Position}")
            # 返回True,让鼠标事件继续传递给系统,这样点击能正常作用于其他程序
            return True
        _hookManager.SubscribeMouseLeftDown(onclick)
        _hookManager.HookMouse()
        while recording_running:
            PumpWaitingMessages()
        _hookManager.UnhookMouse()
    
    hookManager = HookManager()
    threadClick = Thread(target=MonitorMouseClicks, args=(hookManager,))
    threadPress = Thread(target=MonitorKeyPresses, args=(hookManager,))
    threadClick.start()
    threadPress.start()
    
    wait('f')
    # 设置退出标志,让线程的消息循环终止
    recording_running = False
    # 等待两个线程完全退出
    threadClick.join()
    threadPress.join()
    
    filename = input("Enter the filename (without .txt): ")
    with open(f"{save_dir}/{filename}.txt", 'w') as file:
        for coords in coordsToClick:
            file.write(f"{coords[0]}:{coords[1]}\n")
    print(f"Successfully saved {len(coordsToClick)} points to {filename}.txt!")

def loadAndExecutePoints():
    def click(x, y):
        SetCursorPos((x, y))
        mouse_event(MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
        mouse_event(MOUSEEVENTF_LEFTUP, x, y, 0, 0)
    
    save_dir = "../Saves"
    if not exists(save_dir):
        print("No saved points found! Please record some first.")
        return
    
    # 只显示txt格式的保存文件
    files = [f for f in listdir(save_dir) if f.endswith('.txt')]
    if not files:
        print("No saved points found! Please record some first.")
        return
    
    for i in range(len(files)):
        print(f"[{i}]: {files[i]}")
    filenumber = sanitised_input("Enter the file number: ", type_=int, range_=range(len(files)))
    filename = files[filenumber]
    
    print("Press 's' to start executing the clicks")
    wait('s')
    
    with open(f"{save_dir}/{filename}", 'r') as file:
        # 过滤空行,避免报错
        lines = [line.rstrip('\n') for line in file if line.strip()]
    
    for line in lines:
        components = line.split(":")
        if len(components) != 2:
            print(f"Skipping invalid line: {line}")
            continue
        try:
            x, y = int(components[0]), int(components[1])
            click(x, y)
            sleep(0.2)
        except ValueError:
            print(f"Skipping invalid coordinates: {line}")

def main():
    print("\n=== Mouse Click Recorder & Player ===")
    print("[1]: Record and Save Mouse Clicks")
    print("[2]: Load and Play Saved Clicks")
    option = sanitised_input("Select an option: ", type_=int, range_=(1, 2))
    if option == 1:
        selectAndSavePoints()
    else:
        loadAndExecutePoints()

if __name__ == "__main__":
    try:
        while True:
            main()
    except KeyboardInterrupt:
        print("\nExiting program...")
        exit(0)

关键改动说明

  1. 钩子回调返回True:解决了录制时无法操作其他程序的核心问题,让系统能正常接收你的鼠标/键盘事件。
  2. 全局recording_running标志:替代原来的PostQuitMessage,确保录制线程能平稳退出,钩子被正确卸载,不会残留系统级钩子导致后续异常。
  3. 健壮性优化:自动创建保存目录、过滤无效文件/行、用with管理文件、支持Ctrl+C退出,让程序更稳定。
  4. 用户体验优化:添加了操作反馈提示,比如删除点后的剩余数量、保存成功提示等。

额外建议

pyHook是比较老旧的库,后续如果遇到更多兼容性问题,可以考虑替换为pynput——这是一个更现代、维护更活跃的输入监控/模拟库,API设计更友好,对Python3.x的兼容性也更好。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:26:35