python中可以使用 selenium庫(kù)實(shí)現(xiàn)對(duì)瀏覽器的自動(dòng)化操作谴仙,但selenium庫(kù)能操作的還是瀏覽器對(duì)象
今天我們來(lái)聊一聊如何在桌面實(shí)現(xiàn)自動(dòng)化操作坞靶。與瀏覽器頁(yè)面自動(dòng)化操作類似辽剧,桌面自動(dòng)化操作也是需要定位鼠標(biāo)在桌面的位置排截,然后根據(jù)定位的位置執(zhí)行對(duì)應(yīng)的操作娱节。
pyautogui是一個(gè)使用python的跨平臺(tái)的操作鼠標(biāo)和鍵盤的模塊挠蛉,非常方便使用。還支持一些簡(jiǎn)單的圖像識(shí)別相關(guān)操作
文末附贈(zèng)pyautogui圖像識(shí)別無(wú)法跨分辨率解決方案
安裝
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
安裝如果出現(xiàn)缺少模塊 pyHook https://zhuanlan.zhihu.com/p/143676206
使用
前置參數(shù)
import pyautogui
# 停頓功能
pyautogui.PAUSE = 1 # 調(diào)用在執(zhí)行動(dòng)作后暫停的秒數(shù)谴古,只能在執(zhí)行一些pyautogui動(dòng)作后才能使用,建議用time.sleep
# 自動(dòng) 防故障功能
pyautogui.FAILSAFE = True # 啟用自動(dòng)防故障功能悄窃,左上角的坐標(biāo)為(0讥电,0),將鼠標(biāo)移到屏幕的左上角轧抗,來(lái)拋出failSafeException異常
鼠標(biāo)操作
獲取屏幕的寬度和高度
width, height = pyautogui.size() # 獲取屏幕的寬度和高度
print(width, height)
獲取鼠標(biāo)當(dāng)前位置
currentMouseX, currentMouseY = pyautogui.position() # 鼠標(biāo)當(dāng)前位置
print(currentMouseX, currentMouseY)
鼠標(biāo)移動(dòng)類操作
# pyautogui.moveTo(x,y,持續(xù)時(shí)間) 在持續(xù)時(shí)間內(nèi) 將光標(biāo)移動(dòng)到目標(biāo)位置(x,y)
pyautogui.moveTo(100, 100, duration=0.25) # 移動(dòng)到 (100,100)
#pyautogui.moveRel(xOffset,yxOffset,持續(xù)時(shí)間) 在持續(xù)時(shí)間內(nèi) 將光標(biāo)偏移 距離原始位置 xOffset,yxOffset 的位置
pyautogui.moveRel(50, 0, duration=0.25) # 從當(dāng)前位置右移50像素
# 實(shí)現(xiàn)拖拽效果
pyautogui.mouseDown(740,73) #鼠標(biāo)按下指定位置
pyautogui.moveRel(100,0,2) #移動(dòng)/可以使用其他移動(dòng)方法
pyautogui.mouseUp() # 鼠標(biāo)抬起
#或者
pyautogui.dragTo(100,300,duration=1)
#或者
pyautogui.dragRel(100,300,duration=4)
鼠標(biāo)滾動(dòng)類操作
# scroll函數(shù)控制鼠標(biāo)滾輪的滾動(dòng)恩敌,amount_to_scroll參數(shù)表示滾動(dòng)的格數(shù)。正數(shù)則頁(yè)面向上滾動(dòng)横媚,負(fù)數(shù)則向下滾動(dòng)
# pyautogui.scroll(clicks=amount_to_scroll, x=moveToX, y=moveToY)
# 默認(rèn)從當(dāng)前光標(biāo)位置進(jìn)行滑動(dòng) amount_to_scroll是個(gè)數(shù)字 數(shù)字太小效果可能不明顯, 正數(shù)表示往上劃 負(fù)數(shù)表示往下化
pyautogui.scroll(500, 20, 2)
pyautogui.scroll(100) # 向上滾動(dòng)100格
pyautogui.scroll(-100) # 向下滾動(dòng)100格
pyautogui.scroll(100, x=100, y=100) # 移動(dòng)到(100, 100)位置再向上滾動(dòng)100格
鼠標(biāo)點(diǎn)擊類操作
# pyautogui.click(x,y,clicks=點(diǎn)擊次數(shù),interval=每次點(diǎn)擊間隔頻率,button=可以是left表示左擊 可以是right表示右擊 可以是middle表示中擊)
pyautogui.click(10, 20, 2, 0.25, button='left')
pyautogui.click(x=100, y=200, duration=2) # 先移動(dòng)到(100, 200)再單擊
pyautogui.click() # 鼠標(biāo)當(dāng)前位置點(diǎn)擊一下
pyautogui.doubleClick() # 鼠標(biāo)當(dāng)前位置左擊兩下
pyautogui.doubleClick(x=100, y=150, button="left") # 鼠標(biāo)在(100纠炮,150)位置左擊兩下
pyautogui.tripleClick() # 鼠標(biāo)當(dāng)前位置左擊三下
pyautogui.rightClick(10,10) # 指定位置,雙擊右鍵
pyautogui.middleClick(10,10) # 指定位置灯蝴,雙擊中鍵
鍵盤操作
# 相關(guān)操作
# pyautogui.typewrite(要輸入的字符只能是英文,interval=輸入每個(gè)字符的間隔頻率)
pyautogui.typewrite('python', 1)
# typewrite 還可以傳入單字母的列表
# 運(yùn)行下面代碼恢口,編輯器里面就會(huì)輸出 python 之后換行。
pyautogui.typewrite(['p','y','t','h','o','n','enter'])
# pyautogui.keyDown():模擬按鍵按下
# pyautogui.keyUP():模擬按鍵松開
# pyautogui.press(鍵盤按鍵字母) 模擬一次按鍵過(guò)程穷躁,即 keyDown 和 keyUP 的組合 按下指定的鍵盤按鍵
# pyautogui.hotkey("ctrl","a") 實(shí)現(xiàn)組合鍵功能
# 按住 shift 按鍵耕肩,然后再按住 1 按鍵,就可以了。用 pyautogui 控制就是
pyautogui.keyDown('shift')
pyautogui.press('1')
pyautogui.keyUp('shift')
# 輸入中文字符的方法 借用 pyperclip模塊
import pyperclip
pyperclip.copy("要書寫的字符串") #復(fù)制字符串
time.sleep(2)
pyautogui.hotkey("ctrl","v") #實(shí)現(xiàn)復(fù)制
# pyautogui.KEYBOARD_KEYS數(shù)組中就是press()猿诸,keyDown()婚被,keyUp()和hotkey()函數(shù)可以輸入的按鍵名稱
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
# 顯示一個(gè)簡(jiǎn)單的帶文字和OK按鈕的消息彈窗。用戶點(diǎn)擊后返回button的文字梳虽。
pyautogui.alert(text='', title='', button='OK')
b = pyautogui.alert(text='要開始程序么址芯?', title='請(qǐng)求框', button='OK')
print(b) # 輸出結(jié)果為OK
# 顯示一個(gè)簡(jiǎn)單的帶文字、OK和Cancel按鈕的消息彈窗窜觉,用戶點(diǎn)擊后返回被點(diǎn)擊button的文字谷炸,支持自定義數(shù)字、文字的列表禀挫。
pyautogui.confirm(text='', title='', buttons=['OK', 'Cancel']) # OK和Cancel按鈕的消息彈窗
pyautogui.confirm(text='', title='', buttons=range(10)) # 10個(gè)按鍵0-9的消息彈窗
a = pyautogui.confirm(text='', title='', buttons=range(10))
print(a) # 輸出結(jié)果為你選的數(shù)字
# 可以輸入的消息彈窗旬陡,帶OK和Cancel按鈕。用戶點(diǎn)擊OK按鈕返回輸入的文字语婴,點(diǎn)擊Cancel按鈕返回None季惩。
pyautogui.prompt(text='', title='', default='')
# 樣式同prompt(),用于輸入密碼腻格,消息用*表示。帶OK和Cancel按鈕啥繁。用戶點(diǎn)擊OK按鈕返回輸入的文字菜职,點(diǎn)擊Cancel按鈕返回None。
pyautogui.password(text='', title='', default='', mask='*')
圖像操作
import pyautogui
im = pyautogui.screenshot() # 返回屏幕的截圖旗闽,是一個(gè)Pillow的image對(duì)象
im.save('屏幕截圖.png') #保存圖片
# 或者
im = pyautogui.screenshot('屏幕截圖.png') # 截全屏并設(shè)置保存圖片的位置和名稱
print(im) # 打印圖片的屬性
# 不截全屏酬核,截取區(qū)域圖片。截取區(qū)域region參數(shù)為:左上角XY坐標(biāo)值适室、寬度和高度
pyautogui.screenshot('屏幕截圖.png', region=(0, 0, 300, 400))
# 獲得文件圖片在現(xiàn)在的屏幕上面的坐標(biāo)嫡意,返回的是一個(gè)元組(top, left, width, height)
# 如果截圖沒找到,pyautogui.locateOnScreen()函數(shù)返回None
a = pyautogui.locateOnScreen(r'目標(biāo)圖片路徑')
print(a) # 打印結(jié)果為Box(left=0, top=0, width=300, height=400)
x, y = pyautogui.center(a) # 獲得文件圖片在現(xiàn)在的屏幕上面的中心坐標(biāo)
print(x, y) # 打印結(jié)果為150 200
# 或者
x, y = pyautogui.locateCenterOnScreen(r'目標(biāo)圖片路徑') # 這步與上面的四行代碼作用一樣
print(x, y) # 打印結(jié)果為150 200
# 匹配屏幕所有與目標(biāo)圖片的對(duì)象捣辆,可以用for循環(huán)和list()輸出
for pos in pyautogui.locateAllOnScreen(r'C:\Users\ZDH\Desktop\PY\region_screenshot.png'):
print(pos)
# 打印結(jié)果為Box(left=0, top=0, width=300, height=400)
a = list(pyautogui.locateAllOnScreen(r'C:\Users\ZDH\Desktop\PY\region_screenshot.png'))
print(a) # 打印結(jié)果為[Box(left=0, top=0, width=300, height=400)]
注意:pyautogui的圖像識(shí)別是模板匹配算法 無(wú)法跨分辨率識(shí)別(圖片放大縮小就無(wú)法識(shí)別) 提供以下圖像識(shí)別算法
# -*- coding: utf-8 -*-
"""
使用需求:
需要安裝 airtest pip install airtest -i https://mirrors.aliyun.com/pypi/simple/
運(yùn)行時(shí)如果出現(xiàn)以下錯(cuò)誤:
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)
# 日志級(jí)別有[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: 識(shí)別結(jié)果是否使用rgb三通道進(jìn)行校驗(yàn).
scale_max: 多尺度模板匹配最大范圍.
scale_step: 多尺度模板匹配搜索步長(zhǎng).
"""
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):
"""模板匹配中旧巾,將輸入的截圖適配成 等待模板匹配的截圖."""
# 未記錄錄制分辨率,跳過(guò)
if not self.resolution:
return image
screen_resolution = aircv.get_resolution(screen)
# 如果分辨率一致忍些,則不需要進(jìn)行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__
# 分辨率不一致則進(jìn)行適配鲁猩,默認(rèn)使用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個(gè)像素:
w_re, h_re = max(1, w_re), max(1, h_re)
# 調(diào)試代碼: 輸出調(diào)試信息.
G.LOGGING.debug("resize: (%s, %s)->(%s, %s), resolution: %s=>%s" % (
w, h, w_re, h_re, self.resolution, screen_resolution))
# 進(jìn)行圖片縮放:
image = cv2.resize(image, (w_re, h_re))
return image
if __name__ == '__main__':
"""
用法:
res = Template(目標(biāo)圖片路徑,threshold=匹配閾值,target_pos=可以是123456789 分別對(duì)應(yīng)圖片的九個(gè)點(diǎn)).match_in(screenshot(None))
"""
res = Template("pppp.png",threshold=0.8,target_pos=5).match_in(screenshot(None))
print(res)