所需的包句柄是操作系统中的标识符,相当于我们每个人的身份证。句柄在电脑中也是独一无二的。我们启动的每个程序都有自己的句柄号来表示我们的身份
为什么要说句柄?如果我们想做自动化操作,我们当然不希望程序占用我们的整个计算机。如果我们稍微操作一下程序步骤,我们就会失败。我们希望自动化程序只能在运行时操作某个窗口或程序。即使我们把自动化程序放在后台,也不会影响双方的操作,这里需要使用句柄
#清华镜像源pip配置 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simplepip config set global.trusted-host pypi.tuna.tsinghua.edu.cn #依赖库pip的安装 install pywin32
基本使用#部分参考文件
1、获取鼠标所在位置程序的句柄import timeimport win32apimport win32guitime.sleep(2)point = win32api.GetCursorPos() #win32api.GetCursorPos 获取鼠标当前坐标(x,y)hwnd = win32gui.WindowFromPoint(point) #查看坐标位置窗口的句柄print(hwnd) #输出句柄
2、通过句柄获取类名如下图所示,执行三次后,我将鼠标放在文本、桌面和idea上,并返回句柄ID
每次我们关闭并重新打开一个程序,我们都会发现句柄值发生了变化。每次从头找句柄都太麻烦了。
在开发之初,每个程序都有一个叫做“类名”的概念。每次更改类名和句柄时,定义后几乎不会更改。因此,我们最好先找到程序的类名,然后通过类名直接找到句柄,然后通过句柄进行真正需要的操作
vi main.py
import timeimport win32apimport win32gui# 通过句柄获得窗口类名deff get_clasname(hwnd): clasname = win32gui.GetClassName(hwnd) print(窗名:%s' % (clasname)) return clasnametime.sleep(2)point = win32api.GetCursorPos()hwnd = win32gui.WindowFromPoint(point)#查看窗口类名get__clasname(hwnd)
3、句柄通过类名获取从上面可以看出,我们获得了文档窗口的类名。现在我们直接通过类名获得句柄
我们没有找到具体的方法。我们的一般想法是先在主机上获得所有句柄id,通过循环取出所有句柄id的类名并进行比较。正确的上部id留在列表中。因此,如果我们打开多个相同程序的窗口,我们也将获得多个句柄
import timeimport win32apimport win32gui#获取当前主机上的所有句柄idef get_all_windows(): all_window_handles = [] # 枚举所有窗口句柄,添加到列表中 def enum_windows_proc(hwnd, param): param.append(hwnd) return True # 调用枚举窗口API win32gui.EnumWindows(enum_windows_proc, all_window_handles) return all_window_handles #返回是句柄id列表#查询输入的句柄id、类名def get_title(window_handle, class_name): #查询句柄的类名 window_class = win32gui.GetClassName(window_handle) #判断窗口类名是否与指定类名相同,如果相同,则返回窗口句柄,否则,返回空值 if window_class == class_name: return window_handle#deff遍历了窗口句柄的所有子窗口 get_child_windows(parent_window_handle): child_window_handles = [] def enum_windows_proc(hwnd, param): param.append(hwnd) return True #win32gui.EnumChildWindows 遍历窗口句柄的所有子窗口 win32gui.EnumChildWindows(parent_window_handle, enum_windows_proc, child_window_handles) return child_window_handles# 根据标题找到窗口句柄deff find_hwnd_by_title(title): all_windows = get_all_windows() #查询所有句柄 matched_windows = [] #存储所有匹配类名的句柄id # 在所有窗口中找到标题匹配的窗口句柄 for window_handle in all_windows: #get_title法 检查输入句柄对应的类名和我们的实际类名是否对应 window_title = get_title(window_handle, title) if window_title: matched_windows.append(window_title) #若对应,则写入列表 # 假如没有匹配,在所有子窗口中找到标题匹配的窗口句柄 if matched_windows: return matched_windows else: child_window_handles = [] for parent_window_handle in all_windows: #无论窗口是否有数据,都会添加到列表中 child_window_handles.extend(get_child_windows(parent_window_handle)) for child_window_handle in child_window_handles: if get_title(child_window_handle, title): matched_windows.append(get_title(child_window_handle, title)) return matched_windowsif __name__ == '__main__': hwnd = find_hwnd_by_title("Edit") print(hwnd)
我们可以看到,我们可以直接在同一类别下获得所有打开的窗口句柄,这样我们甚至可以做一个循环和多线程来实现窗口的并发效果
二、模拟按钮常见的新闻类型和标识我们已经从文本文档中获得了句柄信息。我们可以通过句柄做很多事情。最常见的是模拟鼠标和键盘的按键操作。每个操作可能都很琐碎。我们定义一个class类来存储它
#官方参考https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-lbuttondown
消息类型
作用
消息标识
作用
WM_MOUSEMOVE
鼠标 移动
移动通用左键右键标识
WM_RBUTTONDOWN
鼠标 右键按下
MK_RBUTTON
左键按下
WM_RBUTTONUP
鼠标 右键释放
None
释放时不需要标记
WM_LBUTTONDOWN
鼠标 左键按下
MK_LBUTTON
右键按下
WM_LBUTTONUP
鼠标 左键释放
None
释放时不需要标记
使用语法当按钮需要按下时,需要先声明消息类型,然后标明按钮的状态 如果需要释放鼠标按钮,可以通过释放按钮直接释放 如果指定的消息类型是移动的,可以作为声明的消息类型,可以直接使用按钮标识
#在win32api下有一个函数Postmesage,用于windows api交互的参数如下1、目标窗口发送消息的句柄2、发送的消息类型3、以及新闻的参数 win32api.PostMessage(句柄id, 消息类型, 消息标识, 具体的坐标(x,y))
获取目标坐标#获取坐标time.sleep(3)print(win32api.GetCursorPos())
返回
(328, 250)
鼠标按键案例下面定义了一个类,首先接受我们上面获得的句柄id,使用鼠标按钮时调用win32api.Postmesage函数 将按键信息发送到句柄所在的窗口
按下左右键时,需要定义标识。例如,在模拟左键时,将使用WM_LBUTONDOWN和MK_LBUTTON ,松开时使用WM_LBUTONUP和None
变量pos 它是鼠标按钮的坐标,需要win32api.MAKELONG 转换数据类型后才能调用
#声明鼠标操作类classs WinMouse(object): #初始化函数,接受输入的句柄id def __init__(self, handle_num: int): self.handle = handle_num #按下鼠标左键 def left_button_down(self, pos): win32api.PostMessage(self.handle, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, pos) #左键释放鼠标 def left_button_up(self, pos): win32api.PostMessage(self.handle, win32con.WM_LBUTTONUP, None, pos)if __name__ == '__main__': hwnd = find_hwnd_by_title("Edit") #句柄通过类名获取 bd = WinMouse(hwnd[0]) #WinMouse实例化 类,传入句柄值 pos = win32api.MAKELONG(328, 250) #将正常的x、y坐标值转换为特定的数据结构, #给win32api.Postmessage调用 #按下,等待1s、松开 bd.left_button_down(pos) time.sleep(1) bd.left_button_up(pos)
补充其他按钮可以看出,在下图中,在我们操作程序后,无论文本文档是在前台还是后台,即使被屏蔽,我们也会像往常一样点击鼠标(太多的数字看不清楚,大致是我把鼠标放在最后,程序在我上面的坐标点击左键)
#按下鼠标左键并移动 def mouse_move(self, pos): win32api.PostMessage(self.handle, win32con.WM_MOUSEMOVE, win32con.MK_LBUTTON, pos) #按下鼠标右键并移动 def right_button_move(self, pos): win32api.PostMessage(self.handle, win32con.WM_MOUSEMOVE, win32con.MK_RBUTTON, pos) #按右键指定坐标 def right_button_down(self, pos): win32api.PostMessage(self.handle, win32con.WM_RBUTTONDOWN, win32con.MK_RBUTTON, pos) #右键释放 def right_button_up(self, pos): win32api.PostMessage(self.handle, win32con.WM_RBUTTONUP, None, pos) #模拟左键双击 def left_double_click(self, x_pos:int, y_pos:int, click=2, wait=0.4): wait = wait / click #click 表示点击次数,wait是等待时间,意思是双击间隔 point = win32api.MAKELONG(x_pos, y_pos) for i in range(click): self.left_button_down(point) time.sleep(wait) self.left_button_up(point) #右键双击 def right_doubleClick(self, x, y, click=2, wait=0.4): wait = wait / click pos = win32api.MAKELONG(x, y) for i in range(click): self.right_button_down(pos) time.sleep(wait) self.right_button_up(pos)
按键组合函数上面用一个按左键需要几行,我们在这里包装
#让他直接接收x,y坐标,wait是松开按钮的间隔,一般默认 #左键单击 def left_click(self, x_pos:int, y_pos:int, wait=0.2): point = win32api.MAKELONG(x_pos, y_pos) self.left_button_down(point) time.sleep(wait) self.left_button_up(point) #右键单击 def right_click(self, x_pos:int, y_pos:int, wait=0.2): point = win32api.MAKELONG(x_pos, y_pos) self.right_button_down(point) time.sleep(wait) self.right_button_up(point) #模拟左键双击 def left_double_click(self, x_pos:int, y_pos:int, click=2, wait=0.4): wait = wait / click #click 表示点击次数,wait是等待时间,意思是双击间隔 point = win32api.MAKELONG(x_pos, y_pos) for i in range(click): self.left_button_down(point) time.sleep(wait) self.left_button_up(point) #右键双击 def right_doubleClick(self, x, y, click=2, wait=0.4): wait = wait / click pos = win32api.MAKELONG(x, y) for i in range(click): self.right_button_down(pos) time.sleep(wait) self.right_button_up(pos)
滑动和拖动鼠标添加偏移值
vi main.py
#计算鼠标从起点到目标点的偏移过程deff getPointOnLine(start_x, start_y, end_x, end_y, ratio): x = ((end_x - start_x) * ratio) + start_x y = ((end_y - start_y) * ratio) + start_y return int(round(x)), int(round(y))class WinMouse(object): def __init__(self, handle_num: int, num_of_steps=80): #添加num_um_添加num_um_of_steps=80 self.handle = handle_num self.num_of_steps = num_of_steps #添加偏移值
添加左右键拖动方法#模拟点击和拖动目标,接受两对坐标值 def left_click_move(self, x1:int, y1:int, x2:int, y2:int, wait=2): point1 = win32api.MAKELONG(x1, y1) self.left_button_down(point1) #按下鼠标左键 #在init初始化时获得我们定义的偏移值 steps = self.num_of_steps #调用上述方法返回具体,循环0-80的值 #你看这里的循环值是80,也就是说会做80个循环操作 #我们介绍了起始坐标和目标坐标,i / steps相当于从头到尾的偏移位置 #可以理解为从左上角到右下角的点 points = [getPointOnLine(x1, y1, x2, y2, i / steps) for i in range(steps)] points.append((x2, y2)) wait_time = wait / steps unique_points = list(set(points)) unique_points.sort(key=points.index) for point in unique_points: x, y = point point = win32api.MAKELONG(x, y) self.mouse_move(point) time.sleep(wait_time) self.left_button_up(point) #单击右键并滑动批量检查(与上述函数相同) def right_click_move(self, start_x, start_y, end_x, end_y, wait=2): pos = win32api.MAKELONG(start_x, start_y) self.right_button_down(pos) steps = self.num_of_steps points = [getPointOnLine(start_x, start_y, end_x, end_y, i / steps) for i in range(steps)] points.append((end_x, end_y)) time_per_step = wait / steps distinct_points = list(set(points)) distinct_points.sort(key=points.index) for point in distinct_points: x, y = point pos = win32api.MAKELONG(x, y) self.right_button_move(pos) time.sleep(time_per_step) self.right_button_up(pos)
演示bd.left_click_move(109,180,232,341)
全量代码import ctypesimport timeimport cv2import numpy as npimport win32apimport win32conimport win32guiimport win32uifrom PIL.Image import Imageimport main2#-句柄配置的分割线#获取当前主机上的所有句柄idef get_all_windows(): all_window_handles = [] # 枚举所有窗口句柄,添加到列表中 def enum_windows_proc(hwnd, param): param.append(hwnd) return True # 调用枚举窗口API win32gui.EnumWindows(enum_windows_proc, all_window_handles) return all_window_handles #返回是句柄id列表#查询输入的句柄id、类名def get_title(window_handle, class_name): #查询句柄的类名 window_class = win32gui.GetClassName(window_handle) #判断窗口类名是否与指定类名相同,如果相同,则返回窗口句柄,否则,返回空值 if window_class == class_name: return window_handle#deff遍历了窗口句柄的所有子窗口 get_child_windows(parent_window_handle): child_window_handles = [] def enum_windows_proc(hwnd, param): param.append(hwnd) return True #win32gui.EnumChildWindows 遍历窗口句柄的所有子窗口 win32gui.EnumChildWindows(parent_window_handle, enum_windows_proc, child_window_handles) return child_window_handles# 根据标题找到窗口句柄deff find_hwnd_by_title(title): all_windows = get_all_windows() #查询所有句柄 matched_windows = [] #存储所有匹配类名的句柄id # 在所有窗口中找到标题匹配的窗口句柄 for window_handle in all_windows: #get_title法 检查输入句柄对应的类名和我们的实际类名是否对应 window_title = get_title(window_handle, title) if window_title: matched_windows.append(window_title) #若对应,则写入列表 # 假如没有匹配,在所有子窗口中找到标题匹配的窗口句柄 if matched_windows: return matched_windows else: child_window_handles = [] for parent_window_handle in all_windows: #无论窗口是否有数据,都会添加到列表中 child_window_handles.extend(get_child_windows(parent_window_handle)) for child_window_handle in child_window_handles: if get_title(child_window_handle, title): matched_windows.append(get_title(child_window_handle, title)) return matched_windows#-----------------------------------------------------句柄配置的分割线def getPointOnLine(start_x, start_y, end_x, end_y, ratio): x = ((end_x - start_x) * ratio) + start_x y = ((end_y - start_y) * ratio) + start_y return int(round(x)), int(round(y))#声明鼠标操作类classs WinMouse(object): #初始化函数,接受输入的句柄id def __init__(self, handle_num: int, num_of_steps=80): self.handle = handle_num self.num_of_steps = num_of_steps #按下鼠标左键 def left_button_down(self, pos): win32api.PostMessage(self.handle, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, pos) #左键释放鼠标 def left_button_up(self, pos): win32api.PostMessage(self.handle, win32con.WM_LBUTTONUP, None, pos) #按下鼠标左键并移动 def mouse_move(self, pos): win32api.PostMessage(self.handle, win32con.WM_MOUSEMOVE, win32con.MK_LBUTTON, pos) #按下鼠标右键并移动 def right_button_move(self, pos): win32api.PostMessage(self.handle, win32con.WM_MOUSEMOVE, win32con.MK_RBUTTON, pos) #按右键指定坐标 def right_button_down(self, pos): win32api.PostMessage(self.handle, win32con.WM_RBUTTONDOWN, win32con.MK_RBUTTON, pos) #右键释放 def right_button_up(self, pos): win32api.PostMessage(self.handle, win32con.WM_RBUTTONUP, None, pos)#--------------------------------------------------------封装按键法的分割线 #让他直接接收x,y坐标,wait是松开按钮的间隔,一般默认 #左键单击 def left_click(self, x_pos:int, y_pos:int, wait=0.2): point = win32api.MAKELONG(x_pos, y_pos) self.left_button_down(point) time.sleep(wait) self.left_button_up(point) #右键单击 def right_click(self, x_pos:int, y_pos:int, wait=0.2): point = win32api.MAKELONG(x_pos, y_pos) self.right_button_down(point) time.sleep(wait) self.right_button_up(point) #模拟左键双击 def left_double_click(self, x_pos:int, y_pos:int, click=2, wait=0.4): wait = wait / click #click 表示点击次数,wait是等待时间,意思是双击间隔 point = win32api.MAKELONG(x_pos, y_pos) for i in range(click): self.left_button_down(point) time.sleep(wait) self.left_button_up(point) #右键双击 def right_doubleClick(self, x, y, click=2, wait=0.4): wait = wait / click pos = win32api.MAKELONG(x, y) for i in range(click): self.right_button_down(pos) time.sleep(wait) self.right_button_up(pos) #模拟点击和拖动目标,接受两对坐标值 #模拟点击和拖动目标,接受两对坐标值 def left_click_move(self, x1:int, y1:int, x2:int, y2:int, wait=2): point1 = win32api.MAKELONG(x1, y1) self.left_button_down(point1) #按下鼠标左键 #在init初始化时获得我们定义的偏移值 steps = self.num_of_steps #调用上述方法返回具体,循环0-80的值 #你看这里的循环值是80,也就是说会做80个循环操作 #我们介绍了起始坐标和目标坐标,i / steps相当于从头到尾的偏移位置 #可以理解为从左上角到右下角的点 points = [getPointOnLine(x1, y1, x2, y2, i / steps) for i in range(steps)] points.append((x2, y2)) wait_time = wait / steps unique_points = list(set(points)) unique_points.sort(key=points.index) for point in unique_points: x, y = point point = win32api.MAKELONG(x, y) self.mouse_move(point) time.sleep(wait_time) self.left_button_up(point) #单击右键并滑动批量检查(与上述函数相同) def right_click_move(self, start_x, start_y, end_x, end_y, wait=2): pos = win32api.MAKELONG(start_x, start_y) self.right_button_down(pos) steps = self.num_of_steps points = [getPointOnLine(start_x, start_y, end_x, end_y, i / steps) for i in range(steps)] points.append((end_x, end_y)) time_per_step = wait / steps distinct_points = list(set(points)) distinct_points.sort(key=points.index) for point in distinct_points: x, y = point pos = win32api.MAKELONG(x, y) self.right_button_move(pos) time.sleep(time_per_step) self.right_button_up(pos)if __name__ == '__main__': hwnd = find_hwnd_by_title("Edit") #句柄通过类名获取 bd = WinMouse(hwnd[0]) #WinMouse实例化 类,传入句柄值 bd.left_click_move(109,180,232,341)
三、准备opencv环境安装模块
pip install opencv-pythonpip install pyautoguipip install pillowpip install opencv-contrib-python
我这边安装opencv-python 调用cv2后,任何方法都会提示黄色报告,没有代码提示。以下是解决方案
(venv) PS C:\Users\Administrator\IdeaProjects\test> pip install opencv-pythonLooking in indexes: https://pypi.tuna.tsinghua.edu.cn/simpleRequirement already satisfied: opencv-python in c:\users\administrator\ideaprojects\test\venv\lib\site-packages (4.7.0.72)Requirement already satisfied: numpy>=1.21.2 in c:\users\administrator\ideaprojects\test\venv\lib\site-packages (from opencv-python) (1.24.3)
成功安装opencv-python模块后,我们将返回安装路径,登录此路径,进入cv2目录,并将cv2.pyd 将文件放在下面的路径下,重启编辑器
c:\users\administrator\ideaprojects\test\venv\lib\site-packages
测试语句import cv2# 加载图片img = cv2.imread('11.png', 1) #在脚本文件旁边准备一张图片# cv2显示图片.imshow('image', img)cv2.waitKey(0)cv2.destroyAllWindows()