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
:單純用於收發資料包的頁面物件
衍生物:
ChromiumTab
:ChromiumPage
生成的標籤頁物件WebPageTab
:WebPage
生成的標籤頁物件ChromiumFrame
:<iframe>
元素物件ChromiumElement
:瀏覽器元素物件SessionElement
:靜態元素物件ShadowRoot
:shadow-root 元素物件
稱呼
文件裡經常用到這幾個稱呼:
ChromiumPage
、WebPage
統稱為 Page 物件ChromiumTab
、WebPageTab
統稱為 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
要匹配的文字,查詢字串如開頭沒有任何關鍵字,也表示根據傳入的文字作模糊查詢。
如果元素內有多個直接的文字節點,精確查詢時可匹配所有文字節點拼成的字串,模糊查詢時可匹配每個文字節點。
沒有任何匹配符時,預設匹配文字。
如果要匹配的文字包含特殊字元(如' '
、'>'
),需將其轉換為十六進位制形式,詳見《語法速查表》一節。
# 查詢文字為“第二行”的元素 ele2 = ele1.ele('text=第二行') # 查詢文字包含“第二”的元素 ele2 = ele1.ele('text:第二') # 與上一行一致 ele2 = ele1.ele('第二') # 匹配包含 文字的元素 ele2 = ele1.ele('第\u00A0二') # 需將 轉為\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():text
和 tag: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官網