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

求助Python实现模拟真实按键突破第三方应用截图限制

问题

某第三方应用存在行为检测,阻止常规截图操作:

  • 使用keyboard库、pyautogui或普通按键模拟工具无法完成截图
  • 手动按PrtScr键或屏幕键盘按键时,应用允许截图并自动保存至api/screenshots目录(只需窗口处于激活状态)
  • 测试发现模拟数字键"3"有效,但模拟PrtScr无效

已尝试的方法

  1. keyboard库模拟:
keyboard.press('print_screen')
keyboard.release('print_screen')
  1. pyautogui截图:
pyautogui.screenshot()
  1. 基于ctypes的虚拟键模拟:
import ctypes
from ctypes import wintypes
import time

user32 = ctypes.WinDLL('user32', use_last_error=True)

INPUT_MOUSE    = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2

KEYEVENTF_EXTENDEDKEY = 0x0001
KEYEVENTF_KEYUP       = 0x0002
KEYEVENTF_UNICODE     = 0x0004
KEYEVENTF_SCANCODE    = 0x0008

MAPVK_VK_TO_VSC = 0

VK_TAB  = 0x09
VK_MENU = 0x12
VK_SNAPSHOT = 0x2C
key_3 = 0x33


wintypes.ULONG_PTR = wintypes.WPARAM

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))

    def __init__(self, *args, **kwds):
        super(KEYBDINPUT, self).__init__(*args, **kwds)
        if not self.dwFlags & KEYEVENTF_UNICODE:
            self.wScan = user32.MapVirtualKeyExW(self.wVk,
                                                 MAPVK_VK_TO_VSC, 0)

class HARDWAREINPUT(ctypes.Structure):
    _fields_ = (("uMsg",    wintypes.DWORD),
                ("wParamL", wintypes.WORD),
                ("wParamH", wintypes.WORD))

class INPUT(ctypes.Structure):
    class _INPUT(ctypes.Union):
        _fields_ = (("ki", KEYBDINPUT),
                    ("mi", MOUSEINPUT),
                    ("hi", HARDWAREINPUT))
    _anonymous_ = ("_input",)
    _fields_ = (("type",   wintypes.DWORD),
                ("_input", _INPUT))

LPINPUT = ctypes.POINTER(INPUT)

def _check_count(result, func, args):
    if result == 0:
        raise ctypes.WinError(ctypes.get_last_error())
    return args

user32.SendInput.errcheck = _check_count
user32.SendInput.argtypes = (wintypes.UINT, # nInputs
                             LPINPUT,       # pInputs
                             ctypes.c_int)  # cbSize

def PressKey(hexKeyCode):
    x = INPUT(type=INPUT_KEYBOARD,
              ki=KEYBDINPUT(wVk=hexKeyCode))
    user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))

def ReleaseKey(hexKeyCode):
    x = INPUT(type=INPUT_KEYBOARD,
              ki=KEYBDINPUT(wVk=hexKeyCode,
                            dwFlags=KEYEVENTF_KEYUP))
    user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))

def Press_function():
    PressKey(key_3)
    time.sleep(0.1)
    ReleaseKey(key_3)
    time.sleep(0.1)
    PressKey(VK_SNAPSHOT)
    time.sleep(0.1)
    ReleaseKey(VK_SNAPSHOT)

if __name__ == "__main__":
    time.sleep(1)
    Press_function()

解决方案

原因分析

该应用大概率通过**按键事件的扫描码(Scan Code)**或事件来源标记区分真实按键与软件模拟。普通虚拟键(VK)模拟的事件缺少真实按键的硬件扫描码特征,导致PrtScr模拟未通过检测,而数字键"3"的模拟刚好符合应用的检测规则。

修改后的模拟代码

改用扫描码+KEYEVENTF_SCANCODE标记模拟PrtScr,让事件更接近真实硬件触发:

import ctypes
from ctypes import wintypes
import time

user32 = ctypes.WinDLL('user32', use_last_error=True)

INPUT_KEYBOARD = 1
KEYEVENTF_SCANCODE = 0x0008
KEYEVENTF_KEYUP = 0x0002

# 标准PC键盘PrtScr的扫描码,若无效可自行适配当前键盘布局
PRTSCR_SCANCODE = 0x2C

wintypes.ULONG_PTR = wintypes.WPARAM

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_ = (("ki", KEYBDINPUT),)
    _anonymous_ = ("_input",)
    _fields_ = (("type",   wintypes.DWORD),
                ("_input", _INPUT))

LPINPUT = ctypes.POINTER(INPUT)

def _check_count(result, func, args):
    if result == 0:
        raise ctypes.WinError(ctypes.get_last_error())
    return args

user32.SendInput.errcheck = _check_count
user32.SendInput.argtypes = (wintypes.UINT,
                             LPINPUT,
                             ctypes.c_int)

def press_scan_code(scan_code):
    # 按下按键:仅传入扫描码,不指定虚拟键
    input_down = INPUT(type=INPUT_KEYBOARD,
                      ki=KEYBDINPUT(wScan=scan_code, dwFlags=KEYEVENTF_SCANCODE))
    user32.SendInput(1, ctypes.byref(input_down), ctypes.sizeof(input_down))

def release_scan_code(scan_code):
    # 释放按键:添加KEYEVENTF_KEYUP标记
    input_up = INPUT(type=INPUT_KEYBOARD,
                    ki=KEYBDINPUT(wScan=scan_code, dwFlags=KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP))
    user32.SendInput(1, ctypes.byref(input_up), ctypes.sizeof(input_up))

def simulate_prtscr():
    press_scan_code(PRTSCR_SCANCODE)
    time.sleep(0.1)  # 模拟真实按键的按压间隔
    release_scan_code(PRTSCR_SCANCODE)

# 自动激活目标窗口(可选)
def activate_window(window_title):
    hwnd = user32.FindWindowW(None, window_title)
    if hwnd:
        user32.SetForegroundWindow(hwnd)
        return True
    return False

if __name__ == "__main__":
    # 替换为你的目标应用窗口标题
    target_window_title = "目标应用名称"
    # 给3秒手动切换窗口,或使用activate_window自动激活
    time.sleep(3)
    # if activate_window(target_window_title):
    #     time.sleep(0.5)
    simulate_prtscr()

补充说明

  1. 若0x2C扫描码无效,可通过以下代码获取当前键盘布局下的PrtScr扫描码:
VK_SNAPSHOT = 0x2C
MAPVK_VK_TO_VSC = 0
prtscr_scancode = user32.MapVirtualKeyExW(VK_SNAPSHOT, MAPVK_VK_TO_VSC, 0)
  1. 自动激活窗口功能需替换为目标应用的准确窗口标题,避免手动切换。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 16:07:02