2024年6月5日 星期三

Python 學習筆記 : Selenium 4 用法 (上)

最近使用 Selenium 寫了兩個圖書館爬蟲之後, 覺得有必要來整理與更新 Selenium 第四版新用法的筆記, 因為現在新版的 API 介面改變甚多, 我之前於 2018 年寫的筆記用的是第 2 版, 許多函式與方法已經無法在 v4 環境使用, 為了之後專案查考方便, 花點時間重寫是必要的. 
 
本系列之前的文章參考 :


Selenium 教學文件 :
 

關於 Selenium 摘要如下 : 
  • Selenium 起源於 Jason Huggins 於 2004 年創建的一個用來驗證瀏覽器頁面行為的自動化專案, 由於當時的主流自動化工具是 QTP Mercury, 因此取名為汞的剋星 Selenium (硒). Selenium 除了廣泛用在自動化測試與自動化流程外, 它也是用來擷取 Javascript 生成之動態網頁內容的爬蟲工具. 
  • Selenium 2.0 引入了針對不同瀏覽器而開發的 Web Driver, 透過原生瀏覽器的支援 (包含  Chrome, Chromium, Firefox, Edge, Safari 等主流瀏覽器), 解除了第 1 版透過 Selenium RC 執行 Javascript 遇到的沙箱安全模型限制, 且提供 C#, JavaScript, Java, Python, Ruby 等程式語言之 API. 
  • Web Driver 是一種遠端控制介面, 它透過一個跨平台與跨程式語言的 Wire Protocol 與介面來定位 DOM 元素與控制瀏覽器之行為.
  • Selenium 4 於 2018 年發布, 改寫了許多 API 介面, 刷新了相對定位器, 以及實作了完整的 W3C 標準協定之 WebDriver API 等功能 (拋棄了第三版的 JSON Wire Protocol), 使 WebDriver 能直接與目標瀏覽器互動以擁有完整的操控能力. 
參考 :


參考書籍 :
其中 No.1 使用新版 Selenium, 其餘均使用舊版. 


一. 安裝 Selenium :  

Selenium 是第三方套件, 可用 pip 安裝 :

pip install selenium 

如果已安裝過 Selenium 套件想要更新至最新版, 可加上 -U 參數 : 

pip install selenium -U

安裝完成後匯入 selenium 檢視版本 :

>>> import selenium 
>>> selenium.__version__    
'4.11.2'

用 dir() 檢視其成員 :

>>> dir(selenium)     
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'common', 'types', 'webdriver']   

其中 webdriver 模組是 Selenium 的主角, 它提供了用來建立各瀏覽器驅動程式的類別, common 模組則是配角, 它提供了共用的常數, 例如收納按鍵常數的 Keys 類別, 用來模擬滑鼠操作的 ActionChains 類別, 以及新版搜尋元素方法 find_element() 與 find_elements() 必須傳入的第一參數 (例如 By_NAME) 都放在 common 模組裡. 

先用 dir() 檢視 webdriver 模組的成員 :

>>> dir(webdriver)   
['ActionChains', 'Chrome', 'ChromeOptions', 'ChromeService', 'ChromiumEdge', 'DesiredCapabilities', 'Edge', 'EdgeOptions', 'EdgeService', 'Firefox', 'FirefoxOptions', 'FirefoxProfile', 'FirefoxService', 'Ie', 'IeOptions', 'IeService', 'Keys', 'Proxy', 'Remote', 'Safari', 'SafariOptions', 'SafariService', 'WPEWebKit', 'WPEWebKitOptions', 'WPEWebKitService', 'WebKitGTK', 'WebKitGTKOptions', 'WebKitGTKService', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'chrome', 'chromium', 'common', 'edge', 'firefox', 'ie', 'remote', 'safari', 'support', 'webkitgtk', 'wpewebkit']

其中 Chrome, Firefox, Edge, Ie, 與 Safari 六個類別就是用來建立操控瀏覽器的 WebDriver 物件的類別, 呼叫它們的建構式就可以建立操控瀏覽器的 WebDriver 物件 (此物件是 Selenium 與瀏覽器驅動程式之間的介面), 不過在此之前必須先下載這些瀏覽器的驅動程式 (Web Driver). 


二. 下載瀏覽器驅動程式 (Web Driver) : 

Selenium 需要搭配 Web Driver (驅動程式) 才能用來操控瀏覽器以模仿人類操作瀏覽器之行為,  Web Driver 是針對不同之瀏覽器而開發, 因此須視要使用之瀏覽器分別下載其 Web Driver 後放置在 Python 安裝目錄下才可使用, 

Chrome 的驅動程式 chromedriver 下載網址 : 


Firefox 的驅動程式 geckodriver 下載網址 : 


Edge 的驅動程式下載網址 : 


以 Windows 為例, 將下載之驅動程式壓縮檔解開後得到的 chromedriver.exe 檔複製到 Python 安裝目錄的 Scripts 子目錄下, 其路徑可以開啟命令提示字元視窗用 where 指令查詢, 例如 :

C:\Users\tony1>where python    
C:\Users\tony1\AppData\Local\Programs\Python\Python312\python.exe  
C:\Users\tony1\AppData\Local\Microsoft\WindowsApps\python.exe

以此例來說就是複製倒 Python312\Scripts 下, 然後將 C:\Users\tony1\AppData\Local\Programs\Python\Python312\Scripts 加入系統變數 Path 裡面即可. 

如果是 Linux 要看是哪一種 distribution, 例如 Ubuntu 是 /usr/local/bin. 

注意, chromedriver 的版本必須與電腦上安裝的瀏覽器版本一致, 否則無法使用. 這可以執行 chromedrver.exe --version 指令檢視驅動程式版本, 看看是否與 Chrome 瀏覽器版本相同. 相反地,  Firefox 的 geckodriver 則因向下相容而無版本匹配問題, 


三. WebDriver 物件基本用法 :  

使用 Selenium 操控瀏覽器須先建立 WebDriver 物件, 在 Windows 中, 只要將下載的瀏覽器驅動程式放在 Python 安裝目錄的 Scripts 子目錄下, 並將其路徑加入環境變數 path 中即可呼叫 webdriver 模組的 Chrome(), Firefox(), Edge(), Ie(), 或 Safari() 等建構式來建立 WebDriver 物件.


1. 建立 WebDriver 物件 : 

不同的瀏覽器會提供自家的驅動程式, Selenium 針對各家瀏覽器開發不同的介面來介接這些驅動程式, 具體而言就是 webdriver 模組底下的 Firefox, Chrome, 與 Edge 等類別, 透過呼叫他們的建構式建立 WebDriver 物件後即可操控瀏覽器, 以 Firefox 為例 : 

>>> from selenium import webdriver
>>> driver=webdriver.Firefox()    # 傳回 WebDriver 物件  
>>> type(driver)     
<class 'selenium.webdriver.firefox.webdriver.WebDriver'>      

使用 Chrome 或 Chromium 呼叫 Chrome() 建構式 :

>>> driver=webdriver.Chrome()    # 傳回 WebDriver 物件 

注意, 如果瀏覽器驅動程式沒有放在 Python 安裝目錄的 Scripts 子目錄下, 則在呼叫建構式建立 WebDriver 物件時要傳入 executable_path 參數指定其位置, 例如 :

>>> path='D:\webdriver\chromedriver.exe'   
>>> driver=webdriver.Chrome(executable_path=path)    # 指定驅動程式位置


2. WebDriver 物件的屬性與方法 : 

接下來用 dir() 檢視 WebDriver 物件的成員 : 

>>> dir(browser)   
['CONTEXT_CHROME', 'CONTEXT_CONTENT', '__abstractmethods__', '__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__', '_abc_impl', '_authenticator_id', '_check_if_window_handle_is_current', '_file_detector', '_get_cdp_details', '_is_remote', '_mobile', '_shadowroot_cls', '_switch_to', '_unwrap_value', '_web_element_cls', '_wrap_value', 'add_cookie', 'add_credential', 'add_virtual_authenticator', 'application_cache', 'back', 'bidi_connection', 'capabilities', 'caps', 'close', 'command_executor', 'context', 'create_web_element', 'current_url', 'current_window_handle', 'delete_all_cookies', 'delete_cookie', 'desired_capabilities', 'error_handler', 'execute', 'execute_async_script', 'execute_script', 'file_detector', 'file_detector_context', 'find_element', 'find_elements', 'forward', 'fullscreen_window', 'get', 'get_cookie', 'get_cookies', 'get_credentials', 'get_full_page_screenshot_as_base64', 'get_full_page_screenshot_as_file', 'get_full_page_screenshot_as_png', 'get_log', 'get_pinned_scripts', 'get_screenshot_as_base64', 'get_screenshot_as_file', 'get_screenshot_as_png', 'get_window_position', 'get_window_rect', 'get_window_size', 'implicitly_wait', 'install_addon', 'log_types', 'maximize_window', 'minimize_window', 'mobile', 'name', 'orientation', 'page_source', 'pin_script', 'pinned_scripts', 'print_page', 'quit', 'refresh', 'remove_all_credentials', 'remove_credential', 'remove_virtual_authenticator', 'save_full_page_screenshot', 'save_screenshot', 'service', 'session_id', 'set_context', 'set_page_load_timeout', 'set_script_timeout', 'set_user_verified', 'set_window_position', 'set_window_rect', 'set_window_size', 'start_client', 'start_session', 'stop_client', 'switch_to', 'timeouts', 'title', 'uninstall_addon', 'unpin', 'virtual_authenticator_id', 'window_handles']

WebDriver 物件的常用屬性如下 :


 WebDriver 物件常用屬性 說明
 page_source 所開啟網頁之 HTML 原始碼 (字串)
 title 所開啟網頁之標題
 current_url 所開啟網頁之網址
 session_id 所開啟網頁之連線 (Session) ID
 window_handles 傳回驅動程式已開啟之所有瀏覽器 handle 串列
 current_window_handle 目前 active 的瀏覽器 handle
 title 所開啟網頁之標題


這些屬性中最常用的是 page_source, 它可取得網頁的原始碼, 因此可以搭配 BeautifulSoup 進行網頁剖析以取得目標元素之內容 (雖然單靠 Selenium 也可以達成此任務), 特別是使用 Javascript 生成的網頁, 使用 requests 無法取得網頁內容時, 可用 Selenium + BeautifulSoup 來擷取網頁內容. 另外, WebDriver 物件可開啟多個網頁分頁, 每個分頁有一個 handle, 它們都儲存在 window_handles 屬性中. 

WebDriver 物件的常用方法如下 :


 WebDriver 物件常用方法 說明
 get(url) 載入網址 url 之網頁
 find_element(by, target) 依據 by 常數搜尋第一個符合 target 之 DOM 元素
 find_elements(by, target) 依據 by 常數搜尋符合 target 之全部 DOM 元素 (傳回串列)
 close() 按瀏覽器 "關閉" 鈕, 同時關閉驅動程式
 implicitly_wait(second) 隱式等候 second 秒 (預設 0), 若操作完成則結束等候
 quit() 按瀏覽器 "關閉" 鈕
 back() 按瀏覽器 "上一頁" 鈕
 forward() 按瀏覽器 "下一頁" 鈕
 refresh() 按瀏覽器 "更新" 鈕
 save_screenshot(filename) 將目前網頁截圖儲存為 png 檔案下載
 quit() 按瀏覽器 "關閉" 鈕
 get_cookies() 傳回 cookies 字典串列
 switch_to.window(handle) 切換到瀏覽器視窗 handle 
 get_cookies() 傳回 cookies 字典串列
 get_cookie(name) 傳回名稱為 name 的 cookie 值
 switch_to.frame(index/id/name) 切換至內嵌視窗 (iframe 元素), 可傳入其索引, id 或 name
 execute_script(js) 執行 Javascript 字串 js
 execute_async_script(js) 執行非同步 Javascript 字串 js


注意, 由於 Selenium 程式碼執行速度快, 但載入網頁或操作頁面都須要等待瀏覽器回應才能執行下一步指令, 雖然可用 time.sleep() 來設定等待秒數, 但此方式的等待時間是固定的, 即使瀏覽器操作已完成, 但仍須等待 time.sleep() 所設定之秒數 timeout 後才會執行下一個指令, 爬蟲程式的執行效率就降低了; 若秒數太短又會導致擷取不到目標資料, 使爬蟲的穩健性降低. 

WebDriver 物件的 implicitly_wait() 提供了一個智慧式的等待功能, 隱式等待會在設定的秒數內持續檢測網頁是否已載入或頁面操作是否完成, 如果逾時尚未完成就會拋出一個例外 (TimeoutException 或 NoSuchElementException). 因此為了爬蟲程式的穩健性, 應該把對網頁的操作 (載入) 都放在 try except 區塊中, 例如 : 

driver=webdriver.FireFox()
driver.explicitly_wait(20)
try:
    driver.get('https://text.com/target.htm')
    driver.find_element(By.ID, 'name').send_keys('OK')
except Exception as e:
    print(e)
finally:
    driver.close()

注意, implicitly_wait() 一驚呼叫後在整個爬蟲程式都有效, 因此只要在 WebDriver 物件建立後設定一次即可. 


3. 建立 Selenium 爬蟲程式的程序 : 

使用 WebDriver 物件建立 Selenium 爬蟲程式的基本程序如下 :
  1. 匯入 webdriver 模組與 By 類別
  2. 呼叫 Chrome()/Firefox()/Edge() 建立 WebDriver 物件
  3. 呼叫 WebDriver 物件的 implicitly_wait() 方法設定隱式等候秒數
  4. 呼叫 WebDriver 物件的 get(url) 方法載入目標網頁
  5. 呼叫 WebDriver 物件的 find_element() 或 find_elements() 搜尋目標元素
  6. 呼叫 quit() 或 close() 關閉所開啟之瀏覽器釋放記憶體 
從上面程序可知, get(), implicitly_wait(), find_element(), find_elements(), 與 close() 是一個 Selenium 爬蟲程式必用的方法. 其它方法例如 back() 雖然較少用, 但在某些爬蟲任務中需要在擷取目前網頁後點超連結到下一頁取得資料後再回上一頁, 這時就會用到 back() 方法來返回上一頁; 開啟多分頁時會用到 switch_to_window() 來切換分頁; 目標網頁中有內嵌視窗 iframe 時則會用到 switch_to.frame() 方法. 


4. 搜尋目標元素 : 

在 Selenium 爬蟲應用中主要是用 find_element() 與 find_elements() 這兩個方法來定位網頁元素, 前者會傳回第一個搜尋到的元素之 WebElement 物件; 後者則傳回全部符合之 WebElement 物件串列. Selenium 可以使用下列 8 種方式來定位元素 : 
  • tag_name : 元素之標籤名稱, 例如 p, div, span, a ... 等.
  • name : 元素之 name 屬性值.
  • id : 元素之 name 屬性值.
  • class_name : 元素之 class 屬性值.
  • css_selector : 樣式選擇器
  • link_text : 超連結 a 元素之文字內容
  • partial_link_text : 超連結 a 元素之部分文字內容
  • xpath : 元素之 xpath
因此在舊版中定義了 find_element(s)_by_xxx() 的方法來搜尋元素, 方法名稱很長, 例如 find_element_by_tag_name('div') 等, 但在 v4 新版中則將方名稱簡化為 find_element() 與 find_elements() 兩個方法而已, 將搜尋方式定義成常數, 收納在 common 模組的子模組 by 裡面的 By 類別 :

>>> type(selenium.webdriver.common)  
<class 'module'>
>>> type(selenium.webdriver.common.by)   
<class 'module'>  
>>> type(selenium.webdriver.common.by.By)   
<class 'type'>

用 dir() 檢視 By 類別內容 : 

>>> dir(selenium.webdriver.common.by.By)   
['CLASS_NAME', 'CSS_SELECTOR', 'ID', 'LINK_TEXT', 'NAME', 'PARTIAL_LINK_TEXT', 'TAG_NAME', 'XPATH', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

其中定義了 8 個字串常數來代表 8 種定位元素的方式 : 


 By 類別的字串常數 值
 TAG_NAME 'tag name'
 NAME 'name'
 ID 'id'
 CLASS_NAME 'class name'
 CSS_SELECTOR 'css selector'
 LINK_TEXT 'link text'
 PARTIAL_LINK_TEXT 'partial link text'
 XPATH 'xpath'


注意, 若元素的樣式類別 class 屬性值有多個, 例如 <p class="class1 class2">test</p>, 使用 By.CLASS_NAME 或 "class name" 搜尋時, find_element() 或 find_elements() 的第二參數要傳入以逗號隔開的樣式類別字串, 例如 find_element("class name", "class1, class2"). 


>>> print(selenium.webdriver.common.by.By.NAME)   
name
>>> print(selenium.webdriver.common.by.By.TAG_NAME)   
tag name
>>> print(selenium.webdriver.common.by.By.ID)   
id
>>> print(selenium.webdriver.common.by.By.CLASS_NAME)    
class name
>>> print(selenium.webdriver.common.by.By.CSS_SELECTOR)  
css selector
>>> print(selenium.webdriver.common.by.By.LINK_TEXT)  
link text
>>> print(selenium.webdriver.common.by.By.PARTIAL_LINK_TEXT)  
partial link text

故使用新版 Selenium 寫爬蟲程式須匯入 By 類別 : 

from selenium.webdriver.common.by import By    

然後在呼叫 find_element() 或 find_elements() 時把 By.xxx 常數傳入當第一參數, 例如下列的清單 :

<p>最受歡迎韓星</p>
<ul>
  <li>李清娥</li>
  <li>金智媛</li>
  <li>朴恩斌</li>
  <li>孫藝珍</li>
  <li>李世榮</li>
  <li>金裕貞</li>
</ul>

取得清單中的選項元素 li 的 WebElement 物件串列 : 

li_tags=driver.find_elements(By.TAG_NAME, 'li')    

也可以直接傳入字串常數的值 (這樣就毋須匯入 By 類別) :

li_tags=driver.find_elements('tag name', 'li')   

如果呼叫 find_element() 方法, 則會傳回第一個 li 元素的 WebElement 物件. 

參考 :


如果用 ID, NAME, TAG_NAME, CSS_SELECTOR 等方式都不容易定位出目標元素, 可用萬靈丹 XPATH 來解決, 由於 XPATH 是元素在 DOM 語法樹中的唯一路徑, 因此可輕易定位出目標元素. XPATH (XML Path Language) 源自 XML 標記語言, 用來定位 XML 語法樹中的元素, 由於 HTML 是 XML 的子集, 因此也可以用在 HTML 的 DOM 語法樹. 

XPATH 語法如下 : 


 XPath 語法 說明
 tag 搜尋標籤名稱為 tag 的子節點
 * 所有子節點
 . 目前節點
 // 全部子孫節點
 .. 父節點 (上一層)
 [@attr] 搜尋全部含有屬性 attr 的節點
 [@attr='value'] 搜尋全部含有屬性 attr, 且值為 value 的節點
 [tag] 搜尋標籤名稱為 tag 的所有子節點
 [tag='text'] 搜尋全部子孫節點中標籤名稱為 tag, 且內容為 text 的所有子節點
 [index] 搜尋位置索引 (1 起始) 為 index 的子節點


其實瀏覽器的開發者工具就可以很容易取得目標元素的 XPATH, 以 Chrome 為例, 按 F12 開啟開發者工具視窗, 點選 'Elements' 頁籤, 按 Ctrl+F 搜尋目標元素, 點選該元素按滑鼠右鍵, 選擇 Copy -> Copy XPath 即可取得該元素之 XPATH, 例如 : 




參考 :



四. WebElement 物件基本用法 :  

呼叫 WebDriver 物件的 find_element() 方法會傳回第一個符合條件元素的 WebElement 物件; 而呼叫 find_elements() 則會傳回所有符合條件元素之 WebElement 物件串列. 


1. 擷取網頁內容 : 

以下面測試網頁為例 :


  <p name="popular_stars" id="stars">最受歡迎韓星</p>
  <ul>
    <li>李清娥</li>
    <li>金智媛</li>
    <li>朴恩斌</li>
    <li>孫藝珍</li>
    <li>李世榮</li>
    <li>金裕貞</li>
  </ul>

先匯入模組與類別, 建立 WebDriver 物件, 呼叫 get(url) 方法載入目標網頁, 然後呼叫 get_element() 或 get_elements() 方法建立 WebElement 物件或其串列 : 

>>> from selenium import webdriver
>>> from selenium.webdriver.common.by import By    
>>> driver=webdriver.Firefox()     # 建立 WebDriver 物件
>>> driver.implicitly_wait(20)       # 隱式等候最長 20 秒
>>> url='https://tony1966.github.io/test/python/web_crawler/crawler_test_1.htm'  
>>> driver.get(url)    

首先用 page_source 取得網頁原始碼 : 

>>> print(driver.page_source)    
<html><head>
  <meta charset="UTF-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title></title>
 </head>
 <body>
  <p>最受歡迎韓星</p>
  <ul>
    <li>李清娥</li>
    <li>金智媛</li>
    <li>朴恩斌</li>
    <li>孫藝珍</li>
    <li>李世榮</li>
    <li>金裕貞</li>
  </ul>
</body></html>

透過 page_source 取得之 HTML 原始碼可以傳給 BeautifulSoup() 進行剖析以便取得目標資料, 但單用 Selenium 也可以完成整個爬蟲任務, 這主要是透過 WebElement 物件所提供的方法. 

例如此例可呼叫 find_element() 取得 p 元素之 WebElement 物件 : 

>>> p_tag=driver.find_element(By.TAG_NAME, 'P')   
>>> type(p_tag)    
<class 'selenium.webdriver.remote.webelement.WebElement'>  


2. WebElement 物件的屬性與方法 : 

用 dir() 檢視上面取得之 p 元素 WebElement 物件內容 :

>>> dir(p_tag)   
['__abstractmethods__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_abc_impl', '_execute', '_id', '_parent', '_upload', 'accessible_name', 'aria_role', 'clear', 'click', 'find_element', 'find_elements', 'get_attribute', 'get_dom_attribute', 'get_property', 'id', 'is_displayed', 'is_enabled', 'is_selected', 'location', 'location_once_scrolled_into_view', 'parent', 'rect', 'screenshot', 'screenshot_as_base64', 'screenshot_as_png', 'send_keys', 'shadow_root', 'size', 'submit', 'tag_name', 'text', 'value_of_css_property'] 

其中常用屬性如下表 (text 屬性最常用) :


 WebElement 物件常用屬性 說明
 text 元素的純文字內容 (innerText)
 tag_name 元素之標籤名稱
 location 元素在網頁中的對應視窗左上角之座標 (x, y 字典, 單位 px)
 size 元素的尺寸 (px)


例如上面網頁的 p_tag 物件 :

>>> p_tag.text    
'最受歡迎韓星'   
>>> p_tag.tag_name   
'p'   
>>> p_tag.location    
{'x': 8, 'y': 16}
>>> p_tag.size    
{'height': 18.899993896484375, 'width': 1136.0} 

WebElement 物件常用方法如下表 : 


 WebElement 物件常用方法 說明
 find_element(by, target) 依據 by 常數搜尋此物件內第一個符合 target 之 DOM 元素
 find_elements(by, target) 依據 by 常數搜尋此物件內符合 target 之全部 DOM 元素 (傳回串列)
 get_attribute(attr) 傳回屬性名稱 attr 之值
 send_keys(str) 對元素傳送文字 str (文字欄位 input-text 或文字區域 textarea 元素)
 click() 觸發元素之 click 事件 (按鈕, 超連結, 下拉式選單, 選項等)
 submit()  提交表單 (form 元素)
 clear() 清除文字欄位或文字區域之內容
 is_displayed() 傳回 True (元素可見) 或 False (元素不可見)
 is_enabled() 傳回 True (元素可用) 或 False (元素不可用)
 is_selected() 傳回 True (元素有被勾選) 或 False (元素沒被勾選) : 選項


可見 WebElement 物件與 WebDriver 一樣都有 find_element() 與 find_elements() 方法, 但差別是搜尋範圍不同, WebElement 的 find_element() 與 find_elements() 是搜尋目前元素內部的目標; 而 WebDriver 的則是搜尋整棵語法樹. 

以上面的範例網頁為例 :

>>> ul_tag=driver.find_element(By.TAG_NAME, 'ul')   # 取得 ul 元素之物件  
>>> li_tags=ul_tag.find_elements(By.TAG_NAME, 'li')    # 在 ul 內搜尋 li 物件
>>> type(li_tags)     # 傳回串列
<class 'list'>
>>> len(li_tags)        # WebElement 物件個數
6
>>> for li_tag in li_tags:   
    print(li_tag.text)            # 輸出 li 物件的內容
    
李清娥
金智媛
朴恩斌
孫藝珍
李世榮
金裕貞

此處我們先用 WebDriver 物件的 find_element() 找到第一個 ul 元素之 WebElement 物件, 然後用此 WebElement 物件之 find_elements() 方法搜尋其內部的全部 li 元素, 得到 6 個 WebElement 物件之串列, 最後走訪此串列印出每個 li 元素的內容. 

當然也可以直接用 WebDriver 物件去搜尋 li 元素, 例如 :

>>> li_tags=driver.find_elements(By.TAG_NAME, 'li')   
>>> len(li_tags)    
6
>>> li_tags[0].text     
'李清娥'

可見結果是一樣的. 最後呼叫 close() 方法關閉瀏覽器 :

>>> driver.close()   


3. 用 WebElement 物件操作表單 : 

接下來要測試如何用 Selenium 操作表單提交, 目標網頁使用 "網頁擷取-使用 Python" 這本書所提供的範例 : 





檢視此表單網頁原始碼如下 : 

<h2>Tell me your name!</h2>
<form method="post" action="processing.php">
First name: <input type="text" name="firstname"><br>
Last name: <input type="text" name="lastname"><br>
<input type="submit" value="Submit" id="submit">
</form>

可見表單中有名為 firstname 與 lastname 的兩個文字欄位輸入框, 與一個有 id 屬性為 submit 的提交按鈕. 在兩個文字欄位輸入姓名後按 "Submit" 鈕, 會將表單內容以 POST 方法提交給後端 PHP 程式 processing.php, 該程式會回傳提交的姓名 : 



匯入模組類別後載入目標網頁 :

>>> from selenium import webdriver
>>> from selenium.webdriver.common.by import By    
>>> driver=webdriver.Firefox()     # 建立 WebDriver 物件
>>> driver.implicitly_wait(20)       # 隱式等候最長 20 秒
>>> url='https://pythonscraping.com/pages/files/form.html'  
>>> driver.get(url)  

然後呼叫 find_element() 取得 firstname 與 lastname 兩個輸入框的 WebElement 物件 :

>>> firstname=driver.find_element(By.NAME, 'firstname')   
>>> firstname.get_attribute('type')   
'text'
>>> lastname=driver.find_element(By.NAME, 'lastname')   
>>> lastname.get_attribute('type')   
'text'

接下來呼叫這兩個物件的 send_keys() 方法填入姓名 :
 
>>> firstname.send_keys('Tony')   
>>> lastname.send_keys('Huang')   

這時檢視 WebDriver 開啟的瀏覽器已填入姓名 :



最後只要取得 Submit 按鈕之物件, 呼叫其 click() 方法提交表單即可 :

>>> btn_tag=driver.find_element(By.ID, 'submit')   
>>> btn_tag.click()   

因為 Submit 按鈕有 id 屬性, 故此處用 By.ID 搜尋元素, 結果如下 :



完成後關閉瀏覽器 :

>>> driver.close()

下面範例測試 clear() 方法, 將 https://pythonscraping.com/pages/files/form.html 的網頁原始碼下載修改為如下的 crawler_test_2.htm 後上傳到 GitHub :


<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title></title>
  </head>
  <body>
    <h2>Tell me your name!</h2>
    <form method="post" action="https://pythonscraping.com/pages/files/processing.php">
    First name: <input type="text" name="firstname" value="Donald"><br>
    Last name: <input type="text" name="lastname" value="Trump"><br>
    <input type="submit" value="Submit" id="submit">
    </form>
  </body>
</html>

注意此網頁的兩個文字欄位輸入框已經添加 value 屬性填入預設值 Donald 與 Trump, 下面的程式碼使用 clear() 方法先清除輸入框的預設值, 然後再呼叫 send_keys() 填入新值, 如果不先清除就呼叫 send_keys() 的話, 新值會串在預設值後面. 其次, 因為此網頁與後端處理程式不在同一網域, 所以 action 屬性必須給予完整的域名路徑 :

>>> from selenium import webdriver
>>> from selenium.webdriver.common.by import By    
>>> driver=webdriver.Firefox()     # 建立 WebDriver 物件
>>> driver.implicitly_wait(20)       # 隱式等候最長 20 秒
>>> url='https://tony1966.github.io/test/python/web_crawler/crawler_test_2.htm'  
>>> driver.get(url)  

到這裡時瀏覽器載入之網頁中, 兩個輸入框含有預設值, 接著執行下令程式碼清除預設值 :

>>> firstname=driver.find_element(By.NAME, 'firstname')   
>>> firstname.clear() 
>>> lastname=driver.find_element(By.NAME, 'lastname') 
>>> lastname.clear()

這樣就清除預設值了, 然後填入新的姓名後按 Submit 鈕 :

>>> firstname.send_keys('Tony')   
>>> lastname.send_keys('Huang')   
>>> btn_tag=driver.find_element(By.ID, 'submit')   
>>> btn_tag.click() 

這樣會得到與上面同樣的結果. 關閉瀏覽器結束爬蟲 : 

>>> driver.close()

沒有留言 :