如何用 Python 子進(jìn)程關(guān)閉 Excel 自動(dòng)化中的彈窗
利用Python進(jìn)行Excel自動(dòng)化操作的過(guò)程中,尤其是涉及VBA時(shí),可能遇到消息框/彈窗(MsgBox)。此時(shí)需要人為響應(yīng),否則代碼卡死直至超時(shí) [^1] [^2]。根本的解決方法是VBA代碼中不要出現(xiàn)類(lèi)似彈窗,但有時(shí)我們無(wú)權(quán)修改被操作的Excel文件,例如這是我們進(jìn)行自動(dòng)化測(cè)試的對(duì)象。所以本文記錄從代碼角度解決此類(lèi)問(wèn)題的方法。
假想場(chǎng)景
使用xlwings(或者其他自動(dòng)化庫(kù))打開(kāi)Excel文件test.xlsm,讀取Sheet1!A1單元格內(nèi)容。很簡(jiǎn)單的一個(gè)操作:
import xlwings as xw wb = xw.Book('test.xlsm') msg = wb.sheets('Sheet1').range('A1').value print(msg) wb.close()
然而不幸的是,打開(kāi)工作簿時(shí)進(jìn)行了熱情的歡迎儀式:
Private Sub Workbook_Open() MsgBox "Welcome" MsgBox "to open" MsgBox "this file." End Sub
第一個(gè)彈窗Welcome就卡住了Excel,Python代碼相應(yīng)卡死在第一行。
基本思路
主程序中不可能直接處理或者繞過(guò)此類(lèi)問(wèn)題,也不能奢望有人隨時(shí)蹲守點(diǎn)擊下一步——那就開(kāi)啟一個(gè)子線(xiàn)程來(lái)護(hù)航吧。因此,解決方案是利用子線(xiàn)程監(jiān)聽(tīng)并隨時(shí)關(guān)閉彈窗,直到主程序圓滿(mǎn)結(jié)束。
解決這個(gè)問(wèn)題,需要以下兩個(gè)知識(shí)點(diǎn)(基礎(chǔ)知識(shí)請(qǐng)課外學(xué)習(xí)):
- Python多線(xiàn)程(本文采用threading.Thread)
- Python界面自動(dòng)化庫(kù)(本文涉及pywinauto和pywin32)
pywinauto方案
pywinauto顧名思義是Windows界面自動(dòng)化庫(kù),模擬鼠標(biāo)和鍵盤(pán)操作窗體和控件 [^3]。不同于先獲取句柄再獲取屬性的傳統(tǒng)方式,pywinauto的API更加友好和pythonic。例如,兩行代碼搞定窗口捕捉和點(diǎn)擊:
from pywinauto.application import Application win = Application(backend="win32").connect(title='Microsoft Excel') win.Dialog.Button.click()
本文采用自定義線(xiàn)程類(lèi)的方式,啟動(dòng)線(xiàn)程后自動(dòng)執(zhí)行run()函數(shù)來(lái)完成上述操作。具體代碼如下,注意構(gòu)造函數(shù)中的兩個(gè)參數(shù):
- title 需要捕捉的彈窗的標(biāo)題,例如Excel默認(rèn)彈窗的標(biāo)題為Microsoft Excel
- interval 監(jiān)聽(tīng)的頻率,即每隔多少秒檢查一次
# listener.py import time from threading import Thread, Event from pywinauto.application import Application class MsgBoxListener(Thread): def __init__(self, title:str, interval:int): Thread.__init__(self) self._title = title self._interval = interval self._stop_event = Event() def stop(self): self._stop_event.set() @property def is_running(self): return not self._stop_event.is_set() def run(self): while self.is_running: try: time.sleep(self._interval) self._close_msgbox() except Exception as e: print(e, flush=True) def _close_msgbox(self): '''Close the default Excel MsgBox with title "Microsoft Excel".''' win = Application(backend="win32").connect(title=self._title) win.Dialog.Button.click() if __name__=='__main__': t = MsgBoxListener('Microsoft Excel', 3) t.start() time.sleep(10) t.stop()
于是,整個(gè)過(guò)程分為三步:
- 啟動(dòng)子線(xiàn)程監(jiān)聽(tīng)彈窗
- 主線(xiàn)程中打開(kāi)Excel開(kāi)始自動(dòng)化操作
- 關(guān)閉子線(xiàn)程
import xlwings as xw from listener import MsgBoxListener # start listen thread listener = MsgBoxListener('Microsoft Excel', 3) listener.start() # main process as before wb = xw.Book('test.xlsm') msg = wb.sheets('Sheet1').range('A1').value print(msg) wb.close() # stop listener thread listener.stop()
到此問(wèn)題基本解決,本地運(yùn)行效果完全達(dá)到預(yù)期。但我的真實(shí)需求是以系統(tǒng)服務(wù)方式在服務(wù)器上進(jìn)行Excel文件自動(dòng)化測(cè)試,后續(xù)發(fā)現(xiàn),當(dāng)以系統(tǒng)服務(wù)方式運(yùn)行時(shí),pywinauto竟然捕捉不到彈窗!這或許是pywinauto一個(gè)潛在的問(wèn)題 [^4]。
win32gui方案
那就只好轉(zhuǎn)向相對(duì)底層的win32gui,所幸完美解決了上述問(wèn)題。
win32gui是pywin32庫(kù)的一部分,所以實(shí)際安裝命令是:
pip install pywin32
整個(gè)方案和前文描述完全一致,只是替換MsgBoxListener類(lèi)中關(guān)閉彈窗的方法:
import win32gui, win32con def _close_msgbox(self): # find the top window by title hwnd = win32gui.FindWindow(None, self._title) if not hwnd: return # find child button h_btn = win32gui.FindWindowEx(hwnd, None,'Button', None) if not h_btn: return # show text text = win32gui.GetWindowText(h_btn) print(text) # click button win32gui.PostMessage(h_btn, win32con.WM_LBUTTONDOWN, None, None) time.sleep(0.2) win32gui.PostMessage(h_btn, win32con.WM_LBUTTONUP, None, None) time.sleep(0.2)
更一般的方案
更一般地,當(dāng)同時(shí)存在默認(rèn)標(biāo)題和自定義標(biāo)題的彈窗時(shí),就不便于采用標(biāo)題方式進(jìn)行捕捉了。例如
MsgBox "Message with default title.", vbInformation, MsgBox "Message with title My App 1", vbInformation, "My App 1" MsgBox "Message with title My App 2", vbInformation, "My App 2"
那就擴(kuò)大搜索范圍,依次點(diǎn)擊所有包含確定性描述的按鈕(例如OK,Yes,Confirm)來(lái)關(guān)閉彈窗。同理替換MsgBoxListener類(lèi)的_close_msgbox()方法(同時(shí)構(gòu)造函數(shù)中不再需要title參數(shù)):
def _close_msgbox(self): '''Click any button ("OK", "Yes" or "Confirm") to close message box.''' # get handles of all top windows h_windows = [] win32gui.EnumWindows(lambda hWnd, param: param.append(hWnd), h_windows) # check each window for h_window in h_windows: # get child button with text OK, Yes or Confirm of given window h_btn = win32gui.FindWindowEx(h_window, None,'Button', None) if not h_btn: continue # check button text text = win32gui.GetWindowText(h_btn) if not text.lower() in ('ok', 'yes', 'confirm'): continue # click button win32gui.PostMessage(h_btn, win32con.WM_LBUTTONDOWN, None, None) time.sleep(0.2) win32gui.PostMessage(h_btn, win32con.WM_LBUTTONUP, None, None) time.sleep(0.2)
最后,實(shí)例演示結(jié)束全文,以后再也不用擔(dān)心意外彈窗了。
以上就是如何用 Python 子進(jìn)程關(guān)閉 Excel 自動(dòng)化中的彈窗的詳細(xì)內(nèi)容,更多關(guān)于Python 子進(jìn)程關(guān)閉 Excel 彈窗的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 通過(guò)python實(shí)現(xiàn)彈窗廣告攔截過(guò)程詳解
- python 彈窗提示警告框MessageBox的實(shí)例
- python實(shí)現(xiàn)彈窗祝福效果
- python實(shí)現(xiàn)祝福彈窗效果
- Python爬蟲(chóng)之Selenium警告框(彈窗)處理
- Python GUI編程 文本彈窗的實(shí)例
- python中的tkinter庫(kù)彈窗messagebox詳解
- Python繪制圣誕樹(shù)+落葉+雪花+背景音樂(lè)+浪漫彈窗?五合一版圣誕樹(shù)
- Python爬蟲(chóng)之獲取心知天氣API實(shí)時(shí)天氣數(shù)據(jù)并彈窗提醒
- python?tkinter實(shí)現(xiàn)彈窗的輸入輸出
相關(guān)文章
手機(jī)Python編程軟件QPython支持第三方庫(kù)安裝詳解
這篇文章主要為大家介紹了手機(jī)Python編程軟件QPython的推薦使用,支持第三方庫(kù)安裝,這樣大家在上下班途中也可以來(lái)練練手啦,有需要的朋友一起用起來(lái)吧2021-10-10python數(shù)組排序方法之sort、sorted和argsort詳解
這篇文章主要給大家介紹了關(guān)于python數(shù)組排序方法之sort、sorted和argsort的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-03-03Python for循環(huán)與range函數(shù)的使用詳解
這篇文章主要介紹了Python for循環(huán)與range函數(shù)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03python+selenium+Chrome options參數(shù)的使用
這篇文章主要介紹了python+selenium+Chrome options參數(shù)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03C語(yǔ)言中的結(jié)構(gòu)體在Python中實(shí)現(xiàn)轉(zhuǎn)換
這篇文章主要為大家介紹了C語(yǔ)言中的結(jié)構(gòu)體在Python中實(shí)現(xiàn)轉(zhuǎn)換示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06python?os.stat()如何獲取相關(guān)文件的系統(tǒng)狀態(tài)信息
這篇文章主要介紹了python?os.stat()如何獲取相關(guān)文件的系統(tǒng)狀態(tài)信息,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11利用Python的裝飾器解決Bottle框架中用戶(hù)驗(yàn)證問(wèn)題
這篇文章主要介紹了Python的Bottle框架中解決用戶(hù)驗(yàn)證問(wèn)題,代碼基于Python2.x版本,需要的朋友可以參考下2015-04-04spark?dataframe全局排序id與分組后保留最大值行
這篇文章主要為大家介紹了spark?dataframe全局排序id與分組后保留最大值行實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02