幾步教你用 Python 制作一個 RPA 機器人!
?△點擊上方“Python貓”關(guān)注 ,回復(fù)“1”領(lǐng)取電子書

作者 | 阿文
繁瑣且重復(fù)的工作
在我們?nèi)粘5墓ぷ髦?,有很多事情是重?fù)且繁瑣的,組織內(nèi)部不同的部門開發(fā)出來的系統(tǒng)相互之間并沒有過多的依賴關(guān)系,于是使用系統(tǒng)的人在利用現(xiàn)有系統(tǒng)去解決問題,經(jīng)常需要跨越多個不同部門所提供的平臺去進行操作,舉個例子,在我們的日常工作中會依賴工單系統(tǒng)、用戶信息查詢系統(tǒng)、審核系統(tǒng)以及內(nèi)部管理平臺。這四個平臺都是有不同的部門維護的,相互之間數(shù)據(jù)不互通。
有時候用戶提交了一些審核性質(zhì)的工單,其中包含 20 幾張圖片,人工審核下,需要去打開這 20 幾張圖片獲取到指定的信息,然后去上述幾個平臺去做對應(yīng)的操作。比如查詢、提交等等。這樣一來一回一個問題的解決時??赡芫托枰?1 個小時以上。
那么我們能不能通過機器去自動完成這些固定流程且繁瑣的工作呢?答案是肯定的。現(xiàn)階段,我們可以通過引入 RPA 來實現(xiàn)整個流程的打通。
什么是 RPA
RPA 是機器人流程自動化的簡稱,聽起來很高大上的名字,實際上本質(zhì)就是自動化,讓機器幫人去做一些流程固定的事情,機器可以 7* 24小時不停轉(zhuǎn)的完成工作。但是人最多只能 996,畢竟還是要睡覺的,不能剝削的太狠。
RPA 工具選型
RPA 其實出現(xiàn)的時間不短,但是在國內(nèi)興起也就最近幾年的事情,成熟的產(chǎn)品并不多,例如阿里云的RPA、國外的uiPath 等等,但是這些工具對于平臺依賴性較大,他們只能部署在Windows 操作系統(tǒng)上,而我們希望部署在Linux 服務(wù)器上,在命令行模式下運行,這樣可以節(jié)省資源。
基于此,我們決定通過 Python 來實現(xiàn)自動化,由于我們所需要對接的系統(tǒng)大部分都不會給我們提供現(xiàn)成的 API 接口,我們一開始通過 requests 來模擬登錄獲取coookies 進行請求,但是這個過程中發(fā)現(xiàn)很多頁面都是異步加載數(shù)據(jù),而 requests 是同步的,無法獲取數(shù)據(jù),且內(nèi)部系統(tǒng)做了非常嚴格的認證鑒權(quán),僅僅靠 requests、Beautiful Soup 等是搞不定這些鑒權(quán)的。因此我們需要一些工具來實現(xiàn)模擬瀏覽器請求爬取數(shù)據(jù),對比了目前比較流行的幾款開源的自動化工具:
Selenium:老牌自動化測試工具,優(yōu)點是支持大部分主流瀏覽器,它提供了功能豐富的API接口,且支持瀏覽器無頭模式,但是缺點也很明顯,比如速度太慢、對版本配置要求嚴苛,最麻煩是經(jīng)常要更新對應(yīng)的驅(qū)動,每次瀏覽器升級都需要去重新安裝 Chromedriver。
Puppeteer Puppeteer:是一個 Node 庫,它提供了高級 API 來通過 DevTools 協(xié)議控制 Chrome 或 Chromium,簡單理解成我們?nèi)粘J褂玫?Chrome 的無界面版本,可以使用 js 接口進行進行操控。意味凡是 Chrome 瀏覽器能干的事情,Puppeteer 都能出色的完成。
RPAfor Python:這個是我們最開始使用的一款 RPA 工具,它可以很好的滿足我們的需求,且操作也比較簡單, 通過 Xpath 定位元素就可以對 DOM 進行操作,但是其與 Selenium 有著相同的缺點即速度慢,且不支持瀏覽器無頭模式運行,也就是說它需要一個桌面環(huán)境,對資源消耗較大,尤其是 Chromium 這種吃內(nèi)存較大的程序。而我們希望將其部署到 Linux 服務(wù)器上去,所以 Rap for Python 也就無法滿足需求了。
經(jīng)過對比,最終我們選擇了 Puppeteer 的 Python 版本 Pyppeteer 來作為 RPA 工具
Pyppeteer 是什么
Puppeteer(中文翻譯”木偶”) 是 Google Chrome 團隊官方的無界面(Headless)Chrome 工具,它是一個 Node庫,提供了一個高級的 API 來控制 DevTools協(xié)議上的無頭版 Chrome 。也可以配置為使用完整(非無頭)的 Chrome。它非常適合前端開發(fā)者進行自動化測試,而我們除了使用這個自動化工具,還有一些其他功能是基于 Python 來開發(fā)的,比如使用pandas 處理表格,做數(shù)據(jù)分析,所以我們選擇了一個社區(qū)維護的 Pyppeteer ,他的功能幾乎和 Puppeteer 一樣,所以即使是去看 Puppeteer 的文檔也沒多大問題。
puppeteer 可以做很多事情,簡單來說你可以在瀏覽器中手動完成的大部分事情都可以使用?Puppeteer?完成!例如:
生成頁面的截圖和PDF。
抓取SPA并生成預(yù)先呈現(xiàn)的內(nèi)容(即“SSR”)。
從網(wǎng)站抓取你需要的內(nèi)容。
自動表單提交,UI測試,鍵盤輸入等
創(chuàng)建一個最新的自動化測試環(huán)境。使用最新的JavaScript和瀏覽器功能,直接在最新版本的Chrome中運行測試。
捕獲您的網(wǎng)站的時間線跟蹤,以幫助診斷性能問題。
開始使用 Pyppeteer
1.無頭模式配置
在打開瀏覽器的時候,我們需要設(shè)定一些參數(shù),如果你需要它跑在容器里面或純字符模式的 Linux 中,則 headless 參數(shù)必須設(shè)置為 true,同時 args 中的參數(shù)也要加上,它主要是關(guān)閉Chrome 一些沒有必要的功能,例如擴展、flash、音頻和gpu等,以達到節(jié)省資源的目的,executablePath 可以指定瀏覽器的目錄,默認 Pyppeteer 會自動去執(zhí)行 Pyppeteer-install 來下載 Chromium,在國內(nèi)下載極其慢,建議提前安裝好 Chromium。
browser = await launch({'executablePath': self.config["Chromium_path_linux"], #設(shè)置瀏覽器路徑'headless': True,"autoClose": True,"args": ['--disable-extensions','--hide-scrollbars','--disable-bundled-ppapi-flash','--mute-audio','--no-sandbox','--disable-setuid-sandbox','--disable-gpu',],'dumpio': True})
參數(shù)含義
2.異步編碼
由于 Pyppeteer 是異步的因此在 Python 中 需要使用async def 來增加方法。
3.注入cookie
在一些場合,我們需要與 requests 進行結(jié)合,因為整體上 requets 的效率和實現(xiàn)相對比較容易些,可以在必要的時候調(diào)用 Pyppeteer 喚起瀏覽器,因此可以通過設(shè)置cookie 來讓 Pyppeteer 登錄某個頁面
await page.setExtraHTTPHeaders(cookies)4.阻塞
在一些場景,我們需要進行阻塞,比如說頁面加載中,但是程序執(zhí)行的很快,可能還沒加載完就執(zhí)行其他語句了,這樣就拿不到想要的數(shù)據(jù),這個時候可以使用page.waitFor 讓頁面進行等待,不要去使用 time.sleep()
await page.waitFor(3000)一些頁面要善于使用 Page.waitFor。因為有些click 事件程序觸發(fā)過短會無法喚起
5.定位元素
在獲取頁面某個標(biāo)簽內(nèi)的元素是比較常用的方法,可以通過querySelector 先定位到元素,然后通過 page.evaluate 執(zhí)行js 原生方法來拿到標(biāo)簽內(nèi)的文本
status_text = await page.querySelector(".status-text")sussces_info = await page.evaluate('(element) => element.textContent', status_text)
6.截圖
有時候我們需要對頁面的某一段元素進行截圖,我們可以使用page.J 先定位到元素,然后調(diào)用 screenshot 進行截圖
element = await page.J('.ant-table-wrapper')now_unix_time = int(time())image_name = 'screenshot-{}.png'.format(str(now_unix_time))image_path = '/'.join([self.config["images_path"], image_name])await element.screenshot({"path": image_path})
截圖的時候需要設(shè)置瀏覽器的分辨率await page.setViewport({'width': 1280, 'height': 720})7.快速查找元素
很多時候我們不能通過 id、 class 來定位頁面元素的具體路徑,可以借助 Chrome 的開發(fā)者工具,對元素進行定位,快速的找到元素,而 Pyppeteer 提供了多種方式查找元素,如選擇器、xpath
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-d3PXnzb5-1605862399160)(https://file.awen.me/blog/image-20201115074546436.png)]
例如:
await page.querySelector() # 選擇器方式定位元素await page.xpath() # xpath 方式定位元素
8.Page.waitForpage.waitFor(selectorOrFunctionOrTimeout[, options[, …args]]) 下面三個的綜合 API
page.waitForFunction(pageFunction[, options[, …args]]) 等待 pageFunction 執(zhí)行完成之后
page.waitForNavigation(options) 等待頁面基本元素加載完之后,比如同步的 HTML, CSS, JS 等代碼
page.waitForSelector(selector[, options]) 等待某個選擇器的元素加載之后,這個元素可以是異步加載的。
9.使用工具自動生成代碼
如果你對編寫這種枯燥乏味的元素定位感到厭煩,不妨試試Chrome 的插件 Puppeteer recorder ,他可以錄制你的頁面操作,當(dāng)然很多時候并不是很準,但是通過它來輔助開發(fā),可以大大提升你的開發(fā)效率。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7UXYHRDD-1605862399172)(https://file.awen.me/blog/image-20201115074450488.png)]
10.執(zhí)行程序
由于是異步的,因此我們需要通過異步的方式來調(diào)用,同時使用 loop的create_task 方法獲取回調(diào)拿到返回值。
loop = get_event_loop()task = loop.create_task(sync_payment_platform.get_page_image())image_name = loop.run_until_complete(task)
11.無頭模式下的調(diào)試
在我們爬取一些網(wǎng)站時候發(fā)現(xiàn)在正常有Headless 的情況下可以得到最終的效果,但是在無頭模式下會拿不到元素,提示超時。報類似 下面這樣的超時錯誤。
Waiting for selector "#indexPageViewName > div.content-view > div > div > div.left-view > div.searchform.clearfix > div:nth-child(1) > div:nth-child(3) > div > div > div.field-left" failed: timeout 30000ms exceeds.這種情況下我們可以通過上面說的截圖的方式進行Debug,看下當(dāng)前報錯的頁面是否與實際頁面一致,建議配置上 User-Agent。因為某些情況下系統(tǒng)會把頁面當(dāng)成移動端來訪問,導(dǎo)致獲取到的頁面元素與實際不一致。await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36')12.pypuppet 整合 requests
很多時候,一些系統(tǒng)都會提供接口,如果我們能夠直接請求這些接口,效率會更高,但是內(nèi)部系統(tǒng)會使用非常嚴格的校驗,普通的登錄方式是走不通的。不過 pypuppet 可以幫我們繞過鑒權(quán)限制,并拿到對應(yīng)系統(tǒng)的cookies。
cookies = await page.cookies()cookies_info = {}for i in cookies:key_name = i["name"]value_name = i["value"]cookies_info[key_name] = value_name
當(dāng)我們拿到 cookies 后我們就可以通過 requests 模擬 HTTP 請求了,這樣在一些非異步加載的頁面下可以直接爬取接口,節(jié)省了大量的時間和精力。response = self.request_session.post(url, headers=headers, json=payload, cookies=cookies_info)這里可以把緩存信息寫到 Redis 中去,設(shè)置下過期時間,這樣只需要在首次進行登錄,后面直接讀取cookies 進行請求,與此同時,一些網(wǎng)站的請求頭中加了一些自定義的頭,如果缺少這些頭,則無法進行請求,這時候,我們可以通過page.on 來攔截請求或響應(yīng)信息,例如抓取特定的url,拿到對應(yīng)的 headers 將其進行緩存,然后讀取 headers 信息放到請求頭中去,完美的繞過鑒權(quán)。async def intercept_response(self, res):if res.request.url == self.config["api_url"] + "api/web/emp/business:print(f"獲取請求頭 {res.request.headers}")self.redis_connect.set_redis("key", str(res.request.headers))async def login_meike(self):……page.on('response', self.intercept_response)
13.服務(wù)器環(huán)境依賴
我們是將其部署在虛擬機上,由于單位提供的鏡像非常精簡,如果想讓程序能夠在無頭模式下運行,只需要安裝 Xvfb 即可,Xvfb是一個實現(xiàn)了X11顯示服務(wù)協(xié)議的顯示服務(wù)器。不同于其他顯示服務(wù)器,Xvfb在內(nèi)存中執(zhí)行所有的圖形操作,不需要借助任何顯示設(shè)備。執(zhí)行下面的命令即可安裝:
yum -y install Xvfb然后默認centos 的源中是沒有 Chromium 的,需要安裝 epel-release 然后執(zhí)行:yum -y install epel-releaseyum -y install Chromium
接著就可以部署到服務(wù)端運行了。
不過需要注意了,如果你的服務(wù)器沒有安裝中文字體。Chromium 中會顯示方塊字。這個時候只需要安裝上對應(yīng)的中文字體就行
yum -y groupinstall chinese-supportyum -y groupinstall Fonts
案例演示
下面是一個使用 pyppeteer 登錄某網(wǎng)站,我們可以看到這個網(wǎng)站需要輸入手機號、密碼還有
那么我們怎么使用 Pyppeteer 開完成呢?
首先,我們需要定位到手機號和密碼還有驗證碼所在的元素,我們先定義一個函數(shù),用于配置一些基礎(chǔ)的瀏覽器屬性,包括是否要啟用無頭模式,以及關(guān)閉瀏覽器一些沒有用的選項,比如chrome的擴展、瀏覽器的頁面大小和 UserAgent。以及 Webdriver 的屬性, Useragent 和Webdriver 的設(shè)置主要是為了防止別識別是 Pyppeteer 在操作而被攔截,比如淘寶等網(wǎng)站就會有大量的反爬蟲機制識別機器人登錄。
async def open_browser(self):browser = await launch({'executablePath': "c:/chrome-win/chrome.exe",'headless': False, # 是否啟用無頭模式,F(xiàn)alse 會打開瀏覽器,True 則在后臺運行"autoClose": True,"ignoreDefaultArgs": ["--enable-automation"],"args": ['--disable-extensions','--hide-scrollbars','--disable-bundled-ppapi-flash','--mute-audio','--no-sandbox', # --no-sandbox 在 docker 里使用時需要加入的參數(shù),不然會報錯'--disable-setuid-sandbox','--disable-gpu'],'dumpio': True})await page.setViewport({'width': 1920, 'height': 1080}) # 定義瀏覽器的窗口大小,如果太小了,則頁面顯示不全await page.evaluateOnNewDocument('Object.defineProperty(''navigator, "webdriver", {get: () => undefined})')await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) ''AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36')return [page, browser]
定義一個函數(shù)用戶打開網(wǎng)站,輸入用戶名和密碼以及驗證碼 page.type 中的元素地址,我們可以參考上面快速查找元素部分來通過chrome 開發(fā)者模式調(diào)試獲取元素路徑,我們可以看到這個網(wǎng)站他的id 為 userLoginCode 的 input 有2個,但是他們的name 是不一樣的,所以我們可以這樣去選擇。#personLi > td > div > input[name=loginCode]
同時這個網(wǎng)站還有驗證碼。這里的驗證碼我們可以通過一些開放的OCR 識別能力去搞定他,比如百度的OCR 識別。

下面是這個登錄函數(shù)的代碼:
async def login(self):#調(diào)用上面的函數(shù)打開瀏覽器page, browser = await self.open_browser()login_url = "https:/xxx.cn/xxx/"# 打開網(wǎng)站await page.goto(login_url)login_random_time = randint(30, 150)# 獲取頁面驗證碼的圖片元素并截圖verification_code = await page.querySelector("#userGetValidCodeImg > a > img")images_path = "images/verification_code.png"await verification_code.screenshot({'path': images_path})# 通過 OCR 識別驗證碼,如果返回False 則不斷重試,直到登錄成功,如果返回Ture,則輸入用戶名、密碼、驗證碼進行登錄。code = await self.ocr_verification_code(images_path)print(f"當(dāng)前驗證碼 {code}")if code is False:while True:await page.reload()if await self.login_yaohao():breakelse:# 填寫用戶名、密碼和驗證碼并點擊登錄按鈕await page.type('#personLi > td > div > input[name=loginCode]', self.username,{'delay': login_random_time - 50})await page.type('#userPassword', self.password, {'delay': login_random_time - 50})await page.type('#userValidCode', code, {'delay': login_random_time - 50})await page.click('#userLoginButton')await page.waitFor(2000)cookies = await page.cookies()cookies_info = {}for i in cookies:key_name = i["name"]value_name = i["value"]cookies_info[key_name] = value_nameself.redis_connect.set_redis("yaohao", "cookies", str(cookies_info), ex=3600)await browser.close()return cookies_info
通過上述方式我們登錄成功后,就可以拿到cookies。并可以通過定義一個 Session(),然后去請求啦。def __init__(self):super().__init__()self.request_session = Session()
好了,以上就是關(guān)于使用Python 制作 RPA 機器人的分享。

近期熱門文章推薦:

