RPA實(shí)戰(zhàn):基于Python圖像識(shí)別與鍵鼠模擬的簡(jiǎn)易自動(dòng)化工具指南)
簡(jiǎn)介RPAworker簡(jiǎn)易自動(dòng)化工具是一款面向零基礎(chǔ)辦公人員的RPA自動(dòng)化輔助軟件采用圖片定位與Excel指令編排相結(jié)合的方式讓用戶通過(guò)截圖和表格設(shè)置就能生成自動(dòng)化流程工具支持近40個(gè)操作指令可按照從上到下的順序逐條執(zhí)行覆蓋報(bào)表下載、表單填寫(xiě)、重復(fù)點(diǎn)擊等日常辦公場(chǎng)景有效減少機(jī)械化重復(fù)操作。其配置邏輯直觀把界面元素截圖作為定位依據(jù)在Excel表中按順序填寫(xiě)操作指令即可生成可重復(fù)運(yùn)行的流程后期修改只需調(diào)整表格內(nèi)容。使用者無(wú)需編程語(yǔ)言基礎(chǔ)只要掌握截圖與Excel基本操作即可自行配置若后續(xù)學(xué)習(xí)一點(diǎn)數(shù)據(jù)庫(kù)知識(shí)還可借助同一套邏輯搭建網(wǎng)頁(yè)爬蟲(chóng)采集數(shù)據(jù)把自動(dòng)化范圍擴(kuò)展到網(wǎng)絡(luò)端。資源包為zip壓縮格式大小約76.71MB支持Win7及以上版本服務(wù)器需Win2008及以上Win10下運(yùn)行最穩(wěn)定Win7若閃退可按系統(tǒng)位數(shù)安裝vc_redist運(yùn)行庫(kù)后正常使用。目前已有251人學(xué)習(xí)關(guān)注對(duì)于文員、個(gè)人開(kāi)發(fā)者等希望低成本入門(mén)RPA的用戶而言這份輕量工具可直接下載解壓進(jìn)行流程配置和測(cè)試。1. RPAworker簡(jiǎn)易自動(dòng)化工具為什么它比“大而全”的RPA平臺(tái)更適合個(gè)人和小團(tuán)隊(duì)每天早上一到工位先把三個(gè)業(yè)務(wù)系統(tǒng)的報(bào)表導(dǎo)出來(lái)照著規(guī)則填進(jìn)Excel再打開(kāi)網(wǎng)頁(yè)逐個(gè)錄入最后發(fā)郵件通知——這套動(dòng)作我做了快一年直到有一天我數(shù)了一下每天大概要花四十分鐘在這類(lèi)毫無(wú)技術(shù)含量的重復(fù)操作上。后來(lái)我搭了一個(gè)RPAworker簡(jiǎn)易自動(dòng)化工具把這套流程交給腳本去跑每天只留一道人工復(fù)核。RPA這個(gè)名字聽(tīng)起來(lái)很工業(yè)但“簡(jiǎn)易”兩個(gè)字才是關(guān)鍵它不追求像企業(yè)級(jí)RPA平臺(tái)那樣覆蓋所有系統(tǒng)、支持流程編排、帶控制臺(tái)管理只解決一件事——把你在電腦上反復(fù)做的機(jī)械操作用圖像識(shí)別加鍵鼠模擬的方式自動(dòng)跑起來(lái)。這套思路特別適合個(gè)人開(kāi)發(fā)者、小團(tuán)隊(duì)和兼職做數(shù)據(jù)處理的從業(yè)者你不需要部署服務(wù)器不需要被審批流程卡住甚至不需要太深的編程功底只要機(jī)器上能裝Python把幾行腳本寫(xiě)得能看懂就行。更重要的是它不依賴被自動(dòng)化系統(tǒng)的任何接口不侵入對(duì)方軟件的改造范圍窗口在旁邊開(kāi)著它就能干活活干完你只需要盯一眼結(jié)果。這篇文章會(huì)把從技術(shù)選型到實(shí)際落地的完整路徑講清楚包括參數(shù)怎么調(diào)、哪些地方容易翻車(chē)、怎么把腳本做得不容易壞。2. 選型先立住簡(jiǎn)易R(shí)PA工具的定位、技術(shù)路線與運(yùn)行環(huán)境2.1 三種自動(dòng)化路線的取舍為什么簡(jiǎn)易工具選“圖像識(shí)別鍵鼠模擬”市面上常見(jiàn)的RPA實(shí)現(xiàn)路線大致有三類(lèi)。第一類(lèi)基于UI控件樹(shù)通過(guò)系統(tǒng)輔助功能接口讀取窗口上的按鈕、輸入框、表格節(jié)點(diǎn)然后直接調(diào)用控件的屬性去賦值或觸發(fā)點(diǎn)擊。這路線的優(yōu)點(diǎn)是定位精準(zhǔn)、速度快但前提是被自動(dòng)化程序必須暴露控件樹(shù)很多自繪界面、網(wǎng)頁(yè)里的Canvas區(qū)域、遠(yuǎn)程桌面里的窗口都拿不到穩(wěn)定的控件信息。第二類(lèi)基于坐標(biāo)腳本也就是提前記住按鈕在屏幕上的像素位置到點(diǎn)就點(diǎn)擊。這玩意兒最脆弱窗口一挪、分辨率一換、屏幕上多彈出個(gè)通知整套腳本就全偏了。坐標(biāo)腳本一般只適合固定工位的演示場(chǎng)景真正用來(lái)跑日常任務(wù)很容易讓人血壓升高。第三類(lèi)就是RPAworker這類(lèi)簡(jiǎn)易工具采用的路線用截圖模板匹配來(lái)做視覺(jué)定位用鍵鼠模擬來(lái)執(zhí)行動(dòng)作。先截取目標(biāo)按鈕或輸入框的小圖作為模板運(yùn)行時(shí)在屏幕上找到匹配位置再去點(diǎn)擊或輸入。整套方案不需要接觸被控程序內(nèi)部也不依賴絕對(duì)坐標(biāo)窗口只要在屏幕上可見(jiàn)且外觀沒(méi)大變基本就能找得到、點(diǎn)得準(zhǔn)。三者的關(guān)鍵差異我用一張表來(lái)梳理路線定位方式環(huán)境要求穩(wěn)定性適用場(chǎng)景控件樹(shù)讀取UI控件接口被控程序暴露控件結(jié)構(gòu)高但挑程序標(biāo)準(zhǔn)Windows桌面應(yīng)用坐標(biāo)腳本固定屏幕坐標(biāo)窗口位置分辨率完全固定低演示、一次性任務(wù)圖像識(shí)別鍵鼠模擬截圖模板匹配目標(biāo)窗口可見(jiàn)中上靠閾值與容錯(cuò)補(bǔ)救網(wǎng)頁(yè)、遺留系統(tǒng)、跨平臺(tái)界面我一般會(huì)優(yōu)先推薦第三類(lèi)做個(gè)人自動(dòng)化理由很直接它不挑系統(tǒng)不要求對(duì)方開(kāi)放接口連老舊的內(nèi)部系統(tǒng)都能照跑不誤。代價(jià)是稍微吃一點(diǎn)CPU以及需要處理匹配失敗和誤匹配的容錯(cuò)邏輯后面第4章和第5章會(huì)展開(kāi)講。2.2 簡(jiǎn)易R(shí)PAworker技術(shù)棧的選型理由雖然標(biāo)題叫RPAworker簡(jiǎn)易自動(dòng)化工具代碼實(shí)現(xiàn)上并不需要單獨(dú)寫(xiě)一個(gè)商業(yè)級(jí)框架?;谒芘?、好維護(hù)、夠輕量的原則技術(shù)棧一般可以收斂為三個(gè)部分Python做膠水語(yǔ)言開(kāi)源的計(jì)算機(jī)視覺(jué)庫(kù)做模板匹配再加一個(gè)跨平臺(tái)的鍵鼠控制庫(kù)做動(dòng)作執(zhí)行。常見(jiàn)組合中動(dòng)作控制用pyautogui它負(fù)責(zé)模擬鼠標(biāo)移動(dòng)、點(diǎn)擊、拖拽、鍵盤(pán)輸入接口簡(jiǎn)單到幾分鐘就能上手自帶屏幕截圖功能還能順便用來(lái)做調(diào)試。圖像匹配用OpenCV的模板匹配方法把預(yù)先截好的按鈕小圖作為模板在實(shí)時(shí)截圖中滑動(dòng)計(jì)算相似度返回最可能的中心坐標(biāo)。中間再加一個(gè)watchdog庫(kù)用于監(jiān)聽(tīng)文件夾變化當(dāng)有文件落地時(shí)自動(dòng)觸發(fā)流程這樣就不需要人工去點(diǎn)“開(kāi)始”按鈕。為什么不在這個(gè)組合里引入Selenium或Playwright直接操作瀏覽器因?yàn)镽PAworker定位的是“所見(jiàn)即所得”的桌面級(jí)自動(dòng)化網(wǎng)頁(yè)只是其中一類(lèi)目標(biāo)。很多內(nèi)部系統(tǒng)登錄后是混合界面一部分頁(yè)面元素在DOM里一部分是嵌入的桌面控件或Flash遺留物單一瀏覽器方案根本覆蓋不全。而圖像識(shí)別方案一視同仁——無(wú)論那部分是HTML、Canvas還是畫(huà)出來(lái)的按鈕只要人眼能看見(jiàn)機(jī)器就能按模板去找。2.3 把環(huán)境搭起來(lái)最小依賴安裝與基線驗(yàn)證環(huán)境準(zhǔn)備按以下命令執(zhí)行建議先創(chuàng)建獨(dú)立虛擬環(huán)境避免和別的項(xiàng)目打架。python -m venv rpa_env source rpa_env/bin/activate # Windows下用 rpa_env\Scripts\activate pip install pyautogui opencv-python pillow watchdog這幾步做完依賴就齊了。pyautogui負(fù)責(zé)輸入模擬opencv-python提供cv2.matchTemplate接口pillow是pyautogui截圖時(shí)繞不開(kāi)的圖像處理底層庫(kù)它依賴PIL的接口watchdog用來(lái)做文件系統(tǒng)監(jiān)聽(tīng)。安裝完成后先跑一段十秒的驗(yàn)證腳本確認(rèn)pyautogui能正??刂剖髽?biāo)鍵盤(pán)import pyautogui import time screen_width, screen_height pyautogui.size() print(f當(dāng)前屏幕分辨率: {screen_width} x {screen_height}) pyautogui.moveTo(screen_width // 2, screen_height // 2, duration0.5) time.sleep(1) pyautogui.click()這段腳本把鼠標(biāo)移到屏幕中心并單擊。如果運(yùn)行正常光標(biāo)會(huì)平滑移動(dòng)過(guò)去。邏輯上把這一小段作為基線驗(yàn)證是因?yàn)楹芏喹h(huán)境里pyautogui的鼠標(biāo)控制受權(quán)限限制例如某些遠(yuǎn)程會(huì)話或屏幕設(shè)置影響先在最小范圍確認(rèn)它可用可以避免后面寫(xiě)了整套流程才發(fā)現(xiàn)底層動(dòng)作發(fā)不出去。參數(shù)上moveTo的duration控制移動(dòng)時(shí)長(zhǎng)0.5秒代表半秒內(nèi)完成移動(dòng)數(shù)值越小動(dòng)作越突兀越大越接近人手移動(dòng)click不帶參數(shù)表示在當(dāng)前光標(biāo)位置按下左鍵。3. 核心實(shí)現(xiàn)從定位、動(dòng)作到流程編排跑通一條真實(shí)任務(wù)3.1 用模板匹配做可靠的目標(biāo)定位基礎(chǔ)代碼與匹配參數(shù)RPAworker里所有“找東西”的操作都建立在模板匹配上。核心思路是先截取目標(biāo)區(qū)域的圖片保存為模板文件程序運(yùn)行時(shí)把屏幕整圖讀進(jìn)來(lái)用cv2.matchTemplate在整圖上滑動(dòng)搜索最相似的位置。這是OpenCV內(nèi)置功能不需要訓(xùn)練任何模型模板圖片大概幾百字節(jié)到幾KB就能發(fā)揮作用。下面這段代碼展示一次完整的定位邏輯把“找圖標(biāo)并返回坐標(biāo)”封裝成一個(gè)可復(fù)用函數(shù)import cv2 import numpy as np import pyautogui def find_template(template_path, confidence0.8): # 截取當(dāng)前屏幕 screen pyautogui.screenshot() # 轉(zhuǎn)為OpenCV使用的BGR格式 screen_cv cv2.cvtColor(np.array(screen), cv2.COLOR_RGB2BGR) # 讀取模板圖片 template cv2.imread(template_path) # 執(zhí)行模板匹配 result cv2.matchTemplate(screen_cv, template, cv2.TM_CCOEFF_NORMED) # 找到最相似位置及其相似度 min_val, max_val, min_loc, max_loc cv2.minMaxLoc(result) if max_val confidence: h, w template.shape[:2] # 返回模板中心坐標(biāo) center_x max_loc[0] w // 2 center_y max_loc[1] h // 2 return center_x, center_y, max_val return NonematchTemplate的TM_CCOEFF_NORMED是歸一化相關(guān)系數(shù)匹配結(jié)果值域在-1到1之間越接近1代表越相似。confidence參數(shù)設(shè)置為0.8意思是相似度不低于80%才認(rèn)為找到目標(biāo)。這個(gè)值不是固定不變的后面講參數(shù)時(shí)會(huì)細(xì)說(shuō)。取中心坐標(biāo)是為了讓后續(xù)點(diǎn)擊動(dòng)作落在模板區(qū)域正中央避免點(diǎn)到模板邊緣導(dǎo)致誤觸。如果函數(shù)返回None說(shuō)明屏幕上沒(méi)找到目標(biāo)調(diào)用方就應(yīng)該進(jìn)入等待或重試邏輯而不是直接點(diǎn)擊。需要注意的一點(diǎn)是模板圖片要截取目標(biāo)物體的“穩(wěn)定外觀”。例如一個(gè)按鈕圖標(biāo)盡量只截按鈕主體不要帶上背景的漸變紋理或旁邊的文字否則背景一變匹配率就暴跌。截圖分辨率要和運(yùn)行環(huán)境一致建議模板和屏幕都在同一個(gè)縮放比例下截取后面遇到縮放的坑會(huì)再提。3.2 動(dòng)作模擬的細(xì)節(jié)點(diǎn)擊、輸入、快捷鍵與等待策略定位到目標(biāo)之后動(dòng)作層要處理的主要是四類(lèi)操作移動(dòng)并點(diǎn)擊、鍵盤(pán)輸入、快捷鍵組合、流程之間的等待。RPAworker在動(dòng)作層會(huì)封裝幾個(gè)常用函數(shù)讓主流程代碼看起來(lái)像在描述自然語(yǔ)言步驟。import pyautogui import time import random def smart_click(x, y): # 略微加入隨機(jī)偏移避免每次點(diǎn)擊都在同一個(gè)像素點(diǎn) offset_x random.randint(-3, 3) offset_y random.randint(-3, 3) pyautogui.moveTo(x offset_x, y offset_y, duration0.2) pyautogui.click() def type_text(text, interval0.05): # 逐字符輸入模擬人工打字節(jié)奏 for char in text: pyautogui.write(char, intervalinterval) def press_hotkey(*keys): # 組合快捷鍵例如 press_hotkey(ctrl, s) pyautogui.hotkey(*keys) def wait_for(target_func, timeout15): # 輪詢等待目標(biāo)出現(xiàn)超時(shí)拋異常 start time.time() while time.time() - start timeout: result target_func() if result is not None: return result time.sleep(1) raise TimeoutError(f{target_func.__name__} 在 {timeout} 秒內(nèi)未出現(xiàn))smart_click中加入隨機(jī)偏移是血淚經(jīng)驗(yàn)?zāi)承┫到y(tǒng)會(huì)記錄操作軌跡如果每次點(diǎn)擊都在完全相同的像素點(diǎn)很容易觸發(fā)風(fēng)控或操作審計(jì)告警。type_text逐字符帶間隔寫(xiě)入是因?yàn)閜yautogui.write默認(rèn)一次性輸出整串文本很多網(wǎng)頁(yè)輸入框會(huì)直接吞掉部分字符或觸發(fā)校驗(yàn)這里interval控制每個(gè)字符的間隔秒數(shù)設(shè)0.05秒時(shí)速度接近普通人輸入既快又不容易丟字。wait_for是整套流程的節(jié)拍器RPA最大的翻車(chē)點(diǎn)往往是流程跑得太快——界面還沒(méi)反應(yīng)過(guò)來(lái)就執(zhí)行下一步所以“等待目標(biāo)出現(xiàn)”比“固定sleep幾秒”要可靠得多界面卡頓時(shí)固定sleep直接失效而輪詢能自動(dòng)延長(zhǎng)等待。3.3 用watchdog監(jiān)聽(tīng)文件夾讓流程自己?jiǎn)?dòng)很多自動(dòng)化任務(wù)的觸發(fā)方式不是“手動(dòng)運(yùn)行”而是“有新文件丟進(jìn)來(lái)就開(kāi)工”。常見(jiàn)場(chǎng)景是業(yè)務(wù)人員把導(dǎo)出的Excel報(bào)表放到一個(gè)約定文件夾RPA腳本監(jiān)測(cè)到新文件后自動(dòng)開(kāi)始處理。這里用watchdog實(shí)現(xiàn)一個(gè)輕量監(jiān)聽(tīng)器比寫(xiě)死循環(huán)輪詢要省資源得多。from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler import time class RpaJobHandler(FileSystemEventHandler): def on_created(self, event): # 只處理文件創(chuàng)建事件跳過(guò)目錄 if event.is_directory: return print(f檢測(cè)到新文件: {event.src_path}) # 這里調(diào)用實(shí)際的處理流程例如 process_file(event.src_path) def start_monitor(path./jobs): observer Observer() handler RpaJobHandler() observer.schedule(handler, path, recursiveFalse) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join()on_created回調(diào)只在有新文件創(chuàng)建時(shí)觸發(fā)src_path給出完整路徑。之所以用事件監(jiān)聽(tīng)而不是寫(xiě)while輪詢是因?yàn)檩喸冇泄潭ǖ膾呙栝g隔文件處理時(shí)間會(huì)被延長(zhǎng)事件監(jiān)聽(tīng)是操作系統(tǒng)層面通知實(shí)時(shí)性更好CPU占用幾乎為零。recursiveFalse表示不遞歸監(jiān)聽(tīng)子目錄這能避免程序自己往目錄里寫(xiě)輸出文件時(shí)再次觸發(fā)事件造成無(wú)限循環(huán)——這一點(diǎn)非常關(guān)鍵輸出目錄和監(jiān)聽(tīng)目錄必須分開(kāi)或者輸出文件名做排除處理。正式使用中我一般會(huì)把監(jiān)聽(tīng)目錄和輸出目錄分開(kāi)成兩個(gè)文件夾這樣監(jiān)聽(tīng)器只看到輸入文件處理完畢后把結(jié)果寫(xiě)去另一個(gè)目錄互不干擾。3.4 拼裝一個(gè)最小但完整的流程把三個(gè)模塊擰成一股繩前面三個(gè)模塊單看都簡(jiǎn)單但串起來(lái)就能形成一個(gè)完整的自動(dòng)化閉環(huán)。拿一個(gè)典型任務(wù)舉例每天把某個(gè)報(bào)表軟件導(dǎo)出的CSV文件自動(dòng)合并、填入網(wǎng)頁(yè)表單并提交。整個(gè)流程用偽代碼級(jí)別就能表達(dá)清楚。# run_job.py from finder import find_template from actions import smart_click, type_text, wait_for import pyautogui def process_file(file_path): # 1. 打開(kāi)目標(biāo)網(wǎng)頁(yè)或業(yè)務(wù)系統(tǒng) pyautogui.hotkey(ctrl, t) type_text(http://internal-report.example) pyautogui.press(enter) # 2. 等待登錄按鈕出現(xiàn) login_btn wait_for(lambda: find_template(templates/login_btn.png)) smart_click(*login_btn[:2]) # 3. 定位文件上傳區(qū)域填入待處理文件路徑 upload_area wait_for(lambda: find_template(templates/upload_area.png)) smart_click(*upload_area[:2]) type_text(file_path) # 4. 點(diǎn)擊提交并等待成功頁(yè)面元素 submit_btn wait_for(lambda: find_template(templates/submit_btn.png)) smart_click(*submit_btn[:2]) ok_icon wait_for(lambda: find_template(templates/success_icon.png), timeout30) print(f文件 {file_path} 處理完成) if __name__ __main__: start_monitor(process_file)上面的代碼結(jié)構(gòu)上很清晰find_template負(fù)責(zé)視覺(jué)定位smart_click和type_text負(fù)責(zé)動(dòng)作執(zhí)行wait_for作為流程節(jié)拍器。邏輯上每個(gè)步驟之間都做了目標(biāo)出現(xiàn)校驗(yàn)其中任何一步失敗都會(huì)在wait_for處拋出TimeoutError外層捕獲異常后可以歸檔失敗文件、發(fā)告警通知。參數(shù)上有一個(gè)細(xì)節(jié)容易被忽視type_text填入文件路徑時(shí)路徑中如果有反斜杠建議先做一次路徑分隔符替換因?yàn)閜yautogui.write對(duì)反斜杠的處理在不同鍵盤(pán)布局下有差異。一般我會(huì)在拼接內(nèi)容前加一句 path.replace(\, /)犧牲一點(diǎn)Windows路徑原生化換取輸入環(huán)節(jié)的穩(wěn)定。4. 邊界參數(shù)與可靠性把腳本從“能跑”調(diào)到“長(zhǎng)期能跑”4.1 匹配置信度與多目標(biāo)策略什么時(shí)候該用0.9什么時(shí)候0.7就夠了confidence參數(shù)的選擇不是拍腦袋。目標(biāo)元素外觀越穩(wěn)定、屏幕環(huán)境越干凈閾值可以拉得越高。例如登錄界面上一個(gè)位置固定的按鈕背景是純色模板匹配的峰值通常在0.95以上此時(shí)把confidence設(shè)為0.9可以有效避免誤點(diǎn)。但如果目標(biāo)元素是表格里的某一行標(biāo)題欄包含大量相似的行結(jié)構(gòu)多目標(biāo)并存時(shí)最高相似度可能只有0.75左右閾值設(shè)得太高就會(huì)一直找不到。多目標(biāo)場(chǎng)景是另一個(gè)常見(jiàn)需求窗口里可能有多個(gè)相同的“刪除”按鈕你希望點(diǎn)的是某一行邊上的。cv2.matchTemplate直接返回的是全圖最高分的位置但對(duì)多目標(biāo)情況需要借助閾值化加連通區(qū)域分析來(lái)做。def find_all_templates(template_path, confidence0.8): screen pyautogui.screenshot() screen_cv cv2.cvtColor(np.array(screen), cv2.COLOR_RGB2BGR) template cv2.imread(template_path) result cv2.matchTemplate(screen_cv, template, cv2.TM_CCOEFF_NORMED) locations np.where(result confidence) points [] h, w template.shape[:2] for pt in zip(*locations[::-1]): center (pt[0] w // 2, pt[1] h // 2) points.append(center) return points這段代碼返回所有相似度超過(guò)閾值的目標(biāo)中心點(diǎn)。注意直接用np.where會(huì)把同一個(gè)目標(biāo)附近的重疊區(qū)域全部算進(jìn)來(lái)同一按鈕周?chē)赡軘D出十幾個(gè)點(diǎn)。實(shí)際使用時(shí)要再做一步去重按最小距離合并聚類(lèi)通常設(shè)置兩個(gè)中心點(diǎn)距離小于模板寬度的一半就視為同一個(gè)目標(biāo)。這個(gè)去重邏輯建議在流程里實(shí)現(xiàn)否則點(diǎn)擊“第二個(gè)刪除按鈕”時(shí)可能因?yàn)橹貜?fù)點(diǎn)導(dǎo)致鼠標(biāo)在一個(gè)按鈕上劃來(lái)劃去。4.2 超時(shí)、重試與失敗歸檔讓腳本具備自我保護(hù)意識(shí)線上長(zhǎng)期跑的腳本最怕的不是功能寫(xiě)錯(cuò)而是某個(gè)步驟卡住后無(wú)限等待或者失敗后把數(shù)據(jù)弄臟。RPAworker的做法是給每一步加超時(shí)超時(shí)后重試有限次數(shù)最終失敗則把輸入文件移動(dòng)到failures目錄并記錄日志而不是留在原地。import time import shutil from pathlib import Path def run_with_retry(action_func, retries3, timeout15): last_error None for attempt in range(retries): try: result wait_for(action_func, timeouttimeout) return result except TimeoutError as e: last_error e print(f第 {attempt 1} 次嘗試失敗剩余 {retries - attempt - 1} 次) time.sleep(2) raise last_error def archive_failed_file(file_path, reason): fail_dir Path(./failures) fail_dir.mkdir(exist_okTrue) dest fail_dir / Path(file_path).name shutil.move(file_path, dest) print(f失敗歸檔: {file_path} - {dest}原因: {reason})重試邏輯里每次間隔2秒是為了給界面留出恢復(fù)時(shí)間部分卡頓是瞬時(shí)抖動(dòng)兩秒后可能就恢復(fù)了。歸檔失敗文件比直接刪掉更穩(wěn)妥方便日后復(fù)盤(pán)或手動(dòng)補(bǔ)跑。這里有一個(gè)設(shè)計(jì)習(xí)慣值得強(qiáng)調(diào)腳本寧可在失敗時(shí)把流程停下來(lái)也不要嘗試用猜測(cè)去處理模糊情況。例如匹配到多個(gè)目標(biāo)無(wú)法決定點(diǎn)擊哪個(gè)時(shí)直接歸檔失敗讓人來(lái)救場(chǎng)八成比自動(dòng)賭一個(gè)要安全。4.3 執(zhí)行留痕日志、屏幕截圖與每步動(dòng)作證據(jù)不用等到出錯(cuò)才后悔不截圖。我習(xí)慣在每個(gè)關(guān)鍵動(dòng)作后保存一條帶時(shí)間戳的日志在匹配到目標(biāo)時(shí)順手截一張全屏圖存檔。這樣一旦流程跑歪可以從截圖里回放當(dāng)時(shí)界面到底長(zhǎng)什么樣。import logging import pyautogui from datetime import datetime logging.basicConfig( filenamerpa_run.log, levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s ) def log_with_screenshot(step_name): ts datetime.now().strftime(%Y%m%d_%H%M%S) shot_path f./screenshots/{step_name}_{ts}.png pyautogui.screenshot().save(shot_path) logging.info(f步驟 {step_name} 完成截圖: {shot_path})每個(gè)步驟留一張截圖看似占磁盤(pán)空間實(shí)際上單張PNG截圖大約幾百KB一天跑幾十個(gè)步驟也就幾十MB完全可接受。留痕的價(jià)值在排查問(wèn)題時(shí)才能體現(xiàn)某天早上有三筆單子沒(méi)處理回看日志和截圖能直接定位卡在哪一步是按鈕沒(méi)加載出來(lái)還是彈窗擋了道。這比對(duì)著屏幕發(fā)呆猜原因高效得多。日志級(jí)別建議分級(jí)記錄正常流程打INFO匹配失敗打WARNING歸檔失敗打ERROR分析時(shí)直接按級(jí)別過(guò)濾。5. 避坑指南RPAworker跑不起來(lái)、跑不穩(wěn)的五個(gè)典型坑5.1 遠(yuǎn)程桌面會(huì)話下鼠標(biāo)鍵鼠動(dòng)作失效現(xiàn)象腳本在本機(jī)運(yùn)行一切正常但放到遠(yuǎn)程桌面環(huán)境里掛著的時(shí)候動(dòng)作執(zhí)行一會(huì)兒就停了pyautogui截圖看起來(lái)是正常的但點(diǎn)擊沒(méi)有反應(yīng)。原因鎖屏或斷開(kāi)的遠(yuǎn)程桌面會(huì)話里操作系統(tǒng)不會(huì)渲染實(shí)際的桌面畫(huà)面。pyautogui的截屏函數(shù)拿到的可能是純色或上一幀的靜態(tài)圖鍵鼠模擬事件也會(huì)被系統(tǒng)丟棄因?yàn)樽烂嬉呀?jīng)處于不可交互狀態(tài)。解決讓自動(dòng)化任務(wù)跑在物理機(jī)或始終保持活動(dòng)狀態(tài)的會(huì)話里。如果確實(shí)需要遠(yuǎn)程掛機(jī)可以嘗試用虛擬顯示器或保持遠(yuǎn)程桌面窗口不被最小化同時(shí)設(shè)置系統(tǒng)電源計(jì)劃為“從不休眠”。更穩(wěn)妥的做法是任務(wù)調(diào)度器注冊(cè)成交互式任務(wù)保證會(huì)話處于激活狀態(tài)。這是一個(gè)硬約束不是靠代碼能繞過(guò)去的。5.2 屏幕縮放比例不一致導(dǎo)致匹配全偏現(xiàn)象模板匹配在本機(jī)分辨率下準(zhǔn)確率很高換到同事機(jī)器上同一個(gè)模板再也找不到或者位置偏了一大截。原因Windows顯示設(shè)置里縮放比例不同例如一臺(tái)是100%另一臺(tái)是125%。pyautogui的截圖和坐標(biāo)基于物理像素如果系統(tǒng)做了縮放拉伸截圖實(shí)際尺寸和邏輯坐標(biāo)之間會(huì)出現(xiàn)換算偏差模板里記錄的像素位置和運(yùn)行時(shí)屏幕內(nèi)容就不對(duì)應(yīng)了。解決所有運(yùn)行RPAworker的機(jī)器統(tǒng)一顯示縮放比例。如果實(shí)在無(wú)法統(tǒng)一需要把截圖分辨率做歸一化處理在匹配前將屏幕截圖縮放為與模板采集時(shí)相同的DPI設(shè)定。這個(gè)方案能做但復(fù)雜最省事還是統(tǒng)一環(huán)境。另外注意DPI縮放不要設(shè)“自定義”級(jí)別預(yù)設(shè)的100%、125%、150%經(jīng)過(guò)的路徑不同模板匹配的偏差模式完全不一樣。5.3 中文輸入法導(dǎo)致文本輸入錯(cuò)亂現(xiàn)象type_text輸入中文或混合文本時(shí)輸入框里出現(xiàn)拼音字母、英文字母甚至觸發(fā)輸入法聯(lián)想彈窗把后續(xù)流程全部打亂。原因pyautogui.write在Windows上通過(guò)模擬鍵盤(pán)事件逐字符輸入當(dāng)系統(tǒng)處于中文輸入法狀態(tài)時(shí)按鍵會(huì)被輸入法攔截并轉(zhuǎn)換為拼音組合而不是直接上屏漢字。解決在輸入前切換到英文輸入法。常見(jiàn)做法是模擬快捷鍵切換輸入法然后發(fā)送一個(gè)英文輸入事件把狀態(tài)徹底切到英文模式。如果系統(tǒng)里裝了三方輸入法快捷鍵可能不是默認(rèn)的CtrlSpace需要提前確認(rèn)。另一個(gè)更可靠的方案是改用剪貼板粘貼把文本寫(xiě)入剪貼板然后發(fā)送CtrlV完全繞開(kāi)字符級(jí)鍵盤(pán)模擬。import pyperclip import pyautogui def type_text_via_clipboard(text): pyperclip.copy(text) pyautogui.hotkey(ctrl, v)parampyperclip提供跨平臺(tái)的剪貼板讀寫(xiě)接口。這個(gè)方案對(duì)中文、特殊字符、換行符都友好粘貼操作是一次整體事件不逐字拆解也就不存在輸入法干擾的問(wèn)題。我現(xiàn)在的做法是默認(rèn)走剪貼板只有目標(biāo)系統(tǒng)禁止粘貼時(shí)才回退到逐字符輸入。5.4 按鈕是動(dòng)態(tài)渲染的模板匹配峰值忽高忽低現(xiàn)象流程跑了幾次之后某個(gè)按鈕偶爾找不到。人工看截圖能清楚看到按鈕就在那里但相似度分?jǐn)?shù)就是達(dá)不到閾值。原因不少現(xiàn)代網(wǎng)頁(yè)的按鈕有hover、focus、active等狀態(tài)鼠標(biāo)經(jīng)過(guò)時(shí)顏色會(huì)加深、邊框會(huì)發(fā)光。模板里截的是靜態(tài)灰底按鈕運(yùn)行中按鈕正好處于高亮狀態(tài)或者背景動(dòng)畫(huà)恰好擋住了一部分就會(huì)導(dǎo)致匹配分?jǐn)?shù)下降。解決模板截取時(shí)盡量選按鈕的最穩(wěn)定外觀狀態(tài)例如默認(rèn)灰色底而不是hover后的高亮色。如果按鈕位置固定還可以考慮退而求其次截取按鈕旁邊的靜態(tài)文案或區(qū)域圖標(biāo)作為錨點(diǎn)因?yàn)槲陌副劝粹o顏色穩(wěn)定得多。匹配閾值也不要卡死在0.9給動(dòng)態(tài)渲染留一點(diǎn)余量一般0.8上下比較合理。5.5 殺毒軟件攔截鍵鼠操作或截屏權(quán)限現(xiàn)象腳本運(yùn)行到某個(gè)動(dòng)作時(shí)莫名中斷有些機(jī)器上直接報(bào)權(quán)限錯(cuò)誤有些運(yùn)行日志什么都沒(méi)有就停了。原因部分安全軟件會(huì)把自動(dòng)化工具模擬的鍵鼠事件識(shí)別為可疑行為直接阻止進(jìn)程級(jí)別的輸入注入。截屏操作在某些帶數(shù)據(jù)防泄漏策略的環(huán)境下也會(huì)被禁止導(dǎo)致pyautogui.screenshot返回黑屏或異常。解決先把腳本加入殺毒軟件的白名單尤其是對(duì)Python解釋器進(jìn)程和腳本目錄放行。如果企業(yè)環(huán)境有統(tǒng)一安全策略需要IT側(cè)配合開(kāi)放權(quán)限。這個(gè)坑不好從代碼層面完全繞過(guò)如果目標(biāo)機(jī)器安全策略很?chē)?yán)需要考慮換一種自動(dòng)化方式或者把任務(wù)放到不受監(jiān)控的獨(dú)立環(huán)境里跑。遇到權(quán)限錯(cuò)誤時(shí)優(yōu)先查系統(tǒng)日志里的進(jìn)程攔截記錄比反復(fù)調(diào)代碼效率更高。6. 進(jìn)階技巧把固定流程升級(jí)成可配置的規(guī)則驅(qū)動(dòng)任務(wù)前面講的是把一條流程寫(xiě)死在代碼里實(shí)際用一段時(shí)間后你會(huì)發(fā)現(xiàn)需求經(jīng)常變可能是上傳按鈕的位置變了可能是要處理的文件類(lèi)型多了一種也可能是填表字段順序調(diào)整了。每次都要改代碼并重新部署太麻煩。更實(shí)用的做法是把“步驟順序”和“目標(biāo)元素模板”拆成配置代碼只保留一個(gè)通用的執(zhí)行引擎。做法并不復(fù)雜準(zhǔn)備一個(gè)JSON文件描述流程步驟每一步包含操作類(lèi)型、模板文件名、輸入內(nèi)容、超時(shí)時(shí)間這幾個(gè)字段。執(zhí)行引擎讀取配置后按順序調(diào)用對(duì)應(yīng)動(dòng)作函數(shù)。例如[ {op: open_url, url: http://internal-report.example}, {op: click, template: login_btn.png, confidence: 0.8, timeout: 15}, {op: type, template: username_input.png, text: admin, via_clipboard: true}, {op: type, template: password_input.png, text: ****, via_clipboard: true}, {op: click, template: submit_btn.png}, {op: wait_fixed, seconds: 2}, {op: click, template: upload_area.png}, {op: type, template: file_path_input.png, text: {input_file}, via_clipboard: true}, {op: click, template: confirm_btn.png} ]執(zhí)行引擎里用一個(gè)字典把op名稱映射到具體函數(shù)再用一個(gè)循環(huán)順序處理。這里有個(gè)設(shè)計(jì)要點(diǎn){input_file}這類(lèi)占位符在執(zhí)行時(shí)統(tǒng)一替換成本次任務(wù)的真實(shí)數(shù)據(jù)這樣同一個(gè)配置可以反復(fù)處理不同文件不需要為每個(gè)文件改一行代碼。模板文件名指向模板目錄下的PNG圖片新增界面元素時(shí)只需要截一張圖放進(jìn)去不用動(dòng)代碼。這個(gè)技巧帶來(lái)的效果是改流程變成改配置、換模板幾行JSON加一張截圖就能上線。穩(wěn)定性上反而比硬編碼更好維護(hù)因?yàn)闃I(yè)務(wù)人員自己也能看懂配置里每一步在干什么溝通成本大幅下降。配合第4章的重試與歸檔機(jī)制整個(gè)腳本更像是流水線上的一臺(tái)小設(shè)備而不是某一次性的手工腳本。我自己的使用習(xí)慣是每個(gè)流程至少留一套調(diào)試模板和一套運(yùn)行模板調(diào)試模板帶明顯邊框方便肉眼確認(rèn)匹配位置。配完流程先用一次真實(shí)文件小跑看截圖日志確認(rèn)每一步都點(diǎn)對(duì)地方再交給定時(shí)任務(wù)長(zhǎng)期跑。長(zhǎng)期跑的過(guò)程中每周扒一次日志和截圖目錄觀察匹配分?jǐn)?shù)有沒(méi)有緩慢下降的趨勢(shì)一旦發(fā)現(xiàn)有下滑跡象提前換模板或調(diào)閾值比等到跑失敗了再救要省心得多。希望這套方案幫到你。本文還有配套的精品資源點(diǎn)擊獲取