說在前面
- 驗證碼登入的demo後續可以單獨講解,VIP學員對這部分應該都是掌握的,此處不再贅述
- 本文假設了一個場景
- 你透過OCR識別的驗證碼是有一定的錯誤機率的
- 本文是透過識別後的驗證碼去加一個隨機字元,如果取到的是''空字元則可能成功,否則必然不會成功
- 所涉及的python庫
- selenium
- ddddocr
- tenacity
上程式碼
- 細節詳見註釋
from selenium import webdriver
from time import sleep
from tenacity import TryAgain, retry, wait_random
def get_element(locator):
'''
這個函式用來判斷是否存在某個元素
'''
try:
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
return WebDriverWait(driver, 5, 0.5).until(EC.visibility_of_element_located(locator))
except:
return False
driver = webdriver.Chrome()
driver.implicitly_wait(5)
driver.get('http://114.116.2.138:8090/forum.php') # 這個地址你可以訪問,是我搭建在雲端的一個容器,開源論壇
driver.find_element('css selector', '#ls_username').send_keys('admin')
driver.find_element('css selector', '#ls_password').send_keys('123456')
driver.find_element('css selector', 'button.pn.vm').click()
@retry(wait=wait_random(min=3, max=5)) # 等待一個時間區間,3-5s,注意這個時間的下限建議>hint存在的最大時間
def code_login():
'''
這個函式是用來反覆驗證碼登入的
'''
# 驗證碼元素
ele_code = driver.find_element('css selector', '[id^=vseccode_cS]>img')
import ddddocr
ocr = ddddocr.DdddOcr()
code_text = ocr.classification(ele_code.screenshot_as_png)
# 當失敗的時候尤其要注意: 清空已輸入的內容
driver.find_element('css selector', 'input[id^=seccodeverify_cS]').clear()
test_data = ['1','a','','2','b']
from random import choice
choice_data = choice(test_data)
# 輸入識別後的資料+隨機字元
driver.find_element('css selector', 'input[id^=seccodeverify_cS]').send_keys(code_text + choice_data)
# 點選登入
driver.find_element('css selector', "[name='loginsubmit']>strong").click()
# 注意! 你可以去操作網頁,點選登入如果失敗會彈出提示 "抱歉,驗證碼填寫錯誤"
hint_locator = 'css selector', '.pc_inner>i'
if get_element(hint_locator): # 如果出現這個元素,就...
# 點選下驗證碼,重新整理一下
driver.find_element('css selector', '[id^=vseccode_cS]>img').click()
# 丟擲異常,重跑
raise TryAgain
code_login()
聊聊tenacity
- 這個庫將重試這件事基本做透了
1、 無條件重試
- 你瘋了
from tenacity import retry
@retry
def retry_without_anything():
print('retry...')
raise Exception # 不加這個可不會重試
retry_without_anything()
2、重試指定次數後停止
from tenacity import retry, stop_after_attempt
@retry(stop=stop_after_attempt(3))
def retry_times():
print(f'retry... times')
raise Exception
retry_times()
- 實際執行的效果是這樣
retry... times
retry... times
retry... times
Traceback (most recent call last):
File "D:\Python39\lib\site-packages\tenacity\__init__.py", line 407, in __call__
result = fn(*args, **kwargs)
File "demo_retry.py", line 20, in retry_times
raise Exception
Exception
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "demo_retry.py", line 23, in <module>
retry_times()
File "D:\Python39\lib\site-packages\tenacity\__init__.py", line 324, in wrapped_f
return self(f, *args, **kw)
File "D:\Python39\lib\site-packages\tenacity\__init__.py", line 404, in __call__
do = self.iter(retry_state=retry_state)
File "D:\Python39\lib\site-packages\tenacity\__init__.py", line 361, in iter
raise retry_exc from fut.exception()
tenacity.RetryError: RetryError[<Future at 0x16628b45460 state=finished raised Exception>]
- 別擔心,你可能在最後一次的時候就不丟擲異常了。
3、過一定時間後停止重試
from tenacity import retry, stop_after_delay
import arrow
from time import sleep
@retry(stop=stop_after_delay(10))
def retry_times():
print(arrow.now().format('YYYY-MM-DD HH:mm:ss'))
sleep(1)
raise Exception
retry_times()
- 輸出像這樣
2023-02-21 17:32:01
2023-02-21 17:32:02
2023-02-21 17:32:03
2023-02-21 17:32:04
2023-02-21 17:32:05
2023-02-21 17:32:06
2023-02-21 17:32:07
2023-02-21 17:32:08
2023-02-21 17:32:10
2023-02-21 17:32:11
# 最後丟擲異常
4、組合條件
@retry(stop=(stop_after_delay(10) | stop_after_attempt(5)))
def retry_multi_conditions():
print("10秒後或者5次重試後停止重試")
raise Exception
5、重試間隔
-
之前的重試是無縫銜接的
-
你可以讓重試之間有延遲
@retry(wait=wait_fixed(2)) def retry_wait_time1(): print(arrow.now().format('YYYY-MM-DD HH:mm:ss')) print("每次重試前等待2秒") raise Exception retry_wait_time1()
-
當然上面的仍然是個無限重試
-
你可以組合前面的停止
@retry(wait=wait_fixed(2),stop=stop_after_attempt(3))
-
重試等待間隔可以設定一個區間(最大最小值)
from tenacity import retry, stop_after_attempt, wait_random import arrow @retry(wait=wait_random(min=1,max=4),stop=stop_after_attempt(3)) def retry_wait_time1(): print(arrow.now().format('YYYY-MM-DD HH:mm:ss')) print("每次重試前等待1~4秒區間") raise Exception retry_wait_time1()
6、是否重試!!!
-
這是最重要的了
-
重試的條件一:引發特定或一般異常的重試
from tenacity import retry, retry_if_exception_type, stop_after_attempt @retry(retry=retry_if_exception_type(ZeroDivisionError), stop= stop_after_attempt(5)) def retry_if_exception(): print('retry...') print(10/0) # 現在觸發的就是ZeroDivisionError,如果把此處改為 print(a),則遇到的是NameError,那就不會重試 raise Exception retry_if_exception()
-
引發 TryAgain 異常隨時顯式重試
-
比如這樣
@retry def try_if_condition(): result = 23 if result == 23: raise TryAgain
-
上面的demo中就用到了這個
-
官網還有很多的例子,相對高階一些,如有興趣可以自行前往或者搜尋之