目錄
- 一、 pywinauto安裝和啟動
- 1.安裝:
- 2.backend選擇 和 控制元件檢視工具inspect介紹
- 2.啟動(例項化程式):以微信示例
- 3.Application物件app的常用方法
- 二、控制元件定位方法和控制元件可用方法
- 2.1 層級查詢控制元件的方法
- 2.2 kwargs篩選條件
- 2.3 控制元件可用的方法屬性
- 三、具體使用舉例
- 1.對話方塊dialog選擇
- 2.列印元素
- 3 常用查詢方法
- 4 快速定位
- 四、控制元件自帶的的方法
- 1. 點選和輸入
- 2.對控制元件截圖並儲存
- 3.視窗的等待
- 4.視窗存在和關閉
- 5.其他
- 五、滑鼠操作
- 六、鍵盤操作
一、 pywinauto安裝和啟動
1.安裝:
pip install pywinauto
2.backend選擇 和 控制元件檢視工具inspect介紹
我們安裝好Pywinauto之後,首先要確定哪種可訪問性技術(backend)可以用於我們的應用程式,在windows上受支援的有兩種:
-
Win32 API (
backend= "win32"
) 預設的backend -
MS UI Automation (
backend="uia"
)
如果不能確定程式到底適用於那種backend,可以藉助於GUI物件檢查工具來做,常用的檢查工具有Inspect.ex,Spy++ ,下載地址:https://github.com/blackrosezy/gui-inspect-tool
giithub的專案中的inspect好像不行了,可以用下面這個:
連結:https://pan.baidu.com/s/1LHvbcP5NKqSHC7FLSpiTFQ
提取碼:p4hm
將inspect左上角的下拉選單中切換到“UI Automation”,然後滑鼠點一下你需要測試的程式窗體,inspect就會顯示相關資訊,如下圖所示。說明backend為uia
程式裡面的任意一個部位其實都是控制元件,在inspect的控制元件樹中都可以找到,是一層一層分級別的,可以一個個點開所有控制元件
2.啟動(例項化程式):以微信示例
from pywinauto.application import Application # 常用方式一:連線已有微信程序(程序號在 工作管理員-詳細資訊 可以檢視,專案中一般根據程序名稱自動獲取) app = Application(backend='uia').connect(process=8948) # 常用方式二:啟動微信程序 (注意路徑中特殊字元的轉義,/和\,不注意有時會出錯) app = Application(backend="uia").start(r'C:\Program Files (x86)\Tencent\WeChat\WeChat.exe')
3.Application物件app的常用方法
透過檢視pywinauto的原始碼中application.py檔案,可以看到app的所有屬性方法,下面列舉常用方法:
app.top_window() # 返回應用程式當前頂部視窗,是WindowSpecification物件,可以繼續使用物件的方法往下繼續查詢控制元件 # eg:如:app.top_window().child_window(title='地址和搜尋欄', control_type='Edit') app.window(**kwargs) # 根據篩選條件,返回一個視窗, 是WindowSpecification物件,可以繼續適用物件的方法往下繼續查詢控制元件 # eg: 微信主介面 app.window(class_name='WeChatMainWndForPC') app.windows(**kwargs) # 根據篩選條件返回一個視窗列表,無條件預設全部,列表項為wrapped物件,可以使用wrapped物件的方法,注意不是WindowSpecification物件 # eg:[<uiawrapper.UIAWrapper - '李渝的早報 - Google Chrome', Pane, -2064264099699444098>] app.kill(soft=False) # 強制關閉 app.cpu_usage() # 返回指定秒數期間的CPU使用率百分比 app.wait_cpu_usage_lower(threshold=2.5, timeout=None, usage_interval=None) # 等待程序CPU使用率百分比小於指定的閾值threshold app.is64bit() # 如果操作的程序是64-bit,返回True
二、控制元件定位方法和控制元件可用方法
操作控制元件需要以下幾個步驟:
第一步 例項化要操作的程序:得到的app是Application物件
第二步 選擇視窗 :app.window('一個或多個篩選條件') 得到的視窗是WindowSpecification物件
第三步:基於WindowSpecification物件使用其方法再往下查詢,定位到具體的控制元件
第四步:使用控制元件的方法屬性執行我們需要的操作
WindowSpecification原始碼中有一些自帶的方法可以直接使用,也有註釋說到:
就是說這是一個查詢空間或者視窗的規範,可以使用等待機制。
並且該物件中__getattribute__和__getitem__兩個魔術方法,隱式地記錄一些私有方法
我的理解是我們可以繼續往下一層一層的查詢,下面一層一層的控制元件其實是各種各樣的wrapper物件,wrapper有很多種是一系列物件,物件原始碼都在pywinauto原始碼的controls目錄中
以下總結了常用方法,基本可以滿足所有場景的操作,如下:
2.1 層級查詢控制元件的方法
# 透過層級查詢控制元件相關方法+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ window(**kwargs) # 用於視窗的查詢 child_window(**kwargs) # 可以不管層級的找後代中某個符合條件的元素,最常用 parent() # 返回此元素的父元素,沒有引數 children(**kwargs) # 返回符合條件的子元素列表,支援索引,是BaseWrapper物件(或子類) iter_children(**kwargs) # 返回子元素的迭代器,是BaseWrapper物件(或子類) descendants(**kwargs) # 返回符合條件的所有後代元素列表,是BaseWrapper物件(或子類) iter_children(**kwargs) # 符合條件後代元素迭代器,是BaseWrapper物件(或子類)
2.2 kwargs篩選條件
常用的一些篩選條件:
# 這些是常用的 class_name=None, # 類名 class_name_re=None, # 正則匹配類名 title=None, # 控制元件的標題文字,對應inspect中Name欄位 title_re=None, # 正則匹配文字 control_type=None, # 控制元件型別,inspect介面LocalizedControlType欄位的英文名 best_match=None, # 這個有坑,我不喜歡用,下文有講解 auto_id=None, # 這個也是固定的可以用,inspect介面AutomationId欄位,但是很多控制元件沒有這個屬性
2.3 控制元件可用的方法屬性
# 以下幾個只支援視窗模式的控制元件======================================================================= dlg.close() # 關閉介面 dlg.minimize() # 最小化介面 dlg.maximize() # 最大化介面 dlg.restore() # 將視窗恢復為正常大小,比如最小化的讓他正常顯示在桌面 dlg.get_show_state() # 正常0,最大化1,最小化2 dlg.exists(timeout=None, retry_interval=None) # 判斷是否存在 #timeout:等待時間,一般預設5s #retry_interval:timeout內重試時間 dlg.wait(wait_for, timeout=None, retry_interval=None) # 等待視窗處於特定狀態 dlg.wait_not(wait_for_not, timeout=None, retry_interval=None) # 等待視窗不處於特定狀態,即等待消失 # wait_for/wait_for_not: # * 'exists' means that the window is a valid handle # * 'visible' means that the window is not hidden # * 'enabled' means that the window is not disabled # * 'ready' means that the window is visible and enabled # * 'active' means that the window is active # timeout:等待多久 # retry_interval:timeout內重試時間 # eg: dlg.wait('ready') # 滑鼠鍵盤操作 ===================================================================================== # 我只列舉常用形式,他們有很多預設引數但不常用,可以在原始碼中檢視 ctrl.click_input() # 最常用的點選方法,一切點選操作的基本方法(底層呼叫只是引數不同),左鍵單擊,使用時一般都使用預設不需要帶引數 ctrl.right_click_input() # 滑鼠右鍵單擊 # 鍵盤輸入,底層還是呼叫keyboard.send_keys ctrl.type_keys(keys, pause = None, with_spaces = False,) # keys:要輸入的文字內容 # pause:每輸入一個字元後等待時間,預設0.01就行 # with_spaces:是否保留keys中的所有空格,預設去除0 ctrl.double_click_input(button ="left", coords = (None, None)) # 左鍵雙擊 ctrl.press_mouse_input(coords = (None, None)) # 指定座標按下左鍵,不傳座標預設左上角 ctrl.release_mouse_input(coords = (None, None)) # 指定座標釋放左鍵,不傳座標預設左上角 ctrl.move_mouse_input(coords=(0, 0)) # 將滑鼠移動到指定座標,不傳座標預設左上角 ctrl.drag_mouse_input(dst=(0, 0)) # 將ctrl拖動到dst,是press-move-release操作集合 # 控制元件的常用屬性=================================================================================== ctrl.children_texts() # 所有子控制元件的文字列表,對應inspect中Name欄位 ctrl.window_text() # 控制元件的標題文字,對應inspect中Name欄位 # ctrl.element_info.name ctrl.class_name() # 控制元件的類名,對應inspect中ClassName欄位,有些控制元件沒有類名 # ctrl.element_info.class_name ctrl.element_info.control_type # 控制元件型別,inspect介面LocalizedControlType欄位的英文名 ctrl.is_child(parent) # ctrl是否是parent的子控制元件
ctrl.legacy_properties().get('Value') # 可以獲取inspect介面LegacyIAccessible開頭的一系列欄位,在原始碼uiawraper.py中找到了這個方法,非常有用
#如某些按鈕顯示值是我們想要的,但是window_text獲取到的是固定文字‘修改群暱稱’,這個值才是我們修改後的新名字
# 控制元件常用操作======================================================================================== ctrl.draw_outline(colour='green') # 空間外圍畫框,便於檢視,支援'red', 'green', 'blue' ctrl.print_control_identifiers(depth=None, filename=None) # 列印其包含的元素,詳見列印元素 ctrl.scroll(direction, amount, count=1,) # 滾動 # direction :"up", "down", "left", "right" # amount:"line" or "page" # count:int 滾動次數 ctrl.capture_as_image() # 返回控制元件的 PIL image物件,可繼續使用其方法如下: eg: ctrl.capture_as_image().save(img_path) ret = ctrl.rectangle() # 控制元件上下左右座標,(L430, T177, R1490, B941),可.輸出上下左右 eg: ret.top=177 ret.bottom=941 ret.left=430 ret.right=1490
三、具體使用舉例
第二節中列舉了能用到的方法屬性,本節列舉實際操作中的具體用法
1.對話方塊dialog選擇
根據pywinauto的原始碼中application.py檔案介紹,視窗選擇有三種方式:
以微信主介面視窗為例:
# 微信主介面幾種方式: # 這個最好用,下面幾種不指名道姓容易出錯且速度很慢 dlg1 = app.window(class_name='WeChatMainWndForPC') # 是WindowSpecification物件 # 下面幾種方法速度慢,我是不喜歡用 # dlg2_1 = app.Dialog # dlg2_2 = app.微信 # dlg3_1 = app['Dialog'] # dlg3_2 = app['微信']
2.列印元素
我們拿到控制元件後,是可以將該控制元件下的所有子控制元件及其屬性以樹形結構列印出來的:
# 拿到微信主視窗 win_main_Dialog = app.window(class_name='WeChatMainWndForPC') # 判斷是否為dialog,一個微信是一個dialog,就是視窗 print(win_main_Dialog.is_dialog) # 給控制元件畫個紅色框便於看出是哪個 win_main_Dialog.draw_outline(colour = 'red') # 列印當前視窗的所有controller(控制元件和屬性) win_main_Dialog. print_control_identifiers(depth=None, filename=None) # 原始碼內部函式名鏈式賦值了,都能用,一樣的 # print_ctrl_ids = dump_tree = print_control_identifiers
depth:列印的深度,預設時列印最大深度。
filename:將返回的標識存成檔案(生成的檔案與當前執行的指令碼在同一個路徑下)
eg:dlg. print_control_identifiers(filename =’a.txt’)
列印出來的文件樹就是inspect中的控制元件樹完全展開的樣子,都是有層級的,和微信程式中的各個元素是一一對應的:
3 常用查詢方法
# 拿到微信主視窗 win_main_Dialog = app.window(class_name='WeChatMainWndForPC') # 主視窗下的某個視窗,不管層級的找 chat_list = win_main_Dialog.child_window(control_type='List', title='會話') first = chat_list.items()[0] # 第一個聊天項 列表支援items(),支援迴圈,支援索引 # 詳情頁修改備註操作 parent()和children()都是隻往上或往下查詢一個層級,所有滿足的放進列表 details_page = win_main_Dialog.child_window(class_name='ContactProfileWnd') # 視窗下的某個視窗 we_id = details_page.child_window(title="微訊號:", control_type="Text").parent().children()[1].window_text() # 視窗的爸爸的第二個兒子的文字 alia = details_page.child_window(title="微訊號:", control_type="Text").parent().parent().children()[0].children()[0].window_text() edit_btn = details_page.child_window(title="備 注", control_type="Text").parent().children()[1] edit_btn.click_input() btn_modify_name_edit = edit_btn # 先ctrl+a選中所有然後再type_keys替換 btn_modify_name_edit.type_keys('^a').type_keys('備註名字', with_spaces=True) # descendants查詢所有後代中滿足的,不管層級,所有滿足的放進列表 btns_list = win_main_Dialog.child_window(control_type='ToolBar').parent().descendants(control_type='Button') btns_list[0].click_input() dialog.child_window(title="檔名(N):", auto_id="1148", control_type="Edit")
4 快速定位
定位一個元素我們可以一層一層定位,但是這樣真就有點笨蛋了,不僅效率低下還不容易適應結構變化,可以先定位某個頁面,列印出頁面結構,然後基於頁面快速定位
四、控制元件自帶的的方法
1. 點選和輸入
# 左點選,可以點進原始碼,還有double_click_input,right_click_input等 edit_btn.click_input() # 先ctrl+a選中所有然後再type_keys替換,和我們選中然後修改一樣的 edit_btn.type_keys('^a').type_keys('備註名字', with_spaces=True)
常規使用很方便,但是有些字元,比如微信中的使用者暱稱什麼的帶有表情等特殊符號,用自帶的輸入方法就會不適用,可以使用keyboard模組(見下)
2.對控制元件截圖並儲存
ctrl_qrcode = self.win_login.child_window(title='二維碼', control_type='Image') if ctrl_qrcode.exists(): ctrl_qrcode.capture_as_image().save(img_path)
capture_as_image() 方法 返回控制元件的其實是 PIL image物件,所以可用該方法的屬性方法,比如save
3.視窗的等待
視窗載入需要時間,我們又不能一直sleep就需要等待,等待視窗出現或者等待視窗關閉:
save_dialog.wait('ready',timeout=2) save_dialog.close() save_dialog.wait_not('visible') # 'exists':視窗是有效的控制代碼 # 'visible':視窗未隱藏,常用 # 'enabled':未禁用視窗 # 'ready':視窗可見並啟用,常用 # 'active':視窗處於活動狀態
4.視窗存在和關閉
self.chatwnd = wechat.app.window(class_name='ChatWnd') if self.chatwnd.exists(): self.chatwnd.close()
5.其他
# 頂層視窗 dlg = app.top_window() # 點方法取值 print(dlg.class_name()) #'WeChatMainWndForPC' # 滾動 常用於頁面的滾動,比如好友列表、聊天列表、訊息介面 chat_list.scroll(direction='up', amount='page')
五、滑鼠操作
pywinauto自帶的滑鼠操作有些時候並不能完全滿足要求,可以呼叫mouse的方法
匯入:
from pywinauto import mouse
常見操作:
# 移動滑鼠 mouse.move(coords=(x, y)) # 指定位置,滑鼠左擊 mouse.click(button='left', coords=(40, 40)) # 滑鼠雙擊 mouse.double_click(button='left', coords=(140, 40)) # 將屬性移動到(140,40)座標處按下 mouse.press(button='left', coords=(140, 40)) # 將滑鼠移動到(300,40)座標處釋放, mouse.release(button='left', coords=(300, 40)) # 右鍵單擊指定座標 mouse.right_click(coords=(400, 400)) # 滑鼠中鍵單擊指定座標(很少用的到) mouse.wheel_click(coords=(400, 400)) # 滾動滑鼠 wheel_dist指定滑鼠滾輪滑動,正數往上,負數往下。 mouse.scroll(coords=(1200,300),wheel_dist=-3)
示例:
# 以控制元件中心為起點,滾動 def mouse_scroll(control, distance): rect = control.rectangle() cx = int((rect.left+rect.right)/2) cy = int((rect.top + rect.bottom)/2) mouse.scroll(coords=(cx, cy), wheel_dist=distance) mouse_scroll(control=win_main_Dialog.child_window(control_type='List', title='聯絡人'), distance=-5)
六、鍵盤操作
和控制元件自己的type_keys方法效果一樣,但是更快,那個是從前到後啪啪啪的輸入,這個是一下就出來了那種
在傳送檔案和圖片的時候可以使用鍵盤模組,複製貼上,比啪啪啪輸入路徑再傳送速度快多了
並且該模組可以適配很多表情等特殊符號
import keyboard import io for line in io.StringIO(msg): keyboard.write(line.strip()) # keyboard.send('ctrl+enter') keyboard.write(chat_name) keyboard.send('enter') keyboard.send('ctrl+v')
from:https://www.cnblogs.com/xp1315458571/p/13892205.html#_lab20_3