最近在學習 selenium,剛好公司內部有一個課程學習的網(wǎng)站,每個人都需要進去看視頻學習,還會統(tǒng)計學習進度,正好利用 selenium 實現(xiàn)自動掛機學習。
問題
網(wǎng)站頁面有一個課程列表,需要逐個點進去學習。
思路:利用 selenium 找到課程列表元素,通過遍歷找出還沒學習完成的課程,然后逐個點擊課程進行學習。
結果在運行的過程中,經(jīng)常在學習第二個課程的時候報 StaleElementReferenceException 錯誤。
分析
經(jīng)查詢, StaleElementReferenceException 是因為要操作的元素已經(jīng)不在文檔中了,導致操作失敗。
出現(xiàn)這個問題的原因,是因為獲取到這個元素后,頁面可能發(fā)生了以下變化:
- 當前瀏覽器的頁面已經(jīng)不是獲取這個元素時的頁面(比如跳轉到了新的頁面),或者頁面刷新了;
- 當前元素已被刪除并重新添加回頁面(比如通過 ajax 獲取到新的數(shù)據(jù),然后用 javascript 構建出新的元素并替換掉原來的);
- 元素是在 iframe 里面的,而 iframe 刷新了。
對比我的情況,是發(fā)生第 2 種情況:課程的學習進度是每隔一段時間就進行更新,而更新是通過 javascript 替換掉原來元素,所以導致原本獲取到的元素失效了。
解決方法
重新定位元素
經(jīng)過觀察,這個頁面的每個列表元素都有唯一 id,而同一元素的 id 即使元素刷新過后還是保持不變的。
因此考慮通過 beautifulsoup 查找出目標元素的 id,再通過 selenium 的 find_element_by_id 方法重新找出元素并進行點擊。
這樣即使頁面刷新了,但還是能通過 find_element_by_id 方法找到最新的元素,而使用 beautifulsoup 查找的效率比直接用 selenium 要高。
while True:
# 使用 BeautifulSoup 分析
bs = BeautifulSoup(driver.page_source, 'lxml')
# 獲取目標列表
sections = bs.select('.chapter-list-box')
learn_section_id = None
for section in sections:
# 獲取元素的 id
id = section.attrs['id']
state = section.select_one(
'.section-item > .pointer').text.strip()
# 找出未完成的課程
if state == '重新學習':
continue
else:
learn_section_id = id
break
if learn_section_id is None:
break
else:
driver.find_element_by_id(learn_section_id).click()
直接重試
上面的方法經(jīng)使用后,雖然 StaleElementReferenceException 出現(xiàn)的幾率小了,但偶然的情況下還是會繼續(xù)出現(xiàn)。
可能是因為在找元素和點擊的過程間,頁面又再刷新了?
最后實在是沒有辦法,只能通過簡單粗暴的方法解決:捕捉此異常,然后直接重試。
經(jīng)改進后的代碼:
while True:
try:
# 使用 BeautifulSoup 分析
bs = BeautifulSoup(driver.page_source, 'lxml')
# 獲取目標列表
sections = bs.select('.chapter-list-box')
learn_section_id = None
for section in sections:
# 獲取元素的 id
id = section.attrs['id']
state = section.select_one(
'.section-item > .pointer').text.strip()
# 找出未完成的課程
if state == '重新學習':
continue
else:
learn_section_id = id
break
if learn_section_id is None:
break
else:
driver.find_element_by_id(learn_section_id).click()
.......
except StaleElementReferenceException:
continue