今日繼續整理 Selenium 用法, 本系列之前的文章參考 :
六. 動作鏈 (Action Chains) :
從前面的測試中可知, Selenium 可用的網頁頁面操作是呼叫 WebElement 物件的下列三個方法 :
- send_keys() : 模擬鍵盤的輸入動作
- click() : 模擬滑鼠的點擊動作
- submit() : 提交表單
可見能執行的操作很有限. 其實最完整的滑鼠與鍵盤操作如滑鼠左鍵雙擊 (double click) 或右鍵快顯等動作都封裝在 selenium.webdriver.common.action_chains 模組的 ActionChains 類別中, 可以模擬所有的手動操作, 所以動作鏈才是 Selenium 操作瀏覽器的百寶箱.
動作鏈可用鏈式操作來進行一連串的滑鼠移動, 按鍵與鍵盤輸入, 特別是模擬下拉式功能表選項的點選, 如果直接呼叫選像物件的 click() 方法有時會因為位置定位不正確或被其他元素覆蓋而出現 'not clickable' 錯誤時, 使用動作鏈通常可解決, 提高爬蟲程式的強固性 (robotness). 因為動作鏈就是模仿手動操作那樣, 一步步走到目標元素後才呼叫 perform() 去執行, 因為除了一個動作亦行指令寫法外, 還可以用鏈式呼叫寫法, 故稱為動作鏈 :
ActionChains 物件.action1.action2.actions3. .... actionx.perform()
使用前要先匯入 ActionChains 類別 :
>>> from selenium.webdriver.common.action_chains import ActionChains
用 dir() 檢視類別成員 :
>>> dir(ActionChains)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'click', 'click_and_hold', 'context_click', 'double_click', 'drag_and_drop', 'drag_and_drop_by_offset', 'key_down', 'key_up', 'move_by_offset', 'move_to_element', 'move_to_element_with_offset', 'pause', 'perform', 'release', 'reset_actions', 'scroll_by_amount', 'scroll_from_origin', 'scroll_to_element', 'send_keys', 'send_keys_to_element']
可見 ActionChains 類別定義了許多滑鼠與鍵盤的操作函式, 當傳入 WebDriver 物件建立 ActionChains 實體時, 這些函式就是其方法, 說明如下 :
ActionChains 物件常用方法 | 說明 |
click(element) | 在元素 element 上按一下滑鼠左鍵 |
double_click(element) | 在元素 element 上點擊滑鼠左鍵兩下 |
click_and_hold(element) | 在元素 element 上長按滑鼠左鍵 |
context_click(element) | 在元素 element 上長按滑鼠右鍵 |
drag_and_drop(ele1, ele2) | 在元素 ele1 上長按滑鼠左鍵並拖曳到元素 ele2 上放開滑鼠 |
drag_and_drop_by_offset(ele, x, y) | 在元素 ele1 上長按滑鼠左鍵並拖曳偏移座標量 (x, y) |
move_to_element(element) | 將滑鼠移動到 element 元素上 |
move_to_element_with_offset( element, x, y) | 將滑鼠移動到 element 元素上並偏移 (x, y) 座標量 |
move_by_offset(x, y) | 將滑鼠從目前位置移動 (x, y) 偏移量 |
send_keys(text) | 在目前的元素上輸入文字 text |
send_keys_to_element(ele, text) | 在元素 ele 上輸入文字 text |
key_up(Keys.xxx, element) | 在元素 element 上放開鍵盤的 Key.xxx 鍵 |
key_down(Keys.xxx, element) | 在元素 element 上按下鍵盤的 Key.xxx 鍵 |
release(element) | 在元素 element 上放開滑鼠之長按 |
pause(s) | 暫停執行 s 秒 |
perform() | 執行前面動作鏈的事件 |
以 Python 官網首頁 https://www.python.org/ 為例, 該網頁上方有一排功能選單, 當滑鼠移到選單標題上時會顯示選單 (注意, 只要 hover 就會顯現, 點擊反而會使選單消失). 以下用兩種方式來操作瀏覽器, 從 Downloads 選單中點擊 Windows 選項來切換至 Windows 版本的 Python 下載頁 :
- 直接定位元素後呼叫其物件之 click() 方法
- 使用動作鏈來逐步操作
對於這種有選單的網頁, 通常第一種方法會出現例外或錯誤, 無法達成任務, 解決方案就是改用動作鏈,
在 Chrome 開發者工具的 Element 頁籤中搜尋 "Downloads" 會發現此下載選單是一個 id='downloads' 的 li 元素, 裡面的選項是由 ul-li 製作的, Windows 下載網址位於 class='tier-2 element-3' 的 li 所包裹的 a 元素裡面, 只要找出此 a 元素物件, 呼叫 click() 即可.
首先建立一個代表 Firefox 瀏覽器的 WebDriver 物件 :
>>> from selenium import webdriver
>>> driver=webdriver.Firefox()
>>> type(driver)
<class 'selenium.webdriver.firefox.webdriver.WebDriver'>
然後呼叫 driver 的 get() 方法載入 Python 官網 :
>>> driver.get('https://www.python.org/')
我們先用 id 搜尋外層的 li 元素, 然後用得到的 WebElement 物件的 find_elements() 搜尋裡面的所有 li 元素, 可見共有 8 個 li, Windows 的下載網址放在第 3 個 li (索引 2) 所包覆的 a 元素裡 :
>>> downloads_li=driver.find_element('id', 'downloads')
>>> inner_lis=downloads_li.find_elements('tag name', 'li')
>>> len(inner_lis)
8
>>> windows_link=inner_lis[2].find_element('tag name', 'a')
>>> windows_link.get_attribute('href')
'https://www.python.org/downloads/windows/'
當然也可以用外層 li 物件直接搜尋它裡面的 a 元素 (跳過內層 li), 會找到 15 個, 這時 Windows 的下載超連結在第 4 個 a 元素 (索引 3) :
>>> inner_links=downloads_li.find_elements('tag name', 'a')
>>> len(inner_links)
15
>>> inner_links[3].get_attribute('href')
'https://www.python.org/downloads/windows/'
不管是用上面哪一種方式取得 Windows 的下載超連結 a 元素的 WebElement 物件, 呼叫其 click() 方法都會出現如下 ElementNotInteractableException 錯誤 :
>>> inner_links[3].click() # 或用 windows_link.click()
.....
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.ElementNotInteractableException: Message: Element <a href="/downloads/windows/"> could not be scrolled into view
完整程式碼如下 :
# selenium_action_chains_1.py (此程式會出現例外)
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
driver=webdriver.Firefox()
driver.implicitly_wait(30)
driver.get('https://www.python.org/')
downloads_li=driver.find_element('id', 'downloads')
inner_lis=downloads_li.find_elements('tag name', 'li')
windows_link=inner_lis[2].find_element('tag name', 'a')
windows_link.click()
driver.close()
這種有選單的網頁如果改用動作鏈通常就能解決. 先把 WebDriver 物件傳給 ActionChains 的建構式 ActionChains(), 它會傳回一個 ActionChains 物件 :
>>> actions=ActionChains(driver)
然後呼叫 move_to_element() 方法將滑鼠移到上面取得的外層 li 元素 (即 "Downloads" 這個選單), 它會傳回 ActionChains 物件 (代表滑鼠) 目前的位置是在哪個物件上 :
>>> actions.move_to_element(downloads_li)
<selenium.webdriver.common.action_chains.ActionChains object at 0x0000013E56B34B50>
當滑鼠移到 "Downloads" 時選單應該已經展開 (但還沒呼叫 perform 不會真的出現, 這是假想的), 注意, 這些選單都是 hover 就出現的, 千萬不要去按, 按的話選單就消失了, 而是直接按 Windows 連結就可以, 當然, 如果你要先呼叫 move_to_element(windows_link) 將滑鼠移到 Windows 連結上再按也可以, 但那是多此一舉, 因為在選單顯現時 Windows 連結就已出現, 直接按它即可 :
>>> actions.click(windows_link)
<selenium.webdriver.common.action_chains.ActionChains object at 0x0000013E56B34B50>
最後呼叫 perform() 方法即可得到目標網頁 :
>>> actions.perform()
>>> actions.move_to_element(downloads_li).click(windows_link).perform()
得到目標網頁後呼叫 driver.close() 關閉瀏覽器 :
>>> driver.close()
完整程式碼如下 :
# selenium_action_chains_2
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import time
driver=webdriver.Firefox()
driver.implicitly_wait(30)
driver.get('https://www.python.org/')
downloads_li=driver.find_element('id', 'downloads')
inner_lis=downloads_li.find_elements('tag name', 'li')
windows_link=inner_lis[2].find_element('tag name', 'a')
actions=ActionChains(driver)
actions.move_to_element(downloads_li)
#actions.move_to_element(windows_link) # 多此一舉
actions.click(windows_link)
actions.perform()
time.sleep(10)
driver.close()
此處使用 time.sleep(10) 讓頁面停留 10 秒以便能確認已到達目標網頁.
參考 :
沒有留言 :
張貼留言