3.5.0 等待元素
我們大家有沒有這種經(jīng)歷,點擊了一個鏈接或者按鈕。Web界面會經(jīng)過或長或短的一段時間,才顯示出一個新的界面。我們做 web自動化的時候,腳本的執(zhí)行是非常快的,我們腳本里面的代碼,點擊一個鏈接后,立即想去新的界面里面獲取某個元素,往往會出現(xiàn)該元素沒有找到的錯誤,因為瀏覽器的界面還沒有更新,讓這個元素顯示出來。下面我們以以前講的一個小案例來講解這個問題。
#從selenium里面導入webdriver
from selenium import webdriver
#指定chrom的驅(qū)動
#執(zhí)行到這里的時候Selenium會到指定的路徑將chrome driver程序運行起來
driver = webdriver.Chrome('E:\ChromDriver\chromedriver.exe')
#driver = webdriver.Firefox()#這里是火狐的瀏覽器運行方法
#get 方法 打開指定網(wǎng)址
driver.get('http://www.baidu.com')
#選擇網(wǎng)頁元素
element_keyword = driver.find_element_by_id('kw')
#輸入字符
element_keyword.send_keys('宋曲')
#找到搜索按鈕
element_search_button = driver.find_element_by_id('su')
import time
#注意這里必須要等待時間,因為代碼運行過快,代碼運行完的時候頁面還沒加載出來就會找不到元素
time.sleep(2)
ret = driver.find_element_by_id('1')
print(ret.text)
if ret.text.startswith('宋曲'):#是不是已宋曲開頭
print('測試通過')
else:
print('不通過')
#最后,driver.quit()讓瀏覽器和驅(qū)動進程一起退出,不然桌面會有好多窗口
driver.quit()
我們輸入宋曲之后不是要檢查搜索結(jié)果嗎,我們要檢查id為1的元素,如果沒有import time.sleep()的時候,大家會發(fā)現(xiàn)id為1的元素找不到,大家知道原因嗎?因為我們輸入完“宋曲”點擊“百度一下”時候,網(wǎng)頁的內(nèi)容不是一下出來的,他要去百度服務(wù)器獲取內(nèi)容,等百度的服務(wù)器返回給我們的瀏覽器,我們才能呈現(xiàn)出來,中間是有一定時間差的,瀏覽器與服務(wù)器之間的交互沒有我們代碼運行的快,假如沒有sleep我們點擊click,python解釋器立刻執(zhí)行下一段代碼,下一段代碼立刻去尋找id為1的元素了,由于我們的程序和瀏覽器之間是在本地,他們之間的網(wǎng)絡(luò)是很快的,肯定要比百度服務(wù)器速度要快,所以我們的程序請求很快就發(fā)給瀏覽器了,瀏覽器到頁面去查,我們剛點擊“百度一下”的時候,我們的瀏覽器還沒來及把結(jié)果返回回來,所以他查不到就報錯了,我們這里sleep兩秒鐘就解決了這個問題。
大家在加這個2秒鐘的時候時候有沒有疑問,這里為什么要寫2秒,寫2秒好不好?會不會不夠用?那我寫3秒鐘夠不夠呢,4秒或者5秒會不會更好些?這里就存在一個問題,我到底寫多少合適,寫短了心里很虛,寫長呢又不好比如好20秒,但是20秒大家想想這個地方不管有沒有刷出來,他都強行的等待20秒,這只是一個動作假如再點擊另外一個按鈕,又去服務(wù)器上獲取信息了還需要再等待20秒嗎??這樣的話你一個自動化腳本執(zhí)行下來,比如有七八次操作,就要2分鐘才能執(zhí)行一個自動化腳本,是不是太浪費時間,可能百分之九十九的時間不會花那么多時間,百度請求數(shù)據(jù)幾乎1秒都用不到,但是寫短了又怕確實有些操作需要1.5秒,這個問題我們該怎么解決呢?Selenium 早就想到了這個問題,已經(jīng)貼心的為我們提供了兩種方法。如下:
Selenium的解決方案:
選擇一個元素的時候代碼的設(shè)定一個最大的等待時長,周期性(每隔半秒鐘)重新尋找該元素,直到該元素找到(返回)或者超出指定最大等待時長(返回空列表或者拋出異常)。
隱式等待
全局的設(shè)定后面所有的 選擇元素的代碼都不需要單獨的指定周期性等待了driver.implicitly_wait(10) 這里面參數(shù)10的意思是,一但調(diào)用隱式等待方法之后,他就會設(shè)置一個最大的等待時間10秒,執(zhí)行了driver.implicitly_wait(10) 這段代碼之后,后面任何find開頭方法選擇元素所有的方法,根據(jù)后面的條件去找元素by_id或by_name或by_class或by_tag,如果找不到他不會立刻返回異常,而是每隔半秒鐘再去查找一次,如果還沒有他再去半秒鐘查詢一次,這個時候find_element_by_xxx這個函數(shù)一值是睡眠狀態(tài)停在這里不動了,他不會去返回一直等到你找到這個元素,比如這個元素過6秒鐘才呈現(xiàn)出來,這個調(diào)用等了6秒鐘才返回,這是底層實現(xiàn)的,如果超出最大等待時長10秒鐘,element就會拋出異常elements就會返回空列表
以后大家 一創(chuàng)建webdriver對象,就條件反射一樣,加上implicitly_wait(),就像下面這樣:
driver = webdriver.Chrome(r"d:\tools\webdrivers\chromedriver.exe")
driver.implicitly_wait(10)
顯示等待
Selenium里面還有一種稱之為顯式等待的, 可以為一個操作專門指定等待時間,顯示等待的方法運用比較少,顯示等待當前這次尋找有效:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
ele = WebDriverWait(driver, 60).until(EC.presence_of_element_located((By.ID,'username')))
有一個用隱式等待 和它等價的方法,個人覺得更加簡單一些,假如我有一個特殊的操作需要等待1分鐘,我可以不向示例代碼那樣寫,前面先用臨時的隱式等待改為60秒,等操作結(jié)束之后再改為10秒。
driver.implicitly_wait(60)
try:
ele = driver.find_element_by_id('1')
print(ele.text)
if ele.text.startswith('宋曲'):
print('pass')
else:
print('fail')
except:
print('exception happened')
finally:
driver.implicitly_wait(10)
3.5.1 在 frame、iframe 里面操作
現(xiàn)在的網(wǎng)頁,有很多里面會包含frame或者iframe,要對里面的元素進行操作,就必須要使用Selenium WebDriver的switch_to方法。一個例子獲取網(wǎng)易云音樂排行榜信息。
大家打開這個url:https://music.163.com/#/discover/toplist?id=60198
打開之后發(fā)現(xiàn)有個Billboard排行榜,假如有個需求要把排行榜里面的歌找出來我們要怎么做呢,按照我們老的套路找到這些歌所在的區(qū)域有什么特性,我們可以我們之前介紹的兩種方法進行查看。

我們發(fā)現(xiàn)這是第一首歌,我們往上找因為我們要找整個歌曲的列表我們不是只找一首歌。

會發(fā)現(xiàn)所有的td都是屬于tr的,整個tr就是整個第一行代表第一首歌,我們可以看下后面的tr就是第二行第三行。

那我們就可以知道整個tbody就是整個歌如果不放心的話可以把歌拉倒最下面,然后點擊tbody

就是頁面上顯示的所有的歌,tbody就是table里面的表thead就是表頭,接下我們把表里面的內(nèi)容進行處理,接下來我們怎么來找表里面的元素呢?我們可以看表上面,tbody沒有id,通長我們就往上找就是找他的上層元素,上層的table沒有id上面的div有id。

我們看到table是有class其實我們也可以用calss,但是class有可能跟其他元素相同,通常id是唯一的如果有id就用id,我們把光標放在有id的div元素哪里發(fā)現(xiàn)還是表的范圍,這里有的同學可能就用id了,但是這里給大家講個知識點,當我們通過id定位的時候要留個小心眼,看看id的名字是什么意思,看id的名字和這個表是否有種對應(yīng)關(guān)系,如果沒有對應(yīng)關(guān)系,就看看名字他是auto-id后面一串亂七八槽的東西,通常這樣的id要有警覺性,通常id名字和他的內(nèi)容不產(chǎn)生對應(yīng)關(guān)系,要懷疑他是不是隨機值,那怎么驗證他是不是隨機值呢,你可以再打開一個網(wǎng)頁再去查看他的id信息會發(fā)現(xiàn)他的id變了,這是因為有的前端他是通過框架去產(chǎn)生的id,他會去隨機生成,這種情況就不能用這個id,這個時候我們就繼續(xù)往上找,我們再往上找一層,注意有的時候HTML里面好多層包含文本的范圍都是差不多的,我們在往上層找,發(fā)現(xiàn)上層又有一個auto-id。

他還是不能用,繼續(xù)往上找:

我們發(fā)現(xiàn)找到這層高亮顯示的部分還是這個范圍,這個id沒有亂七八槽的字符串了,這個不是隨機產(chǎn)生了,我們先根據(jù)id找到這個元素然后把里面所有的文本內(nèi)容打印出來,對應(yīng)的代碼是:
#從selenium里面導入webdriver
from selenium import webdriver
#指定chrom的驅(qū)動
#執(zhí)行到這里的時候Selenium會到指定的路徑將chrome driver程序運行起來
driver = webdriver.Chrome('E:\ChromDriver\chromedriver.exe')
#driver = webdriver.Firefox()#這里是火狐的瀏覽器運行方法
#隱式等待
driver.implicitly_wait(10)
#get 方法 打開指定網(wǎng)址
driver.get('https://music.163.com/#/discover/toplist?id=60198')
#通過id找到元素,并打印文本
div = driver.find_element_by_id('song-list-pre-cache')
print(div.text)
#退出
driver.quit()
運行上段代碼我們會發(fā)現(xiàn)pychrom輸出結(jié)果會停了好久沒反應(yīng),接著彈出報錯:

這里大家應(yīng)該存在兩個疑問:1.為什么停了好久沒反應(yīng)?2.為什么過了一段時間才拋出異常?根據(jù)報錯信息我們定位報錯的代碼是div = driver.find_element_by_id('song-list-pre-cache'),意思是根據(jù)這個id找不到,但是為什么過了好久才報錯呢,是因為隱式等待,那第二問題為什么會失???為什么沒找到呢,如果大家以后碰到這種問題明明很簡單根據(jù)id沒找到,大家有理由去懷疑這個元素是在一個fram里面,我們繼續(xù)打開網(wǎng)易云,順著這個元素往上找。

底下這個條子就是當前找的這個元素在整個html樹上屬于哪個節(jié)點,上一個就是他的父節(jié)點,這邊用“...”三個點縮起來了,因為他的上層節(jié)點很多,點一下就可以展開,我們往上層一層層找,會找到一個body節(jié)點:

body是整個HTML里面的,我們再網(wǎng)上找發(fā)現(xiàn)確實有個html,一般來說html是整個HTML文本的跟節(jié)點最上層的那個節(jié)點,但是我們再網(wǎng)上找會發(fā)現(xiàn)一個iframe的節(jié)點:

然后iframe上面又有一個body,body上面又有一個html,所以說iframe在HTML里面是一個很特殊的元素,iframe內(nèi)部會有另外一個HTML,如果把一個HTML文檔的范圍比喻成一個國家的話,iframe就像一個國中之國 ,這個地方為什么找不到就是這個原因,我們?nèi)笔∮?strong>Selenium打開一個網(wǎng)頁的時候,他的查找范圍是當前的根HTML里面,如果說HTML里面有個iframe的話,iframe里面這個HTMl元素他會忽略掉的他不會去找,這就是為什么找不到的原因,假如我們現(xiàn)在就要找iframe里面的這個元素要怎么找呢?這個就需要另外的方法了。
常見的frame有兩種:
一種是frameset: 打開http://www.w3school.com.cn/html/html_frames.asp通過使用框架,你可以在同一個瀏覽器窗口中顯示不止一個頁面。每個frame 都對應(yīng)了一個html文檔。

另一種是iframe:inner frame的意思。打開 http://www.w3school.com.cn/html/html_iframe.asp

iframe 用于在網(wǎng)頁內(nèi)顯示網(wǎng)頁。和frameset不同的是,frameset內(nèi)置好幾個子html而 iframe 只內(nèi)置一個子html,因為 frame 或者iframe 都是一個全新的 html 文檔, 是被嵌入的另一份html文檔,我們Selenium driver當前打開的是 主 html, 而我們這里要查看的是被嵌入的html,selenium 當前 的 webdriver element 對應(yīng)的是主 html 不能找到 iframe 里面的 html 的元素。
- 切換到frame里面:
driver.swith_to.frame()
切換frame可以用:index(索引值從0開始)、id(注意那些會變的id)、name 、webelement(driver.find_element_by_tag_name('iframe'))任意種方式切換。
切換回主html里面:
driver.switch_to_default_content()
用這個方法就可以切入到frame里面,注意HTML中他可能不止一個frame有何能有很多,假如有多個要切換到那個frame里面的,我們看一下我們要切入的這個iframe

他有name也有id,我們先通過name,看代碼:
from selenium import webdriver
#指定chrom的驅(qū)動
#執(zhí)行到這里的時候Selenium會到指定的路徑將chrome driver程序運行起來
driver = webdriver.Chrome('E:\ChromDriver\chromdriver2.43\chromedriver.exe')
driver.implicitly_wait(10)
#get 方法 打開指定網(wǎng)址
driver.get('https://music.163.com/#/discover/toplist?id=60198')
#通過name
driver.switch_to.frame("contentFrame")
#找到表并打印文本
div = driver.find_element_by_id('song-list-pre-cache')
print(div.text)
#退出
driver.quit()
也可以通過id:
driver.switch_to.frame("g_iframe")
如果沒有name也沒有id沒有任何屬性該咋辦呢,我們可以通過下標:
driver.switch_to.frame(0)
這樣也是可以的,如果iframe里面又嵌套了一個iframe該怎么辦呢,首先我們要先切換到第一個iframe里面再切換到嵌套的iframe里面,比如我們name等于“contentFrame”iframe里面嵌套了一個id為“abc”的iframe,該怎么寫呢?
driver.switch_to.frame("contentFrame")
driver.switch_to.frame("abc")
就可以了,然后在id為‘a(chǎn)bc’的iframe里面去找元素。我們進入iframe里面操作完歌曲列表,想退出外層比如我想操作導航欄里面的內(nèi)容推薦,排行榜怎么找?注意我們不能緊接著上面的代碼去找,因為導航欄不在iframe里面,這個iframe就是在最外層的,需要怎么退到最外層呢,看代碼:
driver.switch_to.default_content()
這就退出到最外層了。
本章完,喜歡點個贊(* ̄︶ ̄)
下面是小練習
打開百度新歌榜, http://music.baidu.com/top/new
在排名前50的歌曲中,找出其中排名上升的歌曲和演唱者注意: 有的歌曲名里面有 "影視原聲" 這樣的標簽, 要去掉大家打開鏈接看一下,那些是排行上升的歌曲,就是箭頭向上的,經(jīng)過我們的查找i class=“up”(i就是icon圖標的意思)的就是排行上升的歌曲。

那我們就找所有元素里面icon是up的就是上升的,但是這個i class=‘up’不是我們最終要找的,我們要找的是他所屬的上層的節(jié)點。

他的上層節(jié)點是div,div的上層節(jié)點是li,最后我們發(fā)現(xiàn)每行歌對應(yīng)一個li

那我們要找的就是所有的li,這個li是屬于ul的,li里面所有具有i class=‘up’的就是我們要找的歌,那我們怎么找到所有的li呢?我們能不能用find_elements_by_tag_name(),這個方法呢,這里我們是不建議的,這樣找是整個頁面去找的可能會找到不屬于ul的li那就多找了,通長做web自動化我們先找明確包含你想找的范圍里面的li,這里就是ul,你先找到他的上層節(jié)點把他的范圍限定下,不要在整個范圍去找li,方法就是找上層節(jié)點,但是這個上層節(jié)點沒有id我們就再往上層找,這里我們就找到了div。

正好這個div也是包含這20首歌的,代碼如下:
#從selenium里面導入webdriver
from selenium import webdriver
#指定chrom的驅(qū)動
#執(zhí)行到這里的時候Selenium會到指定的路徑將chrome driver程序運行起來
driver = webdriver.Chrome('E:\ChromDriver\chromdriver2.43\chromedriver.exe')
#driver = webdriver.Firefox()#這里是火狐的瀏覽器運行方法
driver.implicitly_wait(10)
#get 方法 打開指定網(wǎng)址
driver.get('http://music.taihe.com/top/new')
#層層往下找
#div = driver.find_element_by_id('songListWrapper')
#liList = div.find_elements_by_tag_name('li')
#上面兩行可以寫成這樣
liList = driver.find_elements_by_css_selector('#songListWrapper li')
#這里改成0.5因為find如果找不到就會等待10秒,
# 但是不是所有的歌都是有上升箭頭的,改成0.5就不用等了
driver.implicitly_wait(0.5)
for li in liList:
#那些是有up標簽的歌曲
upTags = li.find_elements_by_class_name('up')
if upTags:
#由于只要歌曲名和演唱者名
title = li.find_element_by_class_name('song-title')
titlesStr = title.find_element_by_tag_name('a').text
authorsStr = li.find_element_by_class_name('author_list').text
print(f'{titlesStr:10s}:{authorsStr}')
driver.implicitly_wait(10)
driver.quit()
大家可以運行試一下,看看是不是想要的結(jié)果,這里我們用了新的知識點,我們沒學習CSS、XPath之前為了找到尋找songListWrapper里面的li,我們先找到他的上層songListWrapper再找里面的li,這里面我們用一行語句就搞定了,一步到位。