第八章 網易神器airtest:app版的selenium

在這裏插入圖片描述

簡介

想開發網頁爬蟲,發現被反爬了?想對 App 抓包,發現數據被加密了?不要擔心,使用 Airtest 開發 App 爬蟲,只要人眼能看到,你就能抓到,最快只需要2分鐘,兼容 Unity3D、Cocos2dx-*、Android 原生 App、iOS App、Windows Mobile……。

Airtest是網易出品的一款基於圖像識別和poco控件識別的一款UI自動化測試工具。Airtest的框架是網易團隊自己開發的一個圖像識別框架,這個框架的祖宗就是一種新穎的圖形腳本語言Sikuli。Sikuli這個框架的原理是這樣的,計算機用戶不需要一行行的去寫代碼,而是用屏幕截屏的方式,用截出來的圖形擺列組合成神器的程序,這是Airtest的一部分。

另外,Airtest也基於poco這個UI控件搜索框架,這個框架也是網易自家的跨平臺U測試框架,原理類似於appium,通過控件的名稱,id之類的來定位目標控件,然後調用函數方法,例如click(),swip()之類的方法來對目標控件進行點擊或者是操作。

爬蟲開發本着天下工具爲我所用,能讓我獲取數據的工具都能用來開發爬蟲這一信念,決定使用Airtest來開發手機App爬蟲。

官方文檔 : http://airtest.netease.com/docs/docs_AirtestIDE-zh_CN/

關於自動化測試

概念:自動化測試,就是把繁雜的人工測試用例利用自動化測試工具編寫成代碼,讓機器代替人工自動跑用例的過程。實際上就是把以人爲驅動的測試行爲轉化爲機器執行的一種過程。

意義:節省人力、物力、時間、硬件資源等,提升測試效率,特別對於繁瑣重複的測試用例,可以使測試人員更專注於新的測試模塊的建立和開發,從而提高測試覆蓋率。

市面上常見的UI自動化測試工具以及優缺點

在這裏插入圖片描述

安裝

由於目的是介紹如何使用Airtest來開發App爬蟲,那麼Airtest作爲測試開發工具的方法介紹將會一帶而過,僅僅說明如何安裝並進行基本的操作。

安裝Airtest
從Airtest官網:https://airtest.netease.com下載Airtest,然後像安裝普通軟件一樣安裝即可。安裝過程沒有什麼需要特別說明的地方。Airtest已經幫你打包好了開發需要的全部環境,所以安裝完成Airtest以後就能夠直接使用了。

Airtest運行以後的界面如下圖所示。
在這裏插入圖片描述

連接手機(打開開發者模式,以及USB調試模式)

以Android手機爲例,由於Airtest會通過adb命令安裝兩個輔助App到手機上,再用adb命令通過控制這兩個輔助App進而控制手機,因此首先需要確保手機的adb調試功能是打開的,並允許通過adb命令安裝App到手機上。

啓動Airtest以後,把Android手機連接到電腦上,點擊下圖方框中的refresh ADB,會在右側顯示偵測到的設備:

在這裏插入圖片描述

點擊connect按鈕,此時可以在界面上看到手機的界面,並且當你手動操作手機屏幕時,Airtest中的手機畫面實時更新。如下圖所示。

在這裏插入圖片描述

對於某些手機,例如小米,在第一次使用Airtest時,請注意手機上將會彈出提示,詢問你是否允許安裝App,此時需要點擊允許按鈕。

知識

快速入門

先通過一個簡單的例子,來看看如何快速上手Airtest,稍後再來詳解。

例如我現在想使用電腦控制手機,打開應用商店。

此時,點擊下圖中方框框住的touch按鈕:
在這裏插入圖片描述

此時,把鼠標移動到Airtest右邊的手機屏幕區域,鼠標會變成十字型。在圖標的左上角按下鼠標左鍵不放,並拖到右下角鬆開鼠標。此時請注意中間代碼區域發生了什麼變化,如下圖所示。

在這裏插入圖片描述

好了。以上就是你需要使用電腦打開應用商店所要進行的全部操作。

點擊上方工具欄中的三角形圖標,運行代碼,如下圖所示。

在這裏插入圖片描述

代碼運行完成以後,app被打開了。

打開應用

頁面介紹

在有了一個直觀的使用以後,我們再來介紹一下Airtest的界面,將會更加有針對性。

Airtest的界面如下圖所示。

在這裏插入圖片描述

這裏,我把Airtest分成了6個區域,他們的功能如下:

常用操作功能區
Python代碼編寫區
運行日誌區
手機屏幕區
App頁面佈局信息查看區
工具欄

常用操作功能區是常用的基於圖像識別的屏幕操作功能,例如:

touch: 點擊屏幕元素
swipe: 滑動屏幕
exists: 判斷屏幕元素是否存在
text: 在輸入框中輸入文字
snashot: 截圖
……
一般來說,是點擊A區裏面的某一個功能,然後在D區屏幕上進行框選操作,Python代碼編寫區就會自動生成相應的操作代碼。

Python代碼編寫區用來顯示和編寫Python代碼。在多數情況下,不需要手動寫代碼,因爲代碼會根據你在手機屏幕上面的操作自動生成。只有一些需要特別定製化的動作才需要修改代碼。

手機屏幕區顯示了手機屏幕,當你操作手機真機時,這個屏幕會實時刷新。你也可以直接在手機屏幕區屏幕上使用鼠標操作手機,你的操作動作會被自動在真機上執行。

工具欄區是一些常用工具,從左到右,依次爲:

新建項目
打開項目
保存項目
運行代碼
停止代碼
查看運行報告
其中1-5很好理解,那麼什麼是查看運行報告呢?

當你至少運行了一次以後,點擊這個功能,會自動給你打開一個網頁。網頁如下圖所示,這是你的代碼的運行報告,詳細到每一步操作了什麼元素。

在這裏插入圖片描述

基於App佈局信息操作手機

初始化代碼

App的佈局信息就像網頁的HTML一樣,保存了App上面各個元素的相對位置和各個參數。對於一個App而言,在不同分辨率的手機上,可能相同的元素有着不同的座標點,但是這個元素的屬性參數一般是不會變的。因此,如果使用元素的屬性參數來尋找並控制這個元素,就能實現在不同分辨率手機上的精確定位。

App的佈局信息的格式與App的開發環境有關。點擊頁面結構區的下拉菜單,可以看到這裏能夠指定不同的App開發環境。其中的Unity、Cocos-*等等一般是做遊戲用的,Android是安卓原生App,iOS是蘋果的App……如下圖所示。

在這裏插入圖片描述

以手機版知乎爲例,由於它是Android原生的App,所以在界面系欸狗區下拉菜單選擇Android,此時注意python編碼區彈出提示,詢問你是否要插入poco初始代碼到當前輸入光標的位置,點擊Yes,如下圖所示。
在這裏插入圖片描述
此時,代碼區自動插入了一段代碼,如下圖所示。
在這裏插入圖片描述

定位並點擊

定位並點擊(定位方式和xpath一致,路徑表達式)

現在,點擊界面佈局區的鎖形圖標,如下圖所示。
在這裏插入圖片描述

鎖形圖標激活以後,你再操作手機屏幕區,點擊知乎App下面的知乎兩個字,會發現屏幕上被點擊的App並不會打開。但界面佈局區和日誌區卻發生了變化,如下圖所示。

在這裏插入圖片描述

其中界面佈局區顯示的樹狀結構就是當前屏幕的佈局信息,這與Chrome開發者工具裏面顯示的HTML結構如出一轍。日誌區顯示的是當前被我點中的元素的信息。

請注意在這些元素信息中,有一個text屬性,它的值爲知乎。那麼,這個屬性就可以作爲一個定位元素,於是可以在代碼區編寫代碼:

poco(desc="知乎").click()

寫完代碼以後運行程序,可以看到知乎App被打開了。如下圖所示。

在這裏插入圖片描述

注意,如果你發現手機真機顯示的界面與Airtest屏幕顯示的手機界面不一致,可能是因爲Airtest的屏幕被你鎖定了。在F區點一下鎖形圖標,取消鎖定,Airtest中的手機屏幕就會更新了。

定位並輸入

打開知乎以後,我想使用知乎的搜索功能,那麼繼續,把鎖形圖標激活,然後點擊知乎頂部的搜索框,如下圖所示:

在這裏插入圖片描述

繼續看日誌區顯示的搜索框屬性,可以看到這裏有一個name屬性,它的值是com.zhihu.android:id/input,還有一個text屬性,它的值爲用手機攝影 引爆你的朋友圈 。能不能像前面打開知乎一樣,使用text這個屬性呢?也行,也不行。說它行,是因爲你這麼做確實現在能工作;說它不行,因爲這是知乎的熱門搜索關鍵詞,隨時會改變。你今天使用這一句話成功了,明天熱門關鍵詞變化了,那麼你的代碼就無法使用了。所以此時需要使用name這個屬性。

常見的基本上不會變化的屬性包含但不限於:name type resourceId package。

另外還有一點,知乎首頁的這個搜索框,實際上是不能輸入內容的,當你點擊以後,會跳轉到另一個頁面,如下圖所示。

在這裏插入圖片描述

因此你需要先點擊一下這個輸入框,跳轉到真正的搜索界面:

poco(name="com.zhihu.android:id/input").click()

然後再輸入進行搜索:

可以看到,name屬性的值依然是com.zhihu.android:id/input,此時就可以輸入內容了。

輸入內容使用的方法爲set_text,用法爲:

poco(name="com.zhihu.android:id/input").set_text('爬蟲')

在這裏插入圖片描述

運行查看效果

定位並篩選

輸入了搜索關鍵詞以後,再來看看當前頁面,搜索出現了兩個話題,六個提示:
在這裏插入圖片描述

通過對比這三個結果的屬性信息,發現他們的name屬性都是相同的,而text不同。如果像下面這樣寫點擊動作:

poco(name='com.zhihu.android:id/magi_title').click()

那麼默認就會點擊第一個搜索結果。

如果我想點擊第二個搜索結果怎麼辦呢?可以這樣寫代碼:

poco(name='com.zhihu.android:id/magi_title', text='爬蟲(昆蟲)').click()

或者你也可以像列表一樣使用索引定位:

poco(name='com.zhihu.android:id/magi_title')[1].click()

這兩種寫法的前提,都是我們已經知道了每個結果分別是什麼。假設現在我就想搜索爬蟲,但我不知道搜索結果是第幾項,又應該怎麼辦呢?此時還可以使用正則表達式:

poco(name='com.zhihu.android:id/magi_title', textMatches='^爬蟲.*$').click()

滑動屏幕

進入搜索結果以後,需要查看下面的各種問題,此時就需要不斷向上滑動屏幕。這裏有一點需要特別注意,Airtest只能獲取當前屏幕上的元素佈局信息,不在屏幕上的內容是無法獲取的。這一點和Selenium是不一樣的。

滑動屏幕使用的命令爲swipe,滑動屏幕需要使用座標信息。但這種座標和屏幕分辨率無關。這裏的座標定義爲:(x, y),其中x爲橫座標,y爲縱座標。屏幕左上角爲(0, 0),屏幕右下角爲(1, 1),從左向右,橫座標從0逐漸增大到1,從上到下,縱座標從0逐漸增大到1。

現在我要把屏幕向上滑動,那麼在真機上面,我是先按住屏幕下方,然後把屏幕向上滑動,所以代碼可以這樣寫:

#poco.swipe(起點座標,終點左邊)
poco.swipe([0.5, 0.8], [0.5, 0.2])

在這裏插入圖片描述

在一般情況下:

向上滑動,只需要改動縱座標,且起點值大於終點值
向下滑動,只需要改動縱座標,且起點值小於終點值
向左滑動,只需要改動橫座標,且起點值大於終點值
向右滑動,只需要改動橫座標,且起點值小於終點值

在爬蟲開發中,涉及到的Airtest操作基本上已經介紹完畢。

獲取app信息

既然要做爬蟲,就需要獲取手機上的文字內容。回到搜索頁面,我想知道“爬蟲”這個關鍵字能搜索出多少條結果,每條結果有多少個討論,如下圖所示:

此時我們需要做兩件事情:

分別查看每一個搜索結果
獲取屏幕上的文字
界面結構區的樹狀結構如下圖所示:

在這裏插入圖片描述

每一個搜索結果的標題作爲text屬性的值,在name='com.zhihu.android:id/magi_title’對應的元素中;每一個搜索結果的討論數作爲text屬性的值,在name='com.zhihu.android:id/magi_count’對應的元素中。

最直接的做法就是分別獲取標題和討論數,然後把它們合併在一起:

title_obj_list = poco(name='com.zhihu.android:id/magi_title')
title_list = [title.get_text() for title in title_obj_list]

discuss_obj_list = poco(name='com.zhihu.android:id/magi_count')
discuss_list = [discuss.get_text() for discuss in discuss_obj_list]

for title, discuss in zip(title_list, discuss_list):
    print(title, discuss)

運行效果如下圖所示:
在這裏插入圖片描述

但是這種做法實際上是很危險的,假設會有某一個很生僻的搜索結果,只有標題沒有討論數,那麼這樣分開抓取再組合的做法,就會導致最後匹配錯位。所以合理的做法是先抓大再抓小。每一組標題和討論數,他們都有自己的父節點,如下圖箭頭所指向的android.widget.LinearLayout:

那麼現在,使用先抓大再抓小的技巧,先把每一組結果的父節點抓下來,再到每一個結果裏面分別獲取標題和討論數。
然而這個父節點又怎麼獲取呢?如下圖所示,這個父節點每一個屬性值都沒有什麼特殊的,寫任何一個都有可能與別的節點撞上。
此時,最簡單的辦法,就是在界面結構區,雙擊父節點。定位代碼就會自動添加。
這個定位代碼看起來非常複雜,但實際上它的內在邏輯非常簡單,就是從頂層一層一層往下找而已。

自動生成的定位代碼如下:

poco("android.widget.LinearLayout").offspring("com.zhihu.android:id/action_bar_root").offspring("com.zhihu.android:id/parent_fragment_content_id").offspring("android.support.v7.widget.RecyclerView").child("android.widget.LinearLayout")[0]

在這個自動生成的定位代碼中,我們看到了offspring、child這兩種方法。其中child代表子節點,offspring代表孫節點、孫節點的子節點、孫節點的孫節點……。簡言之,使用child只會在子節點中搜索需要的內容,而使用offspring會像文件夾遞歸一樣把裏面的所有節點都遍歷一次,直到找到符合條件的屬性爲止。顯然,offspring速度會比child慢。

實際上,我們可以對這個定位代碼做一些精簡:

poco("com.zhihu.android:id/parent_fragment_content_id").offspring("android.support.v7.widget.RecyclerView").child("android.widget.LinearLayout")[0]

這個精簡的方法,與從Chrome複製的XPath中進行精簡是一樣的邏輯,根本原則就是找到“獨一無二”的屬性值,然後用這個屬性值來進行定位。
由於我點擊的是第一個搜索結果,所以定位代碼的最後有一個[0]。現在由於需要獲得所有搜索結果的內容,所以應該去掉[0]而使用for循環展開,然後獲取裏面的內容:

result_obj = poco("com.zhihu.android:id/parent_fragment_content_id").offspring("android.support.v7.widget.RecyclerView").child("android.widget.LinearLayout")
for result in result_obj:
    title = result.child(name='com.zhihu.android:id/magi_title').get_text()
    count = result.child(name='com.zhihu.android:id/magi_count').get_text()
    print(title, count)

運行效果和之前一致,但更安全。

單獨使用Python控制手機

在Airtest操作手機雖然方便,但是不可能在每一臺電腦上都安裝Airtest吧。所以需要想辦法把代碼從Airtest這個程序中分離出來。

Airtest基於Python的一個開源庫Poco開發,而在Airtest的B區寫的Python代碼,實際上就是Poco的代碼。所以只要安裝Poco庫,就可以在Python中直接控制手機。

安裝Poco庫的命令爲:

pip install pocoui

這個庫依賴的東西有點多,安裝稍稍慢一些。安裝完成以後,我們把代碼複製到PyCharm中,如下圖所示。

在這裏插入圖片描述

常用api

  1. 路徑
    child:查找子節點,等同於xpath表達式 /
    offspring:查找子節點,等同於xpath表達式//
    children:查找子節點,等同於xpath表達式/*

  2. 動作
    click:相當於selenium的click
    set_text:相當於selenium的send_keys
    swipe:相當於selenium的driver.execute_script(“window.scrollTo(0, document.body.scrollHeight)”)

  3. 訪問屬性
    get_text:獲取節點文本,等同於xpath表達式 /text()
    attr:獲取節點屬性,等同於xpath表達式/@屬性名

任務

爬取餓了嗎app商家信息
在這裏插入圖片描述

爬取美團外賣app商家信息
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章