python中可以使用 selenium库实现对浏览器的自动化操作,但selenium库能操作的还是浏览器对象
今天我们来聊一聊如何在桌面实现自动化操作。与浏览器页面自动化操作类似,桌面自动化操作也是需要定位鼠标在桌面的位置,然后根据定位的位置执行对应的操作。
pyautogui是一个使用python的跨平台的操作鼠标和键盘的模块,非常方便使用。还支持一些简单的图像识别相关操作
文末附赠pyautogui图像识别无法跨分辨率解决方案
安装
pip install pyautogui
提示 ModuleNotFoundError: No module named 'win32api'
需要安装pip install pypiwin32
提示import win32api, win32con ImportError: DLL load failed: 找不到指定的程序。
需要 pip install pywin32==227
不行就 pip install pywin32==223
安装如果出现缺少模块 pyHook https://zhuanlan.zhihu.com/p/143676206
使用
前置参数
import pyautogui
# 停顿功能
pyautogui.PAUSE = 1 # 调用在执行动作后暂停的秒数,只能在执行一些pyautogui动作后才能使用,建议用time.sleep
# 自动 防故障功能
pyautogui.FAILSAFE = True # 启用自动防故障功能,左上角的坐标为(0,0),将鼠标移到屏幕的左上角,来抛出failSafeException异常
鼠标操作
获取屏幕的宽度和高度
width, height = pyautogui.size() # 获取屏幕的宽度和高度
print(width, height)
获取鼠标当前位置
currentMouseX, currentMouseY = pyautogui.position() # 鼠标当前位置
print(currentMouseX, currentMouseY)
鼠标移动类操作
# pyautogui.moveTo(x,y,持续时间) 在持续时间内 将光标移动到目标位置(x,y)
pyautogui.moveTo(100, 100, duration=0.25) # 移动到 (100,100)
#pyautogui.moveRel(xOffset,yxOffset,持续时间) 在持续时间内 将光标偏移 距离原始位置 xOffset,yxOffset 的位置
pyautogui.moveRel(50, 0, duration=0.25) # 从当前位置右移50像素
# 实现拖拽效果
pyautogui.mouseDown(740,73) #鼠标按下指定位置
pyautogui.moveRel(100,0,2) #移动/可以使用其他移动方法
pyautogui.mouseUp() # 鼠标抬起
#或者
pyautogui.dragTo(100,300,duration=1)
#或者
pyautogui.dragRel(100,300,duration=4)
鼠标滚动类操作
# scroll函数控制鼠标滚轮的滚动,amount_to_scroll参数表示滚动的格数。正数则页面向上滚动,负数则向下滚动
# pyautogui.scroll(clicks=amount_to_scroll, x=moveToX, y=moveToY)
# 默认从当前光标位置进行滑动 amount_to_scroll是个数字 数字太小效果可能不明显, 正数表示往上划 负数表示往下化
pyautogui.scroll(500, 20, 2)
pyautogui.scroll(100) # 向上滚动100格
pyautogui.scroll(-100) # 向下滚动100格
pyautogui.scroll(100, x=100, y=100) # 移动到(100, 100)位置再向上滚动100格
鼠标点击类操作
# pyautogui.click(x,y,clicks=点击次数,interval=每次点击间隔频率,button=可以是left表示左击 可以是right表示右击 可以是middle表示中击)
pyautogui.click(10, 20, 2, 0.25, button='left')
pyautogui.click(x=100, y=200, duration=2) # 先移动到(100, 200)再单击
pyautogui.click() # 鼠标当前位置点击一下
pyautogui.doubleClick() # 鼠标当前位置左击两下
pyautogui.doubleClick(x=100, y=150, button="left") # 鼠标在(100,150)位置左击两下
pyautogui.tripleClick() # 鼠标当前位置左击三下
pyautogui.rightClick(10,10) # 指定位置,双击右键
pyautogui.middleClick(10,10) # 指定位置,双击中键
键盘操作
# 相关操作
# pyautogui.typewrite(要输入的字符只能是英文,interval=输入每个字符的间隔频率)
pyautogui.typewrite('python', 1)
# typewrite 还可以传入单字母的列表
# 运行下面代码,编辑器里面就会输出 python 之后换行。
pyautogui.typewrite(['p','y','t','h','o','n','enter'])
# pyautogui.keyDown():模拟按键按下
# pyautogui.keyUP():模拟按键松开
# pyautogui.press(键盘按键字母) 模拟一次按键过程,即 keyDown 和 keyUP 的组合 按下指定的键盘按键
# pyautogui.hotkey("ctrl","a") 实现组合键功能
# 按住 shift 按键,然后再按住 1 按键,就可以了。用 pyautogui 控制就是
pyautogui.keyDown('shift')
pyautogui.press('1')
pyautogui.keyUp('shift')
# 输入中文字符的方法 借用 pyperclip模块
import pyperclip
pyperclip.copy("要书写的字符串") #复制字符串
time.sleep(2)
pyautogui.hotkey("ctrl","v") #实现复制
# pyautogui.KEYBOARD_KEYS数组中就是press(),keyDown(),keyUp()和hotkey()函数可以输入的按键名称
pyautogui.KEYBOARD_KEYS = ['\t', '\n', '\r', ' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.',
'/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@',
'[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
'accept', 'add', 'alt', 'altleft', 'altright', 'apps', 'backspace', 'browserback',
'browserfavorites', 'browserforward', 'browserhome', 'browserrefresh', 'browsersearch',
'browserstop', 'capslock', 'clear', 'convert', 'ctrl', 'ctrlleft', 'ctrlright', 'decimal',
'del', 'delete', 'divide', 'down', 'end', 'enter', 'esc', 'escape', 'execute', 'f1', 'f10',
'f11', 'f12', 'f13', 'f14', 'f15', 'f16', 'f17', 'f18', 'f19', 'f2', 'f20', 'f21', 'f22',
'f23', 'f24', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'final', 'fn', 'hanguel', 'hangul',
'hanja', 'help', 'home', 'insert', 'junja', 'kana', 'kanji', 'launchapp1', 'launchapp2',
'launchmail', 'launchmediaselect', 'left', 'modechange', 'multiply', 'nexttrack',
'nonconvert', 'num0', 'num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7', 'num8', 'num9',
'numlock', 'pagedown', 'pageup', 'pause', 'pgdn', 'pgup', 'playpause', 'prevtrack', 'print',
'printscreen', 'prntscrn', 'prtsc', 'prtscr', 'return', 'right', 'scrolllock', 'select',
'separator', 'shift', 'shiftleft', 'shiftright', 'sleep', 'space', 'stop', 'subtract', 'tab',
'up', 'volumedown', 'volumemute', 'volumeup', 'win', 'winleft', 'winright', 'yen', 'command',
'option', 'optionleft', 'optionright']
弹窗操作
import pyautogui
# 显示一个简单的带文字和OK按钮的消息弹窗。用户点击后返回button的文字。
pyautogui.alert(text='', title='', button='OK')
b = pyautogui.alert(text='要开始程序么?', title='请求框', button='OK')
print(b) # 输出结果为OK
# 显示一个简单的带文字、OK和Cancel按钮的消息弹窗,用户点击后返回被点击button的文字,支持自定义数字、文字的列表。
pyautogui.confirm(text='', title='', buttons=['OK', 'Cancel']) # OK和Cancel按钮的消息弹窗
pyautogui.confirm(text='', title='', buttons=range(10)) # 10个按键0-9的消息弹窗
a = pyautogui.confirm(text='', title='', buttons=range(10))
print(a) # 输出结果为你选的数字
# 可以输入的消息弹窗,带OK和Cancel按钮。用户点击OK按钮返回输入的文字,点击Cancel按钮返回None。
pyautogui.prompt(text='', title='', default='')
# 样式同prompt(),用于输入密码,消息用*表示。带OK和Cancel按钮。用户点击OK按钮返回输入的文字,点击Cancel按钮返回None。
pyautogui.password(text='', title='', default='', mask='*')
图像操作
import pyautogui
im = pyautogui.screenshot() # 返回屏幕的截图,是一个Pillow的image对象
im.save('屏幕截图.png') #保存图片
# 或者
im = pyautogui.screenshot('屏幕截图.png') # 截全屏并设置保存图片的位置和名称
print(im) # 打印图片的属性
# 不截全屏,截取区域图片。截取区域region参数为:左上角XY坐标值、宽度和高度
pyautogui.screenshot('屏幕截图.png', region=(0, 0, 300, 400))
# 获得文件图片在现在的屏幕上面的坐标,返回的是一个元组(top, left, width, height)
# 如果截图没找到,pyautogui.locateOnScreen()函数返回None
a = pyautogui.locateOnScreen(r'目标图片路径')
print(a) # 打印结果为Box(left=0, top=0, width=300, height=400)
x, y = pyautogui.center(a) # 获得文件图片在现在的屏幕上面的中心坐标
print(x, y) # 打印结果为150 200
# 或者
x, y = pyautogui.locateCenterOnScreen(r'目标图片路径') # 这步与上面的四行代码作用一样
print(x, y) # 打印结果为150 200
# 匹配屏幕所有与目标图片的对象,可以用for循环和list()输出
for pos in pyautogui.locateAllOnScreen(r'C:\Users\ZDH\Desktop\PY\region_screenshot.png'):
print(pos)
# 打印结果为Box(left=0, top=0, width=300, height=400)
a = list(pyautogui.locateAllOnScreen(r'C:\Users\ZDH\Desktop\PY\region_screenshot.png'))
print(a) # 打印结果为[Box(left=0, top=0, width=300, height=400)]
注意:pyautogui的图像识别是模板匹配算法 无法跨分辨率识别(图片放大缩小就无法识别) 提供以下图像识别算法
# -*- coding: utf-8 -*-
"""
使用需求:
需要安装 airtest pip install airtest -i https://mirrors.aliyun.com/pypi/simple/
运行时如果出现以下错误:
import win32api
ImportError: DLL load failed: 找不到指定的程序。
重新安装win32api版本
pip install pywin32==227 # 安装 227版本
不行的话再试试
pip install pywin32==223 # 安装 223版本
"""
import sys
import types
from copy import deepcopy
from airtest import aircv
from airtest.aircv import cv2
from airtest.aircv.template_matching import TemplateMatching
from airtest.core.cv import MATCHING_METHODS, Predictor
from airtest.core.error import InvalidMatchingMethodError
from airtest.core.helper import logwrap, G
from airtest.core.win.screen import screenshot
from airtest.utils.transform import TargetPos
from six import PY3
from airtest.core.settings import Settings as ST # noqa
# # -*- encoding=utf8 -*-
import logging
logger = logging.getLogger("airtest")
logger.setLevel(logging.ERROR)
# 日志级别有[DEBUG]、[INFO]、[WARNING] 和 [ERROR]
class Template(object):
"""
picture as touch/swipe/wait/exists target and extra info for cv match
filename: pic filename
target_pos: ret which pos in the pic
record_pos: pos in screen when recording
resolution: screen resolution when recording
rgb: 识别结果是否使用rgb三通道进行校验.
scale_max: 多尺度模板匹配最大范围.
scale_step: 多尺度模板匹配搜索步长.
"""
def __init__(self, filename, threshold=None, target_pos=TargetPos.MID, record_pos=None, resolution=(), rgb=False, scale_max=800, scale_step=0.005):
self.filename = filename
# self.filename =os.path.join(Settings.Picture_Path,filename)
self._filepath = None
self.threshold = threshold or ST.THRESHOLD
self.target_pos = target_pos
self.record_pos = record_pos
self.resolution = resolution
self.rgb = rgb
self.scale_max = scale_max
self.scale_step = scale_step
@property
def filepath(self):
return self.filename
def __repr__(self):
filepath = self.filepath if PY3 else self.filepath.encode(sys.getfilesystemencoding())
return "Template(%s)" % filepath
def match_in(self, screen):
match_result = self._cv_match(screen)
G.LOGGING.debug("match result: %s", match_result)
if not match_result:
return None
focus_pos = TargetPos().getXY(match_result, self.target_pos)
return focus_pos
def match_all_in(self, screen):
image = self._imread()
image = self._resize_image(image, screen, ST.RESIZE_METHOD)
return self._find_all_template(image, screen)
@logwrap
def _cv_match(self, screen):
# in case image file not exist in current directory:
ori_image = self._imread()
image = self._resize_image(ori_image, screen, ST.RESIZE_METHOD)
ret = None
for method in ST.CVSTRATEGY:
# get function definition and execute:
func = MATCHING_METHODS.get(method, None)
if func is None:
raise InvalidMatchingMethodError("Undefined method in CVSTRATEGY: '%s', try 'kaze'/'brisk'/'akaze'/'orb'/'surf'/'sift'/'brief' instead." % method)
else:
if method in ["mstpl", "gmstpl"]:
ret = self._try_match(func, ori_image, screen, threshold=self.threshold, rgb=self.rgb, record_pos=self.record_pos,
resolution=self.resolution, scale_max=self.scale_max, scale_step=self.scale_step)
else:
ret = self._try_match(func, image, screen, threshold=self.threshold, rgb=self.rgb)
if ret:
break
return ret
@staticmethod
def _try_match(func, *args, **kwargs):
G.LOGGING.debug("try match with %s" % func.__name__)
try:
ret = func(*args, **kwargs).find_best_result()
except aircv.NoModuleError as err:
G.LOGGING.warning("'surf'/'sift'/'brief' is in opencv-contrib module. You can use 'tpl'/'kaze'/'brisk'/'akaze'/'orb' in CVSTRATEGY, or reinstall opencv with the contrib module.")
return None
except aircv.BaseError as err:
G.LOGGING.debug(repr(err))
return None
else:
return ret
def _imread(self):
return aircv.imread(self.filepath)
def _find_all_template(self, image, screen):
return TemplateMatching(image, screen, threshold=self.threshold, rgb=self.rgb).find_all_results()
def _find_keypoint_result_in_predict_area(self, func, image, screen):
if not self.record_pos:
return None
# calc predict area in screen
image_wh, screen_resolution = aircv.get_resolution(image), aircv.get_resolution(screen)
xmin, ymin, xmax, ymax = Predictor.get_predict_area(self.record_pos, image_wh, self.resolution, screen_resolution)
# crop predict image from screen
predict_area = aircv.crop_image(screen, (xmin, ymin, xmax, ymax))
if not predict_area.any():
return None
# keypoint matching in predicted area:
ret_in_area = func(image, predict_area, threshold=self.threshold, rgb=self.rgb)
# calc cv ret if found
if not ret_in_area:
return None
ret = deepcopy(ret_in_area)
if "rectangle" in ret:
for idx, item in enumerate(ret["rectangle"]):
ret["rectangle"][idx] = (item[0] + xmin, item[1] + ymin)
ret["result"] = (ret_in_area["result"][0] + xmin, ret_in_area["result"][1] + ymin)
return ret
def _resize_image(self, image, screen, resize_method):
"""模板匹配中,将输入的截图适配成 等待模板匹配的截图."""
# 未记录录制分辨率,跳过
if not self.resolution:
return image
screen_resolution = aircv.get_resolution(screen)
# 如果分辨率一致,则不需要进行im_search的适配:
if tuple(self.resolution) == tuple(screen_resolution) or resize_method is None:
return image
if isinstance(resize_method, types.MethodType):
resize_method = resize_method.__func__
# 分辨率不一致则进行适配,默认使用cocos_min_strategy:
h, w = image.shape[:2]
w_re, h_re = resize_method(w, h, self.resolution, screen_resolution)
# 确保w_re和h_re > 0, 至少有1个像素:
w_re, h_re = max(1, w_re), max(1, h_re)
# 调试代码: 输出调试信息.
G.LOGGING.debug("resize: (%s, %s)->(%s, %s), resolution: %s=>%s" % (
w, h, w_re, h_re, self.resolution, screen_resolution))
# 进行图片缩放:
image = cv2.resize(image, (w_re, h_re))
return image
if __name__ == '__main__':
"""
用法:
res = Template(目标图片路径,threshold=匹配阈值,target_pos=可以是123456789 分别对应图片的九个点).match_in(screenshot(None))
"""
res = Template("pppp.png",threshold=0.8,target_pos=5).match_in(screenshot(None))
print(res)