自動化工具DrissionPage

RChow發表於2024-04-15

DrissionPage 是一個基於 python 的網頁自動化工具。它既能控制瀏覽器,也能收發資料包,還能把兩者合而為一。可兼顧瀏覽器自動化的便利性和 requests 的高效率。它功能強大,內建無數人性化設計和便捷功能。它的語法簡潔而優雅,程式碼量少,對新手友好。

DrissionPage與selenium類似,但比selenium簡單很多,不需要瀏覽器的驅動檔案,可以直接使用。因此,嘗試使用一下DrissionPage。

網頁自動化

網頁自動化的形式通常有兩種,它們各有優劣:

  • 直接向伺服器傳送資料包,獲取需要的資料
  • 控制瀏覽器跟網頁進行互動

前者輕量級,速度快,便於多執行緒、分散式部署,如 requests 庫。但當資料包構成複雜,甚至加入加密技術時,開發過程燒腦程度直線上升。

鑑於此,DrissionPage 以頁面為單位將兩者整合,對 Chromium 協議 和 requests 進行了重新封裝,實現兩種模式的互通,並加入常用的頁面和元素控制功能,可大幅降低開發難度和程式碼量。
用於操作瀏覽器的物件叫 Driver,requests 用於管理連線的物件叫 Session,Drission 就是它們兩者的合體。Page 表示以 POM 模式封裝。 在舊版本,本庫是透過對 selenium 和 requests 的重新封裝實現的。
從 3.0 版開始,作者另起爐灶,用 chromium 協議自行實現了 selenium 全部功能,從而擺脫了對 selenium 的依賴,功能更多更強,執行效率更高,開發更靈活。

基本使用邏輯

無論是控制瀏覽器,還是收發資料包,其操作邏輯是一致的。

即先建立頁面物件,然後從頁面物件中獲取元素物件,透過對元素物件的讀取或操作,實現資料的獲取或頁面的控制。

因此,最主要的物件就是兩種:頁面物件,及其生成的元素物件。

主要物件

主頁面物件有 3 種,它們通常是程式的入口:

  • ChromiumPage:單純用於操作瀏覽器的頁面物件
  • WebPage:整合瀏覽器控制和收發資料包於一體的頁面物件
  • SessionPage:單純用於收發資料包的頁面物件

衍生物:

  • ChromiumTabChromiumPage生成的標籤頁物件
  • WebPageTabWebPage生成的標籤頁物件
  • ChromiumFrame<iframe>元素物件
  • ChromiumElement:瀏覽器元素物件
  • SessionElement:靜態元素物件
  • ShadowRoot:shadow-root 元素物件

稱呼

文件裡經常用到這幾個稱呼:

  • ChromiumPageWebPage統稱為 Page 物件
  • ChromiumTabWebPageTab統稱為 Tab 物件
  • Page 物件、Tab 物件和ChromiumFrame統稱為頁面對

工具安裝DrissionPage,直接使用pip進行安裝即可

pip install DrissionPage

嘗試讀取郵箱郵件資訊

程式碼:

from DrissionPage import WebPage

page = WebPage()
# 登入郵箱
page.get("http://mail.163.com")
page('mail').clear()
page('mail').input("xxx") # 賬戶
page('#pwdtext').input("password") # 密碼
page('#dologin').click() # 點選登入
# 等待載入
page.wait.load_start()
# 進入訂閱郵件欄
page('xpath:/html/body/div[1]/nav/div[2]/ul/li[9]/div').click()
items = page('@class:tv0').eles('@class:nl0 hA0 ck0')
while True:
    for item in items:
        print("發件人:"+item.ele('@class:gB0').text, "主題:"+item.ele('@class:da0').text)

    btn = page('下一頁', timeout=2)
    if btn:
        btn.click()
        page.wait.load_start()
    else:
        break

執行截圖

檢視原始碼,瞭解一些基礎內容


關鍵:頁面元素定位

示例程式碼:

<html>
<body>
<div id="one">
    <p class="p_cls" name="row1">第一行</p>
    <p class="p_cls" name="row2">第二行</p>
    <p class="p_cls">第三行</p>
</div>
<div id="two">
    第二個div
</div>
</body>
</html>

用頁面物件去獲取其中的元素:

 1 # 獲取 id 為 one 的元素
 2 div1 = page.ele('#one')
 3 
 4 # 獲取 name 屬性為 row1 的元素
 5 p1 = page.ele('@name=row1')
 6 
 7 # 獲取包含“第二個div”文字的元素
 8 div2 = page.ele('第二個div')
 9 
10 # 獲取所有div元素
11 div_list = page.eles('tag:div')
12 
13 # 也可以獲取到一個元素,然後在它裡面或周圍查詢元素
14 # 獲取到一個元素div1
15 div1 = page.ele('#one')
16 
17 # 在div1內查詢所有p元素
18 p_list = div1.eles('tag:p')
19 
20 # 獲取div1後面一個元素
21 div2 = div1.next()

頁面元素查詢語法

id 匹配符 #

表示id屬性,只在語句最前面且單獨使用時生效,可配合匹配模式使用。

1 # 在頁面中查詢id屬性為one的元素
2 ele1 = page.ele('#one')
3 
4 # 在ele1元素內查詢id屬性包含ne文字的元素
5 ele2 = ele1.ele('#:ne')  

class 匹配符 .

表示class屬性,只在語句最前面且單獨使用時生效,可配合匹配模式使用。

1 # 查詢class屬性為p_cls的元素
2 ele2 = ele1.ele('.p_cls')
3 
4 # 查詢class屬性'_cls'文字開頭的元素
5 ele2 = ele1.ele('.^_cls')  

因為只加 . 時預設是精確匹配元素屬性 class,所以如果某元素有多個類名,必須寫 class 屬性的完整值(類名的順序也不能變)。如果需要只匹配多個類名中的一個,可以使用模糊匹配符 :

1 # 精確查詢class屬性為`p_cls1 p_cls2 `的元素
2 ele2 = ele1.ele('.p_cls1 p_cls2 ')
3 
4 # 模糊查詢class屬性含有類名 'p_cls2' 的元素
5 ele2 = ele1.ele('.:p_cls2')  

單屬性匹配符 @

表示某個屬性,只匹配一個屬性。可單獨使用,也可與tag配合使用。

@關鍵字只有一個簡單功能,就是匹配@後面的內容,不再對後面的字串進行解析。因此即使後面的字串也存在@@@ ,也作為要匹配的內容對待。所以只要是多屬性匹配,包括第一個屬性在內的所有屬性都必須用@@開頭。

注意:

如果屬性中包含特殊字元(如包含@),用這個方式不能正確匹配到,需使用 css selector 方式查詢。且特殊字元要用\轉義。

 1 # 查詢name屬性為row1的元素
 2 ele2 = ele1.ele('@name=row1')
 3 
 4 # 查詢name屬性包含row文字的元素
 5 ele2 = ele1.ele('@name:row')
 6 
 7 # 查詢name屬性以row開頭的元素
 8 ele2 = ele1.ele('@name^row')
 9 
10 # 查詢有name屬性的元素
11 ele2 = ele1.ele('@name')
12 
13 # 查詢沒有任何屬性的元素
14 ele2 = ele1.ele('@')
15 
16 # 查詢email屬性為abc@def.com的元素,有多個@也不會重複處理
17 ele2 = ele1.ele('@email=abc@def.com')
18 
19 # 屬性中有特殊字元的情形,匹配abc@def屬性等於v的元素
20 ele2 = ele1.ele('css:div[abc\@def="v"]')

多屬性與匹配符 @@

匹配同時符合多個條件的元素時使用,每個條件前面新增@@作為開頭。

可單獨使用,也可與tag配合使用。

注意
  • 匹配文字或屬性中出現@@@|@!時,不能使用多屬性匹配,需改用 xpath 的方式。
  • 如果屬性中包含特殊字元(如包含@),用這個方式不能正確匹配到,需使用 css selector 方式查詢。且特殊字元要用\轉義。
# 查詢name屬性為row1且class屬性包含cls文字的元素
ele2 = ele1.ele('@@name=row1@@class:cls')

@@可以與下文介紹的tag配合使用:

ele = page.ele('tag:div@@class=p_cls@@name=row1')

多屬性或匹配符@|

匹配符合多個條件中任一項的元素時使用,每個條件前面新增@|作為開頭。

可單獨使用,也可與tag配合使用。

用法與@@一致,注意事項與@@一致。

注意

@@@|不能同時出現在語句中。

1 # 查詢id屬性為one或id屬性為two的元素
2 ele2 = ele1.ele('@|id=one@|id=two')

@|可以與下文介紹的tag配合使用:

1 ele = page.ele('tag:div@|class=p_cls@|name=row1')

屬性否定匹配符@!

用於否定某個條件,可與@@@|混用,也可單獨使用。

混用時,與還是或關係視@@還是@|而定。

示例:

# 匹配arg1等於abc且arg2不等於def的元素
page.ele('@@arg1=abc@!arg2=def')

# 匹配arg1等於abc或arg2不等於def的div元素
page.ele('t:div@|arg1=abc@!arg2=def')

# 匹配arg1不等於abc
page.ele('@!arg1=abc')

# 匹配沒有arg1屬性的元素
page.ele('@!arg1')

文字匹配符 text

要匹配的文字,查詢字串如開頭沒有任何關鍵字,也表示根據傳入的文字作模糊查詢。
如果元素內有多個直接的文字節點,精確查詢時可匹配所有文字節點拼成的字串,模糊查詢時可匹配每個文字節點。

沒有任何匹配符時,預設匹配文字。

注意

如果要匹配的文字包含特殊字元(如'&nbsp;''&gt;'),需將其轉換為十六進位制形式,詳見《語法速查表》一節。

# 查詢文字為“第二行”的元素
ele2 = ele1.ele('text=第二行')

# 查詢文字包含“第二”的元素
ele2 = ele1.ele('text:第二')

# 與上一行一致
ele2 = ele1.ele('第二')

# 匹配包含&nbsp;文字的元素
ele2 = ele1.ele('第\u00A0二')  # 需將&nbsp;轉為\u00A0

若要查詢的文字包含text: ,可下面這樣寫,即第一個text: 為關鍵字,第二個是要查詢的內容:

ele2 = page.ele('text:text:')

文字匹配符 text()

作為查詢屬性時使用的文字關鍵字,必須與@@@配合使用。

說明

text在作為屬性查詢條件是改為text(),是為了避免遇到名為text的屬性時產生衝突。

# 查詢文字為“第二行”的元素
ele2 = ele1.ele('@text()=第二行')

# 查詢文字包含“第二行”的元素
ele2 = ele1.ele('@text():二行')

# 查詢文字以“第二”開頭且class屬性為p_cls的元素
ele2 = ele1.ele('@@text()^第二@@class=p_cls')

# 查詢文字為“二行”且沒有任何屬性的元素(因第一個 @@ 後為空)
ele2 = ele1.ele('@@@@text():二行')

# 查詢直接子文字包含“二行”字串的元素
ele = page.ele('@text():二行')

@@text()的技巧

值得一提的是,text()配合@@@|能實現一種很便利的按查詢方式。

網頁種經常會出現元素和文字混排的情況,比如:

<li class="explore-categories__item">
    <a href="/explore/new-tech" class="">
        <i class="explore"></i>
        前沿技術
    </a>
</li>
<li class="explore-categories__item">
    <a href="/explore/program-develop" class="">
        <i class="explore"></i>
        程式開發
    </a>
</li>

示例中,如果要用文字獲取'前沿技術'<a>元素,可以這樣寫:

ele = page.ele('text:前沿技術')
#
ele = page.ele('@text():前沿技術')

這兩種寫法都能獲取到包含直接文字的元素。

但如果要用文字獲取<li>元素,就獲取不到,因為文字不是<li>的直接內容。

我們可以這樣寫:

ele = page.ele('tag:li@@text():前沿技術')

@@text()@text()不同之處在於,前者可以搜尋整個元素內所有文字,而不僅僅是直接文字,因此能實現一些非常靈活的查詢。

注意

需要注意的是,使用@@@|時,text()不要作為唯一的查詢條件,否則會定位到整個文件最高層的元素。

❌ 錯誤做法:

ele = page.ele('@@text():前沿技術')
ele = page.ele('@|text():前沿技術@|text():程式開發')

⭕ 正確做法:

ele = page.ele('tag:li@|text():前沿技術@|text():程式開發')

型別匹配符 tag

表示元素的標籤,只在語句最前面且單獨使用時生效,可與@@@@|配合使用。tag:tag=效果一致,沒有tag^tag$語法。

# 定位div元素
ele2 = ele1.ele('tag:div')

# 定位class屬性為p_cls的p元素
ele2 = ele1.ele('tag:p@class=p_cls')

# 定位文字為"第二行"的p元素
ele2 = ele1.ele('tag:p@text()=第二行')

# 定位class屬性為p_cls且文字為“第二行”的p元素
ele2 = ele1.ele('tag:p@@class=p_cls@@text()=第二行')

# 定位class屬性為p_cls或文字為“第二行”的p元素
ele2 = ele1.ele('tag:p@|class=p_cls@|text()=第二行')

# 查詢直接文字節點包含“二行”字串的p元素
ele2 = ele1.ele('tag:p@text():二行')

# 查詢內部文字節點包含“二行”字串的p元素
ele2 = ele1.ele('tag:p@@text():二行')  
注意

tag:div@text():texttag:div@@text():text 是有區別的,前者只在div的直接文字節點搜尋,後者搜尋div的整個內部。

css selector 匹配符 css

表示用 css selector 方式查詢元素。css:css=效果一致,沒有css^css$語法。

# 查詢 div 元素
ele2 = ele1.ele('css:.div')

# 查詢 div 子元素元素,這個寫法是本庫特有,原生不支援
ele2 = ele1.ele('css:>div')  

xpath 匹配符 xpath

表示用 xpath 方式查詢元素。xpath:xpath=效果一致,沒有xpath^xpath$語法。

另外,元素物件的ele()支援完整的 xpath 語法,如能使用 xpath 直接獲取元素屬性(字串型別)。

# 查詢後代中第一個 div 元素
ele2 = ele1.ele('xpath:.//div')

# 和上面一行一樣,查詢元素的後代時,// 前面的 . 可以省略
ele2 = ele1.ele('xpath://div')

# 使用xpath獲取div元素的class屬性(頁面元素無此功能)
ele_class_str = ele1.ele('xpath://div/@class')

查詢元素的後代時,selenium 原生程式碼要求 xpath 前面必須加.,否則會變成在全個頁面中查詢。 作者覺得這個設計是畫蛇添足,既然已經透過元素查詢了,自然應該只查詢這個元素內部的元素。 所以,用 xpath 在元素下查詢時,最前面///前面的.可以省略。

selenium 的 loc 元組

查詢方法能直接接收 selenium 原生定位元組進行查詢,便於專案遷移。

from DrissionPage.common import By

# 查詢id為one的元素
loc1 = (By.ID, 'one')
ele = page.ele(loc1)

# 按 xpath 查詢
loc2 = (By.XPATH, '//p[@class="p_cls"]')
ele = page.ele(loc2)  

簡化寫法

定位符語法簡化

  • 定位語法都有其簡化形式
  • 頁面和元素物件都實現了__call__()方法,所以page.ele('...')可簡化為page('...')
  • 查詢方法都支援鏈式操作

示例:

# 查詢tag為div的元素
ele = page.ele('tag:div')  # 原寫法
ele = page('t:div')  # 簡化寫法

# 用xpath查詢元素
ele = page.ele('xpath://xxxxx')  # 原寫法
ele = page('x://xxxxx')  # 簡化寫法

# 查詢text為'something'的元素
ele = page.ele('text=something')  # 原寫法
ele = page('tx=something')  # 簡化寫法

簡化寫法對應列表

原寫法簡化寫法說明
@id # 表示 id 屬性,簡化寫法只在語句最前面且單獨使用時生效
@class . 表示 class 屬性,簡化寫法只在語句最前面且單獨使用時生效
text tx 按文字匹配
@text() @tx() 按文字查詢與 @ 或 @@ 配合使用時
tag t 按標籤型別匹配
xpath x 用 xpath 方式查詢元素
css c 用 css selector 方式查詢元素

詳細內容,可參考:🌏 安裝 | DrissionPage官網

相關文章