1. 選擇元素的基本方法
對於百度搜索頁面,如果我們想自動化輸入愛編程的小灰灰,怎么做呢?
這就是在網頁中,操控界面元素。
web界面自動化,要操控元素,首先需要選擇界面元素 ,或者說定位界面元素
就是先告訴瀏覽器,你要操作哪個界面元素, 讓它找到你要操作的界面元素。
我們必須要讓瀏覽器先找到元素,然后才能操作元素。
1.1 查看元素的方法
對應web自動化來說,就是要告訴瀏覽器,你要操作的界面元素是什么。
那么,怎么告訴瀏覽器呢?
方法就是:告訴瀏覽器,你要操作的這個web元素的特征。
就是告訴瀏覽器,這個元素它有什么與眾不同的地方,可以讓瀏覽器一下子找到它。
元素的特征怎么查看?
可以使用瀏覽器的開發者工具欄幫我們查看、選擇 web 元素。
請大家用chrome瀏覽器訪問百度,按F12后,點擊下圖箭頭處的Elements標簽,即可查看頁面對應的HTML 元素
然后,再點擊最左邊的圖標,如下所示
之后,鼠標在界面上點擊哪個元素,就可以查看該元素對應的html標簽了。
也可以直接在你想要查看的位置,右鍵選擇檢查,就可以直接定位到元素了。
1.2 根據元素的id屬性選擇元素
我們在input中,可以看到有一個屬性叫id。
我們可以把id想象成元素的編號,是用來在html中標記該元素的。根據規范,如果元素有id屬性,這個id必須是當前html中唯一的。
所以如果元素有id,根據id選擇元素是最簡單高效的方式。
這里,百度搜索框元素的id值為kw
下面的代碼,可以自動化在瀏覽器中訪問百度,並且在輸入框中搜索愛編程的小灰灰。
大家可以運行一下看看。
from selenium import webdriver # 創建 WebDriver 對象,指明使用chrome瀏覽器驅動 wd = webdriver.Chrome(r'E:\webdrivers\chromedriver.exe') # 調用WebDriver 對象的get方法 可以讓瀏覽器打開指定網址 wd.get('https://www.baidu.com') # 根據id選擇元素,返回的就是該元素對應的WebElement對象 element = wd.find_element_by_id('kw') # 通過該 WebElement對象,就可以對頁面元素進行操作了 # 比如輸入字符串到 這個 輸入框里 element.send_keys('愛編程的小灰灰\n')
其中
wd = webdriver.Chrome(r'E:\webdrivers\chromedriver.exe')
前面講過,driver賦值的是WebDriver類型的對象,我們可以通過這個對象來操控瀏覽器,比如打開網址、選擇界面元素等。
下面的代碼
wd.find_element_by_id('kw')
使用了 WebDriver對象的find_element_by_id方法。
這行代碼運行時,就會發起一個請求,通過瀏覽器驅動轉發給瀏覽器,告訴它,需要選擇一個id為kw的元素。
瀏覽器找到id為kw的元素后,將結果通過瀏覽器驅動返回給自動化程序,所以find_element_by_id方法會返回一個WebElement 類型的對象。
這個WebElement對象可以看成是對應頁面元素的遙控器。
我們通過這個WebElement對象,就可以操控對應的界面元素。
比如 :
調用這個對象的send_keys方法就可以在對應的元素中輸入字符串,
調用這個對象的click方法就可以點擊該元素。
1.3 根據class屬性、tag名選擇元素
1.3.1 根據class屬性選擇元素
web自動化的難點和重點之一,就是如何選擇我們想要操作的web頁面元素。
除了根據元素的id,我們還可以根據元素的class屬性選擇元素。
就像一個學生張三可以定義類型為中國人或者學生一樣,中國人和學生都是張三的類型。
元素也有類型, class 屬性就用來標志着元素類型。
html代碼:
<body> <div class="raise"><span>喜羊羊</span></div> <div class="raise"><span>美羊羊</span></div> <div class="raise"><span>暖羊羊</span></div> <div class="wolf"><span>灰太狼</span></div> <div class="wolf"><span>紅太狼</span></div> <div class="wolf"><span>小灰灰</span></div> </body>
所有的羊元素都有個class屬性值為raise。
所有的狼元素都有個class屬性值為wolf。
如果我們要選擇所有的狼,就可以使用方法find_elements_by_class_name。
注意element后面多了個s。
wd.find_elements_by_class_name('wolf')
注意:
find_elements_by_class_name方法返回的是找到的符合條件的所有元素 (這里有3個元素), 放在一個列表中返回。
而如果我們使用find_element_by_class_name (注意少了一個s) 方法,就只會返回第一個元素。
大家可以運行如下代碼看看。(注意html文件的路徑)
from selenium import webdriver # 創建WebDriver實例對象,指明使用chrome瀏覽器驅動 wd = webdriver.Chrome(r'E:\webdrivers\chromedriver.exe') # WebDriver 實例對象的get方法 可以讓瀏覽器打開指定網址 wd.get('http://127.0.0.1:8020/day01/index.html') # 根據 class name 選擇元素,返回的是一個列表 # 里面都是class屬性值為wolf的元素對應的WebElement對象 elements = wd.find_elements_by_class_name('wolf') # 取出列表中的每個WebElement對象,打印出其text屬性的值 # text屬性就是該WebElement對象對應的元素在網頁中的文本內容 for element in elements: print(element.text)
首先,大家要注意:通過WebElement對象的text屬性可以獲取該元素在網頁中的文本內容。
所以下面的代碼,可以打印出element對應網頁元素的文本 。
print(element.text)
如果我們把
elements = wd.find_elements_by_class_name('wolf')
去掉一個s,改為
element = wd.find_element_by_class_name('wolf') print(element.text)
那么返回的就是第一個class屬性為wolf的元素,也就是這個元素。
<div class="wolf"><span>灰太狼</span></div>
就像一個學生張三可以定義有多個類型: 中國人和學生,中國人和學生都是張三的類型。
元素也可以有類型,多個class類型的值之間用空格隔開,比如
<span class="chinese student">張三</span>
注意,這里span元素有兩個class屬性,分別是chinese和student,而不是一個名為chinese student的屬性。
我們要用代碼選擇這個元素,可以指定任意一個class屬性值,都可以選擇到這個元素,如下
element = wd.find_elements_by_class_name('chinese')
或者
element = wd.find_elements_by_class_name('student')
而不能這樣寫
element = wd.find_elements_by_class_name('chinese student')
1.3.2 根據tag名選擇元素
和class方法差不多的,我們可以通過方法find_elements_by_tag_name,選擇所有的tag名為div的元素,如下所示
from selenium import webdriver wd = webdriver.Chrome(r'E:\webdrivers\chromedriver.exe') wd.get('http://127.0.0.1:8020/day01/index.html') # 根據 tag name 選擇元素,返回的是 一個列表 # 里面 都是 tag 名為 div 的元素對應的 WebElement對象 elements = wd.find_elements_by_tag_name('div') # 取出列表中的每個 WebElement對象,打印出其text屬性的值 # text屬性就是該 WebElement對象對應的元素在網頁中的文本內容 for element in elements: print(element.text)
1.3.3 find_element和find_elements的區別
使用find_elements選擇的是符合條件的所有元素,如果沒有符合條件的元素,返回空列表。
使用find_element選擇的是符合條件的第一個 元素, 如果沒有符合條件的元素, 拋出 NoSuchElementException異常。
1.4 通過WebElement對象選擇元素
不僅WebDriver對象有選擇元素的方法,WebElement對象也有選擇元素的方法。
WebElement對象也可以調用find_elements_by_xxx,find_element_by_xxx之類的方法。
WebDriver對象選擇元素的范圍是整個web頁面,而WebElement對象選擇元素的范圍是該元素的內部。
html代碼:
<div id='container'> <span>內層11</span> <span>內層12</span> <span>內層13</span> </div> <div> <span>內層21</span> <span>內層22</span> <span>內層23</span> </div>
from selenium import webdriver wd = webdriver.Chrome(r'E:\webdrivers\chromedriver.exe') wd.get('http://127.0.0.1:8020/day01/index.html') element = wd.find_element_by_id('container') # 限制 選擇元素的范圍是 id 為 container 元素的內部。 spans = element.find_elements_by_tag_name('span') for span in spans: print(span.text)
輸出結果就只有
1.5 等待界面元素出現
在我們進行網頁操作的時候,有的元素內容不是可以立即出現的,可能會等待一段時間。
比如百度搜索一個詞語,我們點擊搜索后,瀏覽器需要把這個搜索請求發送給百度服務器, 百度服務器進行處理后,把搜索結果返回給我們。
所以,從點擊搜索到得到結果,需要一定的時間,只是通常百度服務器的處理比較快,我們感覺好像是立即出現了搜索結果。
百度搜索的每個結果對應的界面元素,其ID分別是數字 1, 2 ,3, 4...
如下:
那么我們可以試試用如下代碼,來將第一個搜索結果里面的文本內容打印出來。
from selenium import webdriver wd = webdriver.Chrome(r'E:\webdrivers\chromedriver.exe') wd.get('https://www.baidu.com') element = wd.find_element_by_id('kw') element.send_keys('愛編程的小灰灰\n') # id 為 1 的元素 就是第一個搜索結果 element = wd.find_element_by_id('1') # 打印出 第一個搜索結果的文本字符串 print (element.text)
如果大家去運行一下,就會發現有如下異常拋出
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"id","selector":"1"}
NoSuchElementException的意思就是在當前的網頁上找不到該元素,找不到id為1的元素。
為什么呢?
因為我們的代碼執行的速度比百度服務器響應的速度快。
百度還沒有來得及返回搜索結果,我們就執行了如下代碼
element = wd.find_element_by_id('1')
在那短暫的瞬間,網頁上是沒有用id為1的元素的(因為還沒有搜索結果呢)。自然就會報告錯誤id為1的元素不存在了。
那么怎么解決這個問題呢?
相信很多人都能想到,就是在點擊搜索后,用sleep來等待幾秒鍾,等百度服務器返回結果后,再去選擇id為1的元素,就像下面這樣
from selenium import webdriver wd = webdriver.Chrome(r'E:\webdrivers\chromedriver.exe') wd.get('https://www.baidu.com') element = wd.find_element_by_id('kw') element.send_keys('愛編程的小灰灰\n') # 等待 2 秒 from time import sleep sleep(2) # 2 秒 過后,再去搜索 element = wd.find_element_by_id('1') # 打印出 第一個搜索結果的文本字符串 print (element.text)
大家可以運行一下,基本是可以的,不會再報錯了。
但是,這樣的方法有一個很大的問題,就是設置等待多長的時間合適呢?
這次百度網站反應可能比較快,我們等了一秒鍾就可以了。
但是誰知道下次他的反應是不是還這么快呢?百度也曾經出現過服務器癱瘓的事情。
可能有的人說,我干脆sleep比較長的時間,等待 20 秒,總歸可以了吧?
這樣也有很大問題,假如一個自動化程序里面需要10次等待,就要花費 200秒。而可能大部分時間,服務器反映都是很快的,根本不需要等20秒,這樣就造成了大量的時間浪費了。
Selenium提供了一個更合理的解決方案,是這樣的:
當發現元素沒有找到的時候,並不立即返回找不到元素的錯誤。
而是周期性(每隔半秒鍾)重新尋找該元素,直到該元素找到,或者超出指定最大等待時長,這時才拋出異常(如果是find_elements之類的方法,則是返回空列表)。
Selenium的Webdriver對象有個方法叫implicitly_wait
該方法接受一個參數,用來指定最大等待時長。
如果我們加入如下代碼
wd.implicitly_wait(10)
那么后續所有的find_element或者find_elements之類的方法調用都會采用上面的策略:
如果找不到元素,每隔半秒鍾再去界面上查看一次,直到找到該元素,或者過了10秒最大時長。
這樣,我們的百度搜索的例子的最終代碼如下
from selenium import webdriver wd = webdriver.Chrome() # 設置最大等待時長為 10秒 wd.implicitly_wait(10) wd.get('https://www.baidu.com') element = wd.find_element_by_id('kw') element.send_keys('愛編程的小灰灰\n') element = wd.find_element_by_id('1') print (element.text)
大家再運行一下,可以發現不會有錯誤了。