You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用Python发送键鼠原始输入,适配仅接受原生输入的程序?

嘿,这个问题我太熟了——很多游戏、专业软件会刻意屏蔽上层Python库的模拟输入,只认硬件级的原始输入流。别慌,给你几个从易到难的解决方案,总有一个能搞定:

方案1:用Windows API直接发送原始输入(Windows平台首选)

pynputpyautogui这类库都是在系统输入框架的上层做模拟,容易被目标程序拦截。而Windows自带的SendInput API是直接把输入事件注入系统的原始输入队列,和真实键鼠硬件产生的输入几乎无差别,大部分程序都会买账。

你可以用Python的ctypes库直接调用这个API,不用额外安装依赖。下面是完整的示例代码,包含鼠标点击和键盘按键模拟:

import ctypes
from ctypes import wintypes

# 加载Windows用户32位动态链接库
user32 = ctypes.WinDLL('user32', use_last_error=True)

# 定义输入类型常量
INPUT_MOUSE = 0
INPUT_KEYBOARD = 1

# 定义鼠标输入结构体
class MOUSEINPUT(ctypes.Structure):
    _fields_ = (("dx", wintypes.LONG),
                ("dy", wintypes.LONG),
                ("mouseData", wintypes.DWORD),
                ("dwFlags", wintypes.DWORD),
                ("time", wintypes.DWORD),
                ("dwExtraInfo", wintypes.ULONG_PTR))

# 定义键盘输入结构体
class KEYBDINPUT(ctypes.Structure):
    _fields_ = (("wVk", wintypes.WORD),
                ("wScan", wintypes.WORD),
                ("dwFlags", wintypes.DWORD),
                ("time", wintypes.DWORD),
                ("dwExtraInfo", wintypes.ULONG_PTR))

# 定义通用输入结构体
class INPUT(ctypes.Structure):
    class _INPUT(ctypes.Union):
        _fields_ = (("mi", MOUSEINPUT),
                    ("ki", KEYBDINPUT))
    _anonymous_ = ("_input",)
    _fields_ = (("type", wintypes.DWORD),
                ("_input", _INPUT))

# 模拟鼠标左键点击(传入屏幕坐标x,y)
def simulate_mouse_click(x, y):
    # 把屏幕坐标转换为API要求的0-65535绝对坐标范围
    screen_width = user32.GetSystemMetrics(0)
    screen_height = user32.GetSystemMetrics(1)
    dx = int(x * 65535 / screen_width)
    dy = int(y * 65535 / screen_height)
    
    # 构造移动到目标位置的输入事件
    move_input = INPUT(type=INPUT_MOUSE)
    move_input.mi.dx = dx
    move_input.mi.dy = dy
    move_input.mi.dwFlags = 0x0001 | 0x8000  # MOUSEEVENTF_ABSOLUTE + MOUSEEVENTF_MOVE
    
    # 构造左键按下事件
    down_input = INPUT(type=INPUT_MOUSE)
    down_input.mi.dwFlags = 0x0002  # MOUSEEVENTF_LEFTDOWN
    
    # 构造左键释放事件
    up_input = INPUT(type=INPUT_MOUSE)
    up_input.mi.dwFlags = 0x0004  # MOUSEEVENTF_LEFTUP
    
    # 批量发送输入事件
    inputs = (INPUT * 3)(move_input, down_input, up_input)
    user32.SendInput(len(inputs), ctypes.byref(inputs), ctypes.sizeof(INPUT))

# 模拟键盘按键(传入虚拟键码,比如0x41对应A键)
def simulate_key_press(key_code):
    # 构造按键按下事件
    down_input = INPUT(type=INPUT_KEYBOARD)
    down_input.ki.wVk = key_code
    down_input.ki.dwFlags = 0
    
    # 构造按键释放事件
    up_input = INPUT(type=INPUT_KEYBOARD)
    up_input.ki.wVk = key_code
    up_input.ki.dwFlags = 0x0002  # KEYEVENTF_KEYUP
    
    # 批量发送输入事件
    inputs = (INPUT * 2)(down_input, up_input)
    user32.SendInput(len(inputs), ctypes.byref(inputs), ctypes.sizeof(INPUT))

# 测试代码:移动到(500,500)点击,然后按A键
simulate_mouse_click(500, 500)
simulate_key_press(0x41)

小提示:

  • 虚拟键码可以查Windows官方文档,常用的比如0x0D是回车,0x1B是ESC。
  • 运行脚本时最好以管理员身份启动Python,避免权限不足的问题。
方案2:创建虚拟输入设备(跨平台,最底层)

如果SendInput还是被拦截,那得直接模拟硬件级的输入——也就是创建一个虚拟的键鼠设备,让系统认为是真实硬件在发送输入。这个方法几乎能绕过所有检测,但需要系统级权限。

Linux平台示例(需要root权限):

Linux下可以通过/dev/uinput设备创建虚拟输入,直接写入输入事件:

import os
import struct

# 打开uinput设备(需要root)
uinput_fd = os.open("/dev/uinput", os.O_WRONLY | os.O_NONBLOCK)

# 设置支持的输入事件类型(鼠标移动、左右键)
os.write(uinput_fd, struct.pack('HHI', 0x01, 0x00, 0x01))  # EV_KEY + BTN_LEFT
os.write(uinput_fd, struct.pack('HHI', 0x01, 0x01, 0x01))  # EV_KEY + BTN_RIGHT
os.write(uinput_fd, struct.pack('HHI', 0x02, 0x00, 0x01))  # EV_REL + REL_X(X轴移动)
os.write(uinput_fd, struct.pack('HHI', 0x02, 0x01, 0x01))  # EV_REL + REL_Y(Y轴移动)

# 创建虚拟设备
os.write(uinput_fd, struct.pack('I', 0x00))  # UI_DEV_CREATE

# 模拟鼠标移动+左键点击
def simulate_mouse_action():
    # 向右移动10像素,向下移动10像素
    os.write(uinput_fd, struct.pack('LLHHI', 0, 0, 0x02, 0x00, 10))
    os.write(uinput_fd, struct.pack('LLHHI', 0, 0, 0x02, 0x01, 10))
    os.write(uinput_fd, struct.pack('LLHHI', 0, 0, 0x00, 0x00, 0))  # 同步事件
    
    # 按下左键
    os.write(uinput_fd, struct.pack('LLHHI', 0, 0, 0x01, 0x00, 1))
    os.write(uinput_fd, struct.pack('LLHHI', 0, 0, 0x00, 0x00, 0))
    
    # 释放左键
    os.write(uinput_fd, struct.pack('LLHHI', 0, 0, 0x01, 0x00, 0))
    os.write(uinput_fd, struct.pack('LLHHI', 0, 0, 0x00, 0x00, 0))

simulate_mouse_action()

# 销毁虚拟设备并关闭文件句柄
os.write(uinput_fd, struct.pack('I', 0x01))  # UI_DEV_DESTROY
os.close(uinput_fd)

Windows平台:

Windows下需要用HID(人机接口设备)相关的API或者第三方驱动库(比如ViGEm)来创建虚拟设备,复杂度较高,如果你是Windows用户,优先试试方案1和方案3。

方案3:借助第三方工具简化开发(最省心)

如果不想写底层代码,直接用专门的自动化工具配合Python调用就行。比如Windows下的AutoHotkey,它的模拟输入是硬件级的,很多屏蔽模拟输入的程序都能识别。

步骤如下:

  1. 安装AutoHotkey(官网下载即可)
  2. 写一个简单的ahk脚本,比如auto_task.ahk
; 移动鼠标到(500,500)并左键点击
Click 500,500
; 按下A键
Send {a}
  1. 在Python中通过subprocess调用这个脚本:
import subprocess
subprocess.run(["AutoHotkey.exe", "auto_task.ahk"])

这个方法简单粗暴,不用关心底层实现,适合快速解决问题。

最后提醒
  • 如果目标程序是带反作弊的游戏,可能会检测虚拟输入设备或者API调用,这种情况可能需要更复杂的绕过手段,但这不在常规技术讨论范围内。
  • 所有底层模拟操作都建议关闭杀毒软件的实时防护,避免被误判为恶意程序。

内容的提问来源于stack exchange,提问作者Joseph Lawrence Loeffler

火山引擎 最新活动