我在 Python 中使用 PyChrome 通过 Chrome DevTools Protocol (CDP) 模拟键盘事件,如 keyDown、keyUp 和 keyPress。 type 方法工作正常,但 down、up 和 press 方法未按预期运行。
问题:
down
:未按住该键。up
:按键未释放。press
:组合键无法正确模拟按键。key_mapping = {
"Backspace": {"code": "Backspace", "keyCode": 8},
"Tab": {"code": "Tab", "keyCode": 9, "text": "\t"},
"Enter": {"code": "Enter", "keyCode": 13, "text": "\r"},
"Escape": {"code": "Escape", "keyCode": 27},
"Space": {"code": "Space", "keyCode": 32, "text": " "},
"Delete": {"code": "Delete", "keyCode": 46},
"A": {"code": "KeyA", "keyCode": 65, "text": "a"},
"B": {"code": "KeyB", "keyCode": 66, "text": "b"},
"C": {"code": "KeyC", "keyCode": 67, "text": "c"},
"D": {"code": "KeyD", "keyCode": 68, "text": "d"},
"E": {"code": "KeyE", "keyCode": 69, "text": "e"},
"F": {"code": "KeyF", "keyCode": 70, "text": "f"},
"G": {"code": "KeyG", "keyCode": 71, "text": "g"},
"H": {"code": "KeyH", "keyCode": 72, "text": "h"},
"I": {"code": "KeyI", "keyCode": 73, "text": "i"},
"J": {"code": "KeyJ", "keyCode": 74, "text": "j"},
"K": {"code": "KeyK", "keyCode": 75, "text": "k"},
"L": {"code": "KeyL", "keyCode": 76, "text": "l"},
"M": {"code": "KeyM", "keyCode": 77, "text": "m"},
"N": {"code": "KeyN", "keyCode": 78, "text": "n"},
"O": {"code": "KeyO", "keyCode": 79, "text": "o"},
"P": {"code": "KeyP", "keyCode": 80, "text": "p"},
"Q": {"code": "KeyQ", "keyCode": 81, "text": "q"},
"R": {"code": "KeyR", "keyCode": 82, "text": "r"},
"S": {"code": "KeyS", "keyCode": 83, "text": "s"},
}
我创建了一个键盘类,它将公开几种方法,例如(向上、按下、向下、键入) 目前类型正在按我的预期工作。但对于向上、按或向下不起作用。我已尝试解决它们,但它们无法正常工作。
class Keyboard:
"""Handles keyboard interactions on the page"""
def __init__(self, tab: Tab):
self._tab = tab
def type(self, text: str):
"""Simulates typing text character by character."""
for char in text:
# self._tab.call_method("Input.dispatchKeyEvent", type="keyDown", text=char)
self._dispatch_key_event("keyDown", text=char)
# self._tab.call_method("Input.dispatchKeyEvent", type="keyUp", text=char)
self._dispatch_key_event("keyUp", text=char)
def down(self, key: str):
"""Holds down a specified key without releasing it."""
key_data = self._get_key_data(key)
if key_data:
print(f"Holding down: {key} -> {key_data}")
self._dispatch_key_event("keyDown", **key_data)
def up(self, key: str):
"""Releases a held key."""
key_data = self._get_key_data(key)
if key_data:
print(f"Releasing: {key} -> {key_data}")
self._dispatch_key_event("keyUp", **key_data)
def press(self, combination: str):
keys = combination.split("+")
for key in keys[:-1]: # Everything except the last key
key_data = self._get_key_data(key)
self._dispatch_key_event("keyDown", **key_data)
# Press the main key
key_data = self._get_key_data(keys[-1]) # Last key in combination
self._dispatch_key_event("keyDown", **key_data)
self._dispatch_key_event("keyUp", **key_data)
for key in reversed(keys[:-1]):
key_data = self._get_key_data(key)
self._dispatch_key_event("keyUp", **key_data)
def _dispatch_key_event(self, event_type: str, **kwargs):
"""Dispatches a key event to the tab."""
print(f"Dispatching {event_type} with arguments: {kwargs}")
self._tab.call_method(
"Input.dispatchKeyEvent", type=event_type, _timeout=5, **kwargs
)
def _get_key_data(self, key: str):
"""Helper method to retrieve the appropriate key data from the key mapping."""
normalized_key = self._normalize_key(key)
key_data = key_mapping.get(normalized_key)
if not key_data:
print(f"Warning: Key '{key}' not found in key_mapping.")
return key_data
@staticmethod
def _normalize_key(key: str) -> str:
"""Normalizes key names for compatibility with the key mapping."""
key = key.capitalize()
if key == "Ctrl":
return "Control"
elif key == "Cmd":
return "Meta" # For Mac "Cmd" becomes "Meta"
elif key == "Controlormeta":
# Automatically use 'Meta' for Mac, 'Control' for Windows/Linux
return "Meta" if sys.platform == "darwin" else "Control"
return key
因此,如果您这样调用 ->
keyboard.press("Control+T")
,预期的行为将打开一个新选项卡。但它不起作用。
经过多次尝试,我发现为了实现键盘按键机制,您必须以 Unicode 代码发送 cdp 命令。
有几种情况需要考虑,例如shift、ctrl和alt,它们被视为修饰键,对于修饰符,我们有Unicode代码。 因此,假设您想发送相当于
shift+i
的按键。
在这种情况下,必须使用 cdp 命令发送修饰符。
我已经附上了键盘方法的代码块。
class Keys(Enum):
SHIFT = "\ue008"
CONTROL = "\ue009"
ALT = "\ue00a"
META = "\ue03d"
ENTER = "\ue007"
BACKSPACE = "\ue003"
TAB = "\ue004"
ESCAPE = "\ue00c"
DELETE = "\ue017"
ARROW_LEFT = "\ue012"
ARROW_UP = "\ue013"
ARROW_RIGHT = "\ue014"
ARROW_DOWN = "\ue015"
modifierBit = {
"\ue008": 8, # SHIFT
"\ue009": 2, # CONTROL
"\ue00a": 1, # ALT
"\ue03d": 4, # META
}
keyDefinitions = {
"a": {"key": "a", "code": "KeyA", "keyCode": 65, "shiftKey": "A", "location": 0},
"b": {"key": "b", "code": "KeyB", "keyCode": 66, "shiftKey": "B", "location": 0},
"c": {"key": "c", "code": "KeyC", "keyCode": 67, "shiftKey": "C", "location": 0},
"d": {"key": "d", "code": "KeyD", "keyCode": 68, "shiftKey": "D", "location": 0},
"e": {"key": "e", "code": "KeyE", "keyCode": 69, "shiftKey": "E", "location": 0},
"f": {"key": "f", "code": "KeyF", "keyCode": 70, "shiftKey": "F", "location": 0},
"g": {"key": "g", "code": "KeyG", "keyCode": 71, "shiftKey": "G", "location": 0},
"h": {"key": "h", "code": "KeyH", "keyCode": 72, "shiftKey": "H", "location": 0},
"i": {"key": "i", "code": "KeyI", "keyCode": 73, "shiftKey": "I", "location": 0},
"j": {"key": "j", "code": "KeyJ", "keyCode": 74, "shiftKey": "J", "location": 0},
"k": {"key": "k", "code": "KeyK", "keyCode": 75, "shiftKey": "K", "location": 0},
"l": {"key": "l", "code": "KeyL", "keyCode": 76, "shiftKey": "L", "location": 0},
"m": {"key": "m", "code": "KeyM", "keyCode": 77, "shiftKey": "M", "location": 0},
"n": {"key": "n", "code": "KeyN", "keyCode": 78, "shiftKey": "N", "location": 0},
"o": {"key": "o", "code": "KeyO", "keyCode": 79, "shiftKey": "O", "location": 0},
"p": {"key": "p", "code": "KeyP", "keyCode": 80, "shiftKey": "P", "location": 0},
"q": {"key": "q", "code": "KeyQ", "keyCode": 81, "shiftKey": "Q", "location": 0},
"r": {"key": "r", "code": "KeyR", "keyCode": 82, "shiftKey": "R", "location": 0},
"s": {"key": "s", "code": "KeyS", "keyCode": 83, "shiftKey": "S", "location": 0},
"t": {"key": "t", "code": "KeyT", "keyCode": 84, "shiftKey": "T", "location": 0},
"u": {"key": "u", "code": "KeyU", "keyCode": 85, "shiftKey": "U", "location": 0},
"v": {"key": "v", "code": "KeyV", "keyCode": 86, "shiftKey": "V", "location": 0},
"w": {"key": "w", "code": "KeyW", "keyCode": 87, "shiftKey": "W", "location": 0},
"x": {"key": "x", "code": "KeyX", "keyCode": 88, "shiftKey": "X", "location": 0},
"y": {"key": "y", "code": "KeyY", "keyCode": 89, "shiftKey": "Y", "location": 0},
"z": {"key": "z", "code": "KeyZ", "keyCode": 90, "shiftKey": "Z", "location": 0},
"1": {"key": "1", "code": "Digit1", "keyCode": 49, "shiftKey": "!", "location": 0},
"2": {"key": "2", "code": "Digit2", "keyCode": 50, "shiftKey": "@", "location": 0},
"3": {"key": "3", "code": "Digit3", "keyCode": 51, "shiftKey": "#", "location": 0},
"4": {"key": "4", "code": "Digit4", "keyCode": 52, "shiftKey": "$", "location": 0},
"5": {"key": "5", "code": "Digit5", "keyCode": 53, "shiftKey": "%", "location": 0},
"6": {"key": "6", "code": "Digit6", "keyCode": 54, "shiftKey": "^", "location": 0},
"7": {"key": "7", "code": "Digit7", "keyCode": 55, "shiftKey": "&", "location": 0},
"8": {"key": "8", "code": "Digit8", "keyCode": 56, "shiftKey": "*", "location": 0},
"9": {"key": "9", "code": "Digit9", "keyCode": 57, "shiftKey": "(", "location": 0},
"0": {"key": "0", "code": "Digit0", "keyCode": 48, "shiftKey": ")", "location": 0},
"-": {"key": "-", "code": "Minus", "keyCode": 189, "shiftKey": "_", "location": 0},
"=": {"key": "=", "code": "Equal", "keyCode": 187, "shiftKey": "+", "location": 0},
"[": {
"key": "[",
"code": "BracketLeft",
"keyCode": 219,
"shiftKey": "{",
"location": 0,
},
"]": {
"key": "]",
"code": "BracketRight",
"keyCode": 221,
"shiftKey": "}",
"location": 0,
},
"\\": {
"key": "\\",
"code": "Backslash",
"keyCode": 220,
"shiftKey": "|",
"location": 0,
},
";": {
"key": ";",
"code": "Semicolon",
"keyCode": 186,
"shiftKey": ":",
"location": 0,
},
"'": {"key": "'", "code": "Quote", "keyCode": 222, "shiftKey": '"', "location": 0},
",": {"key": ",", "code": "Comma", "keyCode": 188, "shiftKey": "<", "location": 0},
".": {"key": ".", "code": "Period", "keyCode": 190, "shiftKey": ">", "location": 0},
"/": {"key": "/", "code": "Slash", "keyCode": 191, "shiftKey": "?", "location": 0},
"`": {
"key": "`",
"code": "Backquote",
"keyCode": 192,
"shiftKey": "~",
"location": 0,
},
# Numpad
"Numpad0": {"key": "0", "code": "Numpad0", "keyCode": 96, "location": 3},
"Numpad1": {"key": "1", "code": "Numpad1", "keyCode": 97, "location": 3},
"Numpad2": {"key": "2", "code": "Numpad2", "keyCode": 98, "location": 3},
"Numpad3": {"key": "3", "code": "Numpad3", "keyCode": 99, "location": 3},
"Numpad4": {"key": "4", "code": "Numpad4", "keyCode": 100, "location": 3},
"Numpad5": {"key": "5", "code": "Numpad5", "keyCode": 101, "location": 3},
"Numpad6": {"key": "6", "code": "Numpad6", "keyCode": 102, "location": 3},
"Numpad7": {"key": "7", "code": "Numpad7", "keyCode": 103, "location": 3},
"Numpad8": {"key": "8", "code": "Numpad8", "keyCode": 104, "location": 3},
"Numpad9": {"key": "9", "code": "Numpad9", "keyCode": 105, "location": 3},
"NumpadMultiply": {
"key": "*",
"code": "NumpadMultiply",
"keyCode": 106,
"location": 3,
},
"NumpadAdd": {"key": "+", "code": "NumpadAdd", "keyCode": 107, "location": 3},
"NumpadSubtract": {
"key": "-",
"code": "NumpadSubtract",
"keyCode": 109,
"location": 3,
},
"NumpadDecimal": {
"key": ".",
"code": "NumpadDecimal",
"keyCode": 110,
"location": 3,
},
"NumpadDivide": {"key": "/", "code": "NumpadDivide", "keyCode": 111, "location": 3},
"F1": {"key": "F1", "code": "F1", "keyCode": 112, "location": 0},
"F2": {"key": "F2", "code": "F2", "keyCode": 113, "location": 0},
"F3": {"key": "F3", "code": "F3", "keyCode": 114, "location": 0},
"F4": {"key": "F4", "code": "F4", "keyCode": 115, "location": 0},
"F5": {"key": "F5", "code": "F5", "keyCode": 116, "location": 0},
"F6": {"key": "F6", "code": "F6", "keyCode": 117, "location": 0},
"F7": {"key": "F7", "code": "F7", "keyCode": 118, "location": 0},
"F8": {"key": "F8", "code": "F8", "keyCode": 119, "location": 0},
"F9": {"key": "F9", "code": "F9", "keyCode": 120, "location": 0},
"F10": {"key": "F10", "code": "F10", "keyCode": 121, "location": 0},
"F11": {"key": "F11", "code": "F11", "keyCode": 122, "location": 0},
"F12": {"key": "F12", "code": "F12", "keyCode": 123, "location": 0},
"\ue007": {
"key": "Enter",
"code": "Enter",
"keyCode": 13,
"text": "\r",
"location": 0,
},
"\ue003": {"key": "Backspace", "code": "Backspace", "keyCode": 8, "location": 0},
"\ue004": {"key": "Tab", "code": "Tab", "keyCode": 9, "text": "\t", "location": 0},
"\ue00c": {"key": "Escape", "code": "Escape", "keyCode": 27, "location": 0},
"\ue017": {"key": "Delete", "code": "Delete", "keyCode": 46, "location": 0},
"Space": {"key": " ", "code": "Space", "keyCode": 32, "text": " ", "location": 0},
"\ue012": {"key": "ArrowLeft", "code": "ArrowLeft", "keyCode": 37, "location": 0},
"\ue013": {"key": "ArrowUp", "code": "ArrowUp", "keyCode": 38, "location": 0},
"\ue014": {"key": "ArrowRight", "code": "ArrowRight", "keyCode": 39, "location": 0},
"\ue015": {"key": "ArrowDown", "code": "ArrowDown", "keyCode": 40, "location": 0},
"\ue008": {"key": "Shift", "code": "ShiftLeft", "keyCode": 16, "location": 1},
"\ue009": {"key": "Control", "code": "ControlLeft", "keyCode": 17, "location": 1},
"\ue00a": {"key": "Alt", "code": "AltLeft", "keyCode": 18, "location": 1},
"\ue03d": {
"key": "Meta",
"code": "MetaLeft",
"keyCode": 91,
"location": 1,
}, # Windows key or Command key
"CapsLock": {"key": "CapsLock", "code": "CapsLock", "keyCode": 20, "location": 0},
"NumLock": {"key": "NumLock", "code": "NumLock", "keyCode": 144, "location": 0},
"ScrollLock": {
"key": "ScrollLock",
"code": "ScrollLock",
"keyCode": 145,
"location": 0,
},
"ShiftRight": {"key": "Shift", "code": "ShiftRight", "keyCode": 16, "location": 2},
"ControlRight": {
"key": "Control",
"code": "ControlRight",
"keyCode": 17,
"location": 2,
},
"AltRight": {"key": "Alt", "code": "AltRight", "keyCode": 18, "location": 2},
"MetaRight": {
"key": "Meta",
"code": "MetaRight",
"keyCode": 92,
"location": 2,
}, # Right Windows key or Command key
"Home": {"key": "Home", "code": "Home", "keyCode": 36, "location": 0},
"End": {"key": "End", "code": "End", "keyCode": 35, "location": 0},
"PageUp": {"key": "PageUp", "code": "PageUp", "keyCode": 33, "location": 0},
"PageDown": {"key": "PageDown", "code": "PageDown", "keyCode": 34, "location": 0},
"Insert": {"key": "Insert", "code": "Insert", "keyCode": 45, "location": 0},
"MediaPlayPause": {
"key": "MediaPlayPause",
"code": "MediaPlayPause",
"keyCode": 179,
"location": 0,
},
"MediaStop": {
"key": "MediaStop",
"code": "MediaStop",
"keyCode": 178,
"location": 0,
},
"MediaTrackNext": {
"key": "MediaTrackNext",
"code": "MediaTrackNext",
"keyCode": 176,
"location": 0,
},
"MediaTrackPrevious": {
"key": "MediaTrackPrevious",
"code": "MediaTrackPrevious",
"keyCode": 177,
"location": 0,
},
"AudioVolumeMute": {
"key": "AudioVolumeMute",
"code": "AudioVolumeMute",
"keyCode": 173,
"location": 0,
},
"AudioVolumeDown": {
"key": "AudioVolumeDown",
"code": "AudioVolumeDown",
"keyCode": 174,
"location": 0,
},
"AudioVolumeUp": {
"key": "AudioVolumeUp",
"code": "AudioVolumeUp",
"keyCode": 175,
"location": 0,
},
"BrowserBack": {
"key": "BrowserBack",
"code": "BrowserBack",
"keyCode": 166,
"location": 0,
},
"BrowserForward": {
"key": "BrowserForward",
"code": "BrowserForward",
"keyCode": 167,
"location": 0,
},
"BrowserRefresh": {
"key": "BrowserRefresh",
"code": "BrowserRefresh",
"keyCode": 168,
"location": 0,
},
"BrowserHome": {
"key": "BrowserHome",
"code": "BrowserHome",
"keyCode": 172,
"location": 0,
},
"F13": {"key": "F13", "code": "F13", "keyCode": 124, "location": 0},
"F14": {"key": "F14", "code": "F14", "keyCode": 125, "location": 0},
"F15": {"key": "F15", "code": "F15", "keyCode": 126, "location": 0},
"F16": {"key": "F16", "code": "F16", "keyCode": 127, "location": 0},
"F17": {"key": "F17", "code": "F17", "keyCode": 128, "location": 0},
"F18": {"key": "F18", "code": "F18", "keyCode": 129, "location": 0},
"F19": {"key": "F19", "code": "F19", "keyCode": 130, "location": 0},
"F20": {"key": "F20", "code": "F20", "keyCode": 131, "location": 0},
"NumpadEnter": {
"key": "Enter",
"code": "NumpadEnter",
"keyCode": 13,
"location": 3,
},
"NumpadEqual": {"key": "=", "code": "NumpadEqual", "keyCode": 187, "location": 3},
"NumLock": {"key": "NumLock", "code": "NumLock", "keyCode": 144, "location": 3},
"ContextMenu": {
"key": "ContextMenu",
"code": "ContextMenu",
"keyCode": 93,
"location": 0,
},
"Power": {"key": "Power", "code": "Power", "keyCode": 255, "location": 0},
"Sleep": {"key": "Sleep", "code": "Sleep", "keyCode": 95, "location": 0},
"WakeUp": {"key": "WakeUp", "code": "WakeUp", "keyCode": 255, "location": 0},
}
class Keyboard:
"""Enhanced keyboard implementation based on DrissionPage's logic"""
def __init__(self, tab: Tab):
self._tab = tab
self.modifier = 0
def make_input_data(self, key: str, key_up: bool = False) -> Optional[Dict]:
"""Creates input data for key events based on DrissionPage's implementation"""
data = keyDefinitions.get(key)
if not data:
return None
result = {
"modifiers": self.modifier,
"autoRepeat": False,
"type": "keyUp" if key_up else "keyDown",
}
shift = self.modifier & 8
if shift and data.get("shiftKey"):
result["key"] = data["shiftKey"]
result["text"] = data["shiftKey"]
elif "key" in data:
result["key"] = data["key"]
if len(result.get("key", "")) == 1:
result["text"] = result["key"]
sys_text = "windowsVirtualKeyCode"
if shift and data.get("shiftKeyCode"):
result[sys_text] = data["shiftKeyCode"]
elif "keyCode" in data:
result[sys_text] = data["keyCode"]
if "code" in data:
result["code"] = data["code"]
if "location" in data:
result["location"] = data["location"]
result["isKeypad"] = data["location"] == 3
else:
result["location"] = 0
result["isKeypad"] = False
if shift and data.get("shiftText"):
result["text"] = data["shiftText"]
result["unmodifiedText"] = data["shiftText"]
elif "text" in data:
result["text"] = data["text"]
result["unmodifiedText"] = data["text"]
if self.modifier & ~8:
result["text"] = ""
result["type"] = (
"keyUp" if key_up else ("keyDown" if result.get("text") else "rawKeyDown")
)
return result
def down(self, key: str):
"""Holds down a specified key"""
key = getattr(Keys, key.upper(), key.lower())
key_value = key.value if isinstance(key, Keys) else key
if key_value in modifierBit:
self.modifier |= modifierBit[key_value]
return self
data = self.make_input_data(key_value)
if not data:
raise ValueError(f"Invalid key: {key}")
self._tab.call_method("Input.dispatchKeyEvent", **data)
return self
def up(self, key: str):
"""Releases a held key"""
key = getattr(Keys, key.upper(), key.lower())
key_value = key.value if isinstance(key, Keys) else key
# Handle modifier keys
if key_value in modifierBit:
self.modifier &= ~modifierBit[key_value]
return self
# Handle regular keys
data = self.make_input_data(key_value, key_up=True)
if not data:
raise ValueError(f"Invalid key: {key}")
self._tab.call_method("Input.dispatchKeyEvent", **data)
return self
def type(self, text: str):
"""Types text character by character"""
for char in text:
if char == " ":
data = self.make_input_data("Space")
else:
data = self.make_input_data(char)
if data:
self._tab.call_method("Input.dispatchKeyEvent", **data)
if char == " ":
up_data = self.make_input_data("Space", key_up=True)
else:
up_data = self.make_input_data(char, key_up=True)
if up_data:
self._tab.call_method("Input.dispatchKeyEvent", **up_data)
return self