JB的閱讀之旅-Selenium2自動(dòng)化測(cè)試實(shí)戰(zhàn)

現(xiàn)狀

好久好久沒(méi)有更新博客了,應(yīng)該有一個(gè)月了吧,這段時(shí)間內(nèi),好忙,公司的業(yè)務(wù)在上漲期,但是卻把下面的一個(gè)小朋友砍掉了(換組),所以很多打雜的時(shí)候都讓jb去做了,基本上每天活著跟咸魚一樣~

雖然是咸魚,但是還是要學(xué)習(xí),最近在返璞歸真,做了那么多年的測(cè)試狗,還是要學(xué)習(xí)點(diǎn)理論知識(shí),畢竟得有些料才能吹的更愉快;

這段時(shí)間基本上都是在學(xué)習(xí)測(cè)試相關(guān)的書籍,測(cè)試工作,理論知識(shí)等,也看到不同的測(cè)試書籍,感覺(jué)很多都很差強(qiáng)人意,每本書都總覺(jué)得有所欠缺,如果有機(jī)會(huì),挺想自己擼一本的,哈哈哈;

本篇作為閱讀篇的第一篇,主要來(lái)介紹下selenium,介紹的原因是,最近有些項(xiàng)目是web端的,隨著項(xiàng)目的穩(wěn)定,后面會(huì)考慮自動(dòng)化,而PC的自動(dòng)化,基本上都是用selenium,那就來(lái)圍觀吧~

前言

之前看coder-pig文章時(shí)有大致了解,很早之前也聽(tīng)過(guò)selenium,但是還是想系統(tǒng)看一遍,聽(tīng)說(shuō)蟲(chóng)師的這本書還可以,因此就來(lái)學(xué)習(xí)學(xué)習(xí)下~

雖然這部書叫selenium,但是有大致三分之二的內(nèi)容是測(cè)試跟Python相關(guān)的內(nèi)容,因此會(huì)優(yōu)先介紹selenium相關(guān)內(nèi)容,最后會(huì)貼一些自己覺(jué)得有點(diǎn)用的信息;

說(shuō)明,本篇內(nèi)容是基于coder-pig的原文進(jìn)行補(bǔ)充及介紹,算是一個(gè)整理;

書籍信息

本書全名:Selenium 2 自動(dòng)化測(cè)試實(shí)戰(zhàn),基于Python語(yǔ)言
作者:蟲(chóng)師
出版社:電子工業(yè)出版社
版次:2016年1月第1版

Selenium

簡(jiǎn)介

Selenium是一個(gè)自動(dòng)化測(cè)試框架,通過(guò)他,可以編寫代碼讓瀏覽器:

  • 自動(dòng)加載網(wǎng)頁(yè),獲取當(dāng)前呈現(xiàn)頁(yè)面的源碼;
  • 模擬點(diǎn)擊和其他交互方式,最常用:模擬表單提交(比如模擬登錄);
  • 頁(yè)面截屏;
  • 判斷網(wǎng)頁(yè)某些動(dòng)作是否發(fā)生,等等。

Selenium是不支持瀏覽器功能的,需要和第三方的瀏覽器一起搭配使用,支持下述瀏覽器,你需要把對(duì)應(yīng)的瀏覽器驅(qū)動(dòng)下載到Python的對(duì)應(yīng)路徑下:

安裝

直接利用pip命令進(jìn)行安裝,命令如下:

pip install selenium

接著下載瀏覽器驅(qū)動(dòng),大部分用的Chrome瀏覽器,就以此為例,其他瀏覽器可自行搜索相關(guān)文檔,打開(kāi)Chrome瀏覽器鍵入:

chrome://version

即可查看Chrome瀏覽器版本的相關(guān)信息,主要是關(guān)注版本號(hào):

Google Chrome   68.0.3440.106 (正式版本) (32 位) (cohort: Stable)
修訂版本    1c32c539ce0065a41cb79da7bfcd2c71af1afe62-refs/branch-heads/3440@{#794}
操作系統(tǒng)    Windows
JavaScript  V8 6.8.275.26
Flash   30.0.0.154 C:\Users\jb\AppData\Local\Google\Chrome\User Data\
PepperFlash\30.0.0.154\pepflashplayer.dll
用戶代理    Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, 
like Gecko) Chrome/68.0.3440.106 Safari/537.36
命令行 "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" 
--flag-switches-begin --flag-switches-end
可執(zhí)行文件路徑 C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
個(gè)人資料路徑  C:\Users\jb\AppData\Local\Google\Chrome\User Data\Default

這里看到,大版本號(hào)是68,接下來(lái)到下面的這個(gè)網(wǎng)站查看對(duì)應(yīng)的驅(qū)動(dòng)版本號(hào):

https://chromedriver.storage.googleapis.com/2.41/notes.txt

可以看到如下的版本號(hào)信息。

----------ChromeDriver v2.41 (2018-07-27)----------
Supports Chrome v67-69
Resolved issue 2458: Chromedriver fails to start with whitelisted-ips option [[Pri-1]]
Resolved issue 2379: Returned capabilities should include remote debugging port [[Pri-2]]
Resolved issue 1005: driver.manage().window().getSize() is not implemented on Android [[Pri-2]]
Resolved issue 2474: desktop launcher error messages are not readable by users [[Pri-]]
Resolved issue 2496: Fail fast when not able to start binary [[Pri-]]
Resolved issue 1990: Close Window return value does not conform with spec [[Pri-]]

ok,那就是下載2.4.1這個(gè)版本驅(qū)動(dòng),鏈接:

https://chromedriver.storage.googleapis.com/index.html?path=2.41/ 

打開(kāi)后可以看到所示的頁(yè)面,選擇對(duì)應(yīng)的系統(tǒng)下載即可。

下載完成后,把zip文件解壓下,解壓后的chromedriver.exe拷貝到Python的Scripts目錄下,另外這里不用糾結(jié)win32,在64位的瀏覽器上也是可以正常使用的!Mac的話把解壓后的文件拷貝到usr/local/bin目錄下 ,Ubuntu的話則拷貝到usr/bin目錄下。

demo

接下來(lái),來(lái)個(gè)小demo吧:

# coding = utf-8
# 為了防止亂碼問(wèn)題,以及方便的在程序中添加中文注釋,把編碼統(tǒng)一成 UTF-8。
from selenium import webdriver
# 導(dǎo)入 selenium 的 webdriver 包,只有導(dǎo)入 webdriver 包我們才能使用 webdriver API 進(jìn)行自動(dòng)化腳本的開(kāi)發(fā)。
browser = webdriver.Chrome()
#把webdriver的Chrome對(duì)象賦值給變量driver;
#只有獲得了瀏覽器對(duì)象后,才可以啟動(dòng)瀏覽器,打開(kāi)網(wǎng)址,操作頁(yè)面元素,Chrome瀏覽器驅(qū)動(dòng)默認(rèn)已經(jīng)在Selenium WebDriver包里了,可以直接調(diào)用;
#要先安裝相關(guān)的瀏覽器驅(qū)動(dòng)
browser.get("http://www.baidu.com")
#獲得瀏覽器對(duì)象后,通過(guò) get()方法,可以向?yàn)g覽器發(fā)送網(wǎng)址。
html_text = browser.page_source  
# 獲得頁(yè)面代碼
browser.quit()
#退出并關(guān)閉窗口

執(zhí)行這段代碼后,會(huì)自動(dòng)調(diào)起Chrome瀏覽器,并訪問(wèn)百度,可以看到瀏覽器頂部有下面的提示。


并且控制臺(tái)會(huì)輸出HTML的代碼,和Chrome的Elements頁(yè)面結(jié)構(gòu)完全一致。
那就可以用selenium這么玩了;

不過(guò),在實(shí)踐之前,再繼續(xù)介紹下selenium其他方法吧;

API

定位元素

  • find_element_by_id():根據(jù)id定位
  • find_element_by_name():根據(jù)節(jié)點(diǎn)名定位
  • find_element_by_class_name():根據(jù)class定位
  • find_element_by_tag_name():通過(guò)tag定位
  • find_element_by_link_text():根據(jù)鏈接的文本來(lái)定位
  • find_element_by_partial_link_text():根據(jù)元素標(biāo)簽對(duì)之間的部分文本信息來(lái)定位
  • find_element_by_xpath():使用Xpath進(jìn)行定位
  • find_element_by_css_selector():根據(jù)css定位
  • find_element(By.XX,value):根據(jù)By來(lái)聲明定位的方法,并且傳入對(duì)應(yīng)定位方法的定位參數(shù),比如By.ID跟By.CLASS_NAME等

另外,如果把element改為elements會(huì)定位所有符合條件的元素,返回一個(gè)List,比如:

find_elements_by_class_name

Selenium定位到結(jié)點(diǎn)位置會(huì)返回一個(gè)WebElement類型的對(duì)象,可以調(diào)用下述方法來(lái)提取需要的信息。

  • 獲取屬性:element.get_attribute()
  • 獲取文本:element.text
  • 獲取標(biāo)簽名稱:element.tag_name
  • 獲取結(jié)點(diǎn)id:element.id

控制窗口大小

# 設(shè)置瀏覽器寬480,高800顯示
driver.set_window_size(480,800): 

# 全屏顯示
driver.maximize_window()

頁(yè)面前進(jìn),后退,切換

在頁(yè)面操作過(guò)程中有時(shí)候點(diǎn)擊某個(gè)鏈接會(huì)彈出新的窗口,這時(shí)候需要切換到新打開(kāi)的窗口上進(jìn)行操作;

# 通過(guò)window_handles來(lái)遍歷
for handle in driver.window_handles:
    driver.switch_to_window(handle)

# 切換窗口
driver.switch_to.window("窗口名")

# 或通過(guò)window_handles來(lái)遍歷
for handle in driver.window_handles:
    driver.switch_to_window(handle)
    
# 前進(jìn)
driver.forward()  

# 后退
driver.back()        

刷新動(dòng)作

driver.refresh()

清除文本,模擬按鍵輸入,點(diǎn)擊動(dòng)作

# clear()用于清除文本輸入框中的內(nèi)容
driver.find_element_by_id("idinput").clear()

# send_keys()用于模擬鍵盤向輸入框里輸入內(nèi)容
driver.find_element_by_id("idinput").send_keys("username")

# click()用于進(jìn)行點(diǎn)擊操作
driver.find_element_by_id("loginbtn").click()

提交表單

# 通過(guò)定位搜索框并通過(guò)submit()提交搜索框的內(nèi)容,達(dá)到點(diǎn)擊搜索按鈕的效果
driver.find_element_by_id("query").submit()

鼠標(biāo)動(dòng)作

有時(shí)需要在頁(yè)面上模擬鼠標(biāo)操作,比如:?jiǎn)螕簦p擊,右鍵,按住,拖拽等,可以導(dǎo)入ActionChains類:selenium.webdriver.common.action_chains.ActionChains,使用ActionChains(driver).XXX調(diào)用對(duì)應(yīng)節(jié)點(diǎn)的行為。

  • click(element):?jiǎn)螕裟硞€(gè)節(jié)點(diǎn);
  • click_and_hold(element):?jiǎn)螕裟硞€(gè)節(jié)點(diǎn)并按住不放;
  • context_click(element):右鍵單擊某個(gè)節(jié)點(diǎn);
  • double_click(element):雙擊某個(gè)節(jié)點(diǎn);
  • drag_and_drop(source,target):按住某個(gè)節(jié)點(diǎn)拖拽到另一個(gè)節(jié)點(diǎn);
  • drag_and_drop_by_offset(source, xoffset, yoffset):按住節(jié)點(diǎn)按偏移拖拽
  • key_down:按下特殊鍵,只能用(Control, Alt and Shift),比如Ctrl+C
  • ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform();
  • key_up:釋放特殊鍵;
  • move_by_offset(xoffset, yoffset):按偏移移動(dòng)鼠標(biāo);
  • move_to_element(element):鼠標(biāo)移動(dòng)到某個(gè)節(jié)點(diǎn)的位置;
  • move_to_element_with_offset(element, xoffset, yoffset):鼠標(biāo)移到某個(gè)節(jié)點(diǎn)并偏移;
  • pause(second):暫停所有的輸入多少秒;
  • perform():執(zhí)行操作,可以設(shè)置多個(gè)操作,調(diào)用perform()才會(huì)執(zhí)行;
  • release():釋放鼠標(biāo)按鈕
  • reset_actions:重置操作
  • send_keys(keys_to_send):模擬按鍵,
    比如輸入框節(jié)點(diǎn).send_keys(Keys.CONTROL,'a') 全選輸入框內(nèi)容,
    輸入框節(jié)點(diǎn).send_keys(Keys.CONTROL,'x')剪切,
    模擬回退:
    節(jié)點(diǎn).send_keys(keys.RETURN);
    或者直接設(shè)置輸入框內(nèi)容:輸入框節(jié)點(diǎn).send_keys('xxx');
  • send_keys_to_element(element, *keys_to_send):和send_keys類似;

鍵盤事件

send_key()雖然可以模擬鍵盤輸入,但除此之外,還需要輸入其他按鍵,比如空格,這時(shí)候就需要用到Keys();
對(duì)應(yīng)類:selenium.webdriver.common.keys.Keys,使用send_keys(Keys.XX)輸入對(duì)應(yīng)的內(nèi)容即可;

  • BACK_SPACE:刪除鍵(BackSpace)(send_key(Keys.BACK_SPACE))
  • SPACE:空格鍵(Space)
  • TAB:制表鍵(TAB)
  • ESCAPE:回退鍵(Esc)
  • ENTER:回車鍵(Enter)
  • CONTROL,'a':全選(Ctrl+A)(send_key(Keys.CONTROL,'a'))
  • CONTROL,'c':復(fù)制(Ctrl+C)
  • CONTROL,'x':剪切(Ctrl+X)
  • CONTROL,'v':粘貼(Ctrl+V)
  • F1:鍵盤F1
  • ...
  • F12:鍵盤F12

頁(yè)面標(biāo)題和鏈接

# 獲取當(dāng)前網(wǎng)頁(yè)的標(biāo)題
driver.title

# 獲取當(dāng)前網(wǎng)頁(yè)的url
driver.current_url

頁(yè)面等待

現(xiàn)在的網(wǎng)頁(yè)越來(lái)越多采用了 Ajax技術(shù),這樣程序便不能確定何時(shí)某個(gè)元素完全加載出來(lái)了。

如果實(shí)際頁(yè)面等待時(shí)間過(guò)長(zhǎng)導(dǎo)致某個(gè)dom元素還沒(méi)出來(lái),但是你的代碼直接使用了這個(gè)WebElement,那么就會(huì)拋出NullPointer的異常。

為了避免這種元素定位困難而且會(huì)提高產(chǎn)生ElementNotVisibleException的概率。

所以Selenium 提供了兩種等待方式,一種是隱式等待,一種是顯式等待。

  • 顯式等待:
    指定某個(gè)條件,然后設(shè)置最長(zhǎng)等待時(shí)間。
    如果在這個(gè)時(shí)間還沒(méi)有找到元素,那么便會(huì)拋出異常,代碼示例如下:
from selenium import webdriver
from selenium.webdriver.common.by import By
# WebDriverWait 庫(kù),負(fù)責(zé)循環(huán)等待
from selenium.webdriver.support.ui import WebDriverWait
# expected_conditions 類,負(fù)責(zé)條件出發(fā)
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.PhantomJS()
driver.get("http://www.xxxxx.com/loading")
try:
    # 每隔10秒查找頁(yè)面元素 id="myDynamicElement",直到出現(xiàn)則返回
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))
    )
finally:
    driver.quit()

如果不寫參數(shù),程序默認(rèn)會(huì)0.5s調(diào)用一次來(lái)查看元素是否已經(jīng)生成,如果本來(lái)元素就是存在的,那么會(huì)立即返回。

下面是一些內(nèi)置的等待條件,你可以直接調(diào)用這些條件,而不用自己寫某些等待條件了。

等待條件 描述
title_is 標(biāo)題是內(nèi)容
title_contains 標(biāo)題包含某內(nèi)容
presence_of_element_located 結(jié)點(diǎn)加載出來(lái),傳入定位元組
visibility_of_element_located 結(jié)點(diǎn)課件,傳入定位元組
visibility_of 可見(jiàn),傳入結(jié)點(diǎn)對(duì)象
presence_of_all_elements_located 所有結(jié)點(diǎn)加載出來(lái)
text_to_be_present_in_element 某個(gè)結(jié)點(diǎn)文本包含某文字
text_to_be_present_in_element_value 某個(gè)結(jié)點(diǎn)值包含某文字
frame_to_be_available_and_switch_to_it 加載并切換
invisibility_of_element_located 結(jié)點(diǎn)不可見(jiàn)
element_to_be_clickable 節(jié)點(diǎn)可點(diǎn)擊
staleness_of 判斷結(jié)點(diǎn)是否仍在DOM,常用于判斷頁(yè)面是否已刷新
element_to_be_selected 結(jié)點(diǎn)可選擇,傳入結(jié)點(diǎn)對(duì)象
element_located_to_be_selected 結(jié)點(diǎn)可選擇,傳入定位元組
element_selection_state_to_be 傳入結(jié)點(diǎn)對(duì)象和狀態(tài),相等返回True,否則返回False
element_located_selection_state_to_be 傳入定位元組和狀態(tài),相等返回True,否則返回False
alert_is_present 是否出現(xiàn)警告
  • 隱式等待:
    隱式等待比較簡(jiǎn)單,就是簡(jiǎn)單地設(shè)置一個(gè)等待時(shí)間,單位為秒,代碼示例如下:
from selenium import webdriver
driver = webdriver.PhantomJS()
driver.implicitly_wait(10) # seconds
driver.get("http://www.xxxxx.com/loading")
myDynamicElement = driver.find_element_by_id("myDynamicElement")

如果不設(shè)置,就默認(rèn)等待時(shí)間為0

休眠sleep

有時(shí)候希望腳本執(zhí)行到某個(gè)位置做固定時(shí)間的休眠,這時(shí)候就需要用到sleep(),這個(gè)sleep方法是由Python的time模塊提供,因此使用之前需要from time import sleep

# 休眠X秒
sleep(X) 

多表單切換

在web應(yīng)用中經(jīng)常會(huì)遇到frame/iframe表單嵌套頁(yè)面的應(yīng)用,webdriver只能在一個(gè)頁(yè)面上對(duì)元素識(shí)別與定位,對(duì)于frame/iframe無(wú)法直接定位;

這時(shí)候就需要通過(guò)switch_to.frame()方法將當(dāng)前定位的主體切換為frame/iframe內(nèi)嵌的的頁(yè)面中;

# 切換到iframe(id = "XX")
driver.switch_to.frame("XX")

切換后就能正常操作元素了;

switch_to.frame()默認(rèn)可以直接獲取表單的id或name屬性,但如果iframe沒(méi)有可用的id和name屬性呢?

# 先通過(guò)xpath定位到iframe
xf = driver.find_element_by_xpath("http://*[@class="XX"]")

# 再將定位對(duì)象傳給switch_to.frame()方法
driver.switch_to.frame(xf)

彈窗

對(duì)應(yīng)類:selenium.webdriver.common.alert.Alert,用得不多,如果你觸發(fā)了某個(gè)事件,彈出了對(duì)話框,可以調(diào)用這個(gè)方法獲得對(duì)話框:alert = driver.switch_to_alert(),然后alert對(duì)象可以調(diào)用下述方法:

  • accept():確定
  • dismiss():關(guān)閉對(duì)話框
  • send_keys():傳入值
  • text():獲得對(duì)話框文本

上傳文件

一般網(wǎng)頁(yè)上傳文件,都是有一個(gè)瀏覽按鈕,選擇文件點(diǎn)擊上傳即可,但是對(duì)應(yīng)做自動(dòng)化測(cè)試來(lái)說(shuō),在選擇文件那個(gè)過(guò)程是非常麻煩的;

其實(shí),做過(guò)類似功能的同學(xué)會(huì)發(fā)現(xiàn),瀏覽按鈕其實(shí)就是input標(biāo)簽實(shí)現(xiàn)的上傳功能,可以看到是一個(gè)輸入框,居然如此,就可以用send_keys()來(lái)模擬;

driver.find_element_by_name("file").send_keys("C:\\jb.txt")

下載文件

運(yùn)行設(shè)置文件下載路徑;

options = webdriver.ChromeOptions()
prefs = {'profile.default_content_settings.popups': 0, 'download.default_directory': os.getcwd()}
options.add_experimental_option('prefs', prefs)
 
driver = webdriver.Chrome(chrome_options=options)
driver.get("http://pypi.Python.org/pypi/selenium")driver.find_element_by_partial_link_text("selenium-3.11.0-py2.py3-none-any").click()

頁(yè)面截圖

driver.save_screenshot("截圖.png")

# 截取當(dāng)前窗口,并指定截圖圖片的保存位置
dirver.get_screenshot_as_file("C:\\jb\\jb.jpg")

Cookies

有些站點(diǎn)需要登錄后才能訪問(wèn),用Selenium模擬登錄后獲取Cookie,

然后供爬蟲(chóng)使用的場(chǎng)景非常常見(jiàn),Selenium提供了獲取,增加,刪除Cookies的函數(shù),代碼示例如下:

# 獲取所有Cookies
browser.get_cookies()

# 獲取name對(duì)應(yīng)的cookie信息
browser.get_cookie(name)

# 增加Cookies,是字典對(duì)象,必須要有name 和value
browers.add_cookie({xxx})
driver.add_cookie({"name":"jb","value":"jbtest"})

# 如果需要遍歷,則如下:
for cookie in driver.get_cookies():
    print("%s -> %s " % (cookie["name"],cookie["value"]))

# 刪除所有Cookies
browser.delete_cookies()

# 刪除Cookie信息,name是要?jiǎng)h除的cookie的名稱,optionsString是該cookie的選項(xiàng),目前支持的選項(xiàng)包括“路徑”和“域”
browser.delete_cookie(name,optionsString)

實(shí)踐:

from selenium import webdriver
browser = webdriver.Chrome()

url = "https://www.baidu.com/"
browser.get(url)
# 通過(guò)js新打開(kāi)一個(gè)窗口
newwindow='window.open("https://www.baidu.com");'
# 刪除原來(lái)的cookie
browser.delete_all_cookies()
# 攜帶cookie打開(kāi)
browser.add_cookie({'name':'ABC','value':'DEF'})
# 通過(guò)js新打開(kāi)一個(gè)窗口
browser.execute_script(newwindow)
input("查看效果")
browser.quit()

這里還是需要說(shuō)下,add_cookie方法接受一個(gè)字典,字典中包含name,value,path,domain,secure,expiry,

正確的寫cookie格式:

cookie = {
    # "domain": ".58.com", #Firefox瀏覽器不能寫domain,如果寫了會(huì)報(bào)錯(cuò),谷歌需要寫否則也是報(bào)錯(cuò),這里就是一個(gè)坑。其他瀏覽器沒(méi)測(cè)試不知道情況。
    'name': name,
    'value': value,
    "expires": "",
    'path': '/',
    'httpOnly': False,
    'HostOnly': False,
    'Secure': False,

}
name:cookie的名稱
value:cookie對(duì)應(yīng)的值,動(dòng)態(tài)生成的
domain:服務(wù)器域名
expiry:Cookie有效終止日期
path:Path屬性定義了Web服務(wù)器上哪些路徑下的頁(yè)面可獲取服務(wù)器設(shè)置的Cookie
httpOnly:防腳本攻擊
secure:在Cookie中標(biāo)記該變量,表明只有當(dāng)瀏覽器和Web Server之間的通信協(xié)議為加密認(rèn)證協(xié)議時(shí)

為什么要構(gòu)造成這樣子,其實(shí)我們看下瀏覽器保存的cookie格式就明白了。下圖就是谷歌瀏覽器的cookie 的截圖。

代碼應(yīng)該這么寫:

這里有個(gè)問(wèn)題,cookie要key:value的格式,是非常麻煩的,尤其是像豆瓣那樣非常非常長(zhǎng)的cookie,再手動(dòng)一個(gè)一個(gè)改格式,這個(gè)工作量還不如重新用selenium寫一個(gè)登陸操作;

fiddler轉(zhuǎn)包的cookie格式是這樣的:

一個(gè)一個(gè)改會(huì)崩潰的;所以遇到cookie很長(zhǎng)的情況,還不如寫一個(gè)登陸腳本保存吧~

執(zhí)行JS語(yǔ)句

雖然webdriver提供瀏覽器的前進(jìn)和后退,但是并不提供滾動(dòng)瀏覽器的操作,因此來(lái)借助JS來(lái)控制瀏覽器的滾動(dòng)條;

driver.execute_script(js語(yǔ)句)

比如滾動(dòng)到底部的代碼示例如下:

js = document.body.scrollTop=10000
driver.execute_script(js)

又如設(shè)置瀏覽器窗口的滾動(dòng)條位置:

js = "window.scrollTo(100,450)"
driver.excute_script(js)

向文本框輸入文本信息:

text = "jbtest"
js = "var sum=document.getElementById("id"); sum.value='"+text+ " ';"
driver.excute_script(js)

處理HTML5 的視頻播放

HTML5定義了一個(gè)新的元素video,指定了一個(gè)標(biāo)準(zhǔn)的方式來(lái)嵌入電影片段;

from selenium import webdriver
from time import sleep

driver = webdriver.Firefox()
driver.get("http://videojs.com/")

video = driver.find_element_by_xpath("body/Setion[1]/div/video")

# 返回播放文件地址
url = driver.execute_script("return arguments[0].currentSrc;",video)

# 播放視頻
driver.execute_script("return arguments[0].play()",video)

# 暫停視頻
driver.execute_script("arguments[0].pause()",video)

JS函數(shù)有個(gè)內(nèi)置的對(duì)象叫做arguments;
arguments對(duì)象包含了函數(shù)調(diào)用的參數(shù)數(shù)組,[0]表示取對(duì)象的第一個(gè)值;

  • currentSrc:返回當(dāng)前音頻/視頻的URL,如果未設(shè)置音頻/視頻,則返回空字符串;
  • load():控制著視頻的加載
  • play():控制著視頻的播放
  • pause():控制著視頻的暫停

Headless

在介紹Headless之前,必須介紹下PhantomJS,

PhantomJS是沒(méi)有界面的瀏覽器,特點(diǎn):

會(huì)把網(wǎng)站加載到內(nèi)存并執(zhí)行頁(yè)面上的JavaScript,因?yàn)椴粫?huì)展示圖形界面,所以運(yùn)行起來(lái)比完整的瀏覽器要高效

而上面的例子,每次執(zhí)行后,都會(huì)打開(kāi)瀏覽器,很麻煩,這么看,PhantomJs是剛需啊;

但這里不打算介紹PhantomJs,因?yàn)?8年4月份,維護(hù)者宣布退出PhantomJs,意味著這個(gè)項(xiàng)目不再維護(hù)了;而同時(shí),Chrome和FireFox也開(kāi)始 提供Headless模式(無(wú)需調(diào)起瀏覽器),所以,后面的phantomjs小伙伴會(huì)遷移到這兩個(gè)瀏覽器上;

注意:Windows Chrome需要60以上的版本才支持 Headless模式,linux,unix系統(tǒng)需要 chrome瀏覽器 >= 59

啟用Headless模式也很簡(jiǎn)單,以上面打開(kāi)百度的代碼舉例,代碼如下:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

chrome_option = Options()
chrome_option.add_argument("--headless")
chrome_option.add_argument("--disable-gpu")

browser = webdriver.Chrome(chrome_options=chrome_option)  # 調(diào)用本地的Chrome瀏覽器
browser.get('http://www.baidu.com')  # 請(qǐng)求頁(yè)面,會(huì)打開(kāi)一個(gè)瀏覽器窗口
html_text = browser.page_source  # 獲得頁(yè)面代碼
browser.quit()  # 關(guān)閉瀏覽器
print(html_text)

就是多了設(shè)置chrome_option這幾步,執(zhí)行后,就不會(huì)再?gòu)棾鰹g覽器了,是不是很方便??

設(shè)置自定義請(qǐng)求頭

from selenium import webdriver
# 進(jìn)入瀏覽器設(shè)置
options = webdriver.ChromeOptions()
# 設(shè)置中文
options.add_argument('lang=zh_CN.UTF-8')
# 更換頭部
options.add_argument('user-agent="Mozilla/5.0 (iPod; U; CPU iPhone OS 2_1 like Mac OS X; ja-jp) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5F137 Safari/525.20"')
browser = webdriver.Chrome(chrome_options=options)
url = "https://httpbin.org/get?show_env=1"
browser.get(url)
browser.quit()

溫馨提示:遇到JS加載的問(wèn)題,建議先嘗試破解JS,不行再用selenium,selenium屬于無(wú)腦類型,而且效率比較差,假如你有1W個(gè)網(wǎng)站,都跑一趟,估計(jì)要一天了;

大部分selenium相關(guān)的操作都介紹完了,先留個(gè)印象,那接下來(lái),讓我們看看怎么用selenium來(lái)獲取豆瓣驗(yàn)證碼captcha這個(gè)屬性吧;

    "Cookie":"your cookie"   #這里需要輸入你自己的cookie信息,如果遇到轉(zhuǎn)義字符,轉(zhuǎn)
        義字符前面加\即可

Selenium詳細(xì)介紹

什么是Selenium

Selenium主要用于Web應(yīng)用程序的自動(dòng)化測(cè)試;

特點(diǎn):

  • 開(kāi)源,免費(fèi);
  • 多瀏覽器支持:Firefox、Chrome、IE、Opera、Edge;
  • 多平臺(tái)支持:Linux、Windows、MAC;
  • 多語(yǔ)言支持:Java、Python、Ruby、C#、JavaScript、C++;
  • 對(duì)Web頁(yè)面有良好的支持;
  • API簡(jiǎn)單、靈活(用開(kāi)發(fā)語(yǔ)言驅(qū)動(dòng))
  • 支持分布式測(cè)試用例執(zhí)行

Selenium有兩個(gè)版本:Selenium 1.0 和Selenium 2.0;
Selenium不是由單獨(dú)一個(gè)工具構(gòu)成的,而是由一些插件、類庫(kù)組成,每個(gè)部分都有起特點(diǎn)和應(yīng)用場(chǎng)景;

Selenium 1.0家譜:

Selenium IDE

Selenium IDE是嵌入到Firefox瀏覽器中的一個(gè)插件,實(shí)現(xiàn)簡(jiǎn)單的瀏覽器操作的錄制與回放功能;

官方給出它自身作用的定位:

快速地創(chuàng)建bug重現(xiàn)腳本,在測(cè)試人員測(cè)試過(guò)程中,發(fā)現(xiàn)bug之后可以通過(guò)IDE將重現(xiàn)的步驟錄制下來(lái),以幫助開(kāi)發(fā)人員
更容易的重現(xiàn)bug;

那就可以理解,IDE的重點(diǎn)在于錄制,避免不會(huì)寫代碼的同學(xué),而且每次寫xpath也會(huì)很煩;

安裝

在線安裝
通過(guò) firefox 瀏覽器訪問(wèn) selenium;

下載頁(yè)面:http://docs.seleniumhq.org/download/

在 selenium IED 下載介紹部分,點(diǎn)擊版本號(hào)鏈接;

firefox 瀏覽器將自動(dòng)識(shí)別需要下載的 selenium IED 插件,,點(diǎn)擊 Install Now 按鈕,安裝 selenium IED 插件。

安裝完成后重啟 firefox 瀏覽器,通過(guò)菜單欄“Tools(工具)”---> selneium IDE 可以打開(kāi),或通過(guò) Ctrl+Alt+S 快捷鍵打開(kāi)。

但是實(shí)際,上面的url,試了半天都訪問(wèn)不了,另外還有,如果非Firefox瀏覽器是不是就用不了IDE?

插件安裝
非也,還有個(gè)插件安裝的方法,此方法來(lái)自小敏同學(xué)親測(cè)可行,Python2.7版本,F(xiàn)irefox40.0版本

1)python2.7
mac/Linux自帶,不用裝;
Windows需要手動(dòng)安裝,要裝的小伙伴自行百度,so easy ~

2)selenium
由于selenium IDE在新版本firefox上并不支持,沒(méi)有錄制功能寫起來(lái)還是挺費(fèi)神的,建議保守的都選擇舊版本。

如果已經(jīng)安裝了selenium3,需要先卸載;

查看selenium版本命令:
pip show selenium

卸載selenium命令:
pip uninstall selenium

selenium用命令行pip install selenium==2.53.6安裝時(shí),由于被墻了,選擇下載安裝包自行安裝,步驟如下:

  • 下載安裝包selenium-2.53.6.tar.gz
  • 將安裝包放到python2.7的site-packages目錄下,解壓,比如目錄是/Library/Python/2.7/site-packages/
  • cd到上一步解壓后的selenium目錄下,python setup.py install,安裝完成,查看版本號(hào)確認(rèn)安裝成功

3)下載安裝firefox 40.0版本

4)下載selenium_ide-2.9.1-fx.xpi,拖到firefox里面安裝好后,firefox右上角多出selenium IDE插件入口

5)下載firebug-2.0.16-fx.xpi,拖到firefox里面安裝好后,firefox右上角多出firebug插件入口

至此,環(huán)境安裝完成。

6)錄制自動(dòng)化腳本
點(diǎn)擊firefox右上角selenium IDE插件圖標(biāo),進(jìn)入selenium IDE主界面;

  • 在下圖序號(hào)1紅框中,輸入你要測(cè)試的web url;
  • 在下圖序號(hào)2紅框中,調(diào)節(jié)你要執(zhí)行用例的速度,一般建議調(diào)到中間;
  • 在下圖序號(hào)3紅框中,點(diǎn)擊錄制按鈕開(kāi)始錄制;
  • 在下圖序號(hào)4紅框中,對(duì)錄制結(jié)果進(jìn)行調(diào)節(jié),可以增刪改事件、參數(shù);
  • 在下圖序號(hào)3紅框中,點(diǎn)擊停止錄制按鈕停止錄制;
  • 在下圖序號(hào)5紅框中,點(diǎn)擊按鈕開(kāi)始執(zhí)行用例;
  • 可以在菜單中選擇文件——export test case as——python 2/unittest/web driver,將腳本導(dǎo)出為python,本地編輯直接腳本調(diào)用。

7)優(yōu)化腳本
上步中最后導(dǎo)出的腳本,只包含了操作,沒(méi)有包含斷言結(jié)果判斷,也缺少容錯(cuò),想要持續(xù)集成自動(dòng)生成結(jié)果,還需要自己按需補(bǔ)充。

Selenium Grid

是一種自動(dòng)的測(cè)試輔助功能,Grid通過(guò)利用現(xiàn)有的計(jì)算機(jī)基礎(chǔ)設(shè)施,能加快Web-App的功能測(cè)試;

利用Grid可以很方便地實(shí)現(xiàn)在多臺(tái)機(jī)器上和異構(gòu)環(huán)境中運(yùn)行測(cè)試用例;

聽(tīng)上去會(huì)一臉懵逼,簡(jiǎn)單說(shuō),Selenium Grid可以解決重復(fù)執(zhí)行測(cè)試和多瀏覽器兼容測(cè)試的亮點(diǎn),并且是使用分布式執(zhí)行測(cè)試;

那分布式是什么概念?簡(jiǎn)單的說(shuō)就是老大收到任務(wù),分發(fā)給手下去干;

通過(guò)Selenium Grid的可以控制多臺(tái)機(jī)器多個(gè)瀏覽器執(zhí)行測(cè)試用例,分布式上執(zhí)行的環(huán)境在Selenium Grid中稱為node節(jié)點(diǎn)。

舉例說(shuō)明一下,比如用例上萬(wàn),一臺(tái)機(jī)器執(zhí)行全部測(cè)試用例耗時(shí)5個(gè)小時(shí),而如果需要覆蓋主流瀏覽器比如Chrome、Firefox,加起來(lái)就是10個(gè)小時(shí);

最笨的辦法就是另外拿臺(tái)機(jī)器,然后部署環(huán)境,把測(cè)試用例分開(kāi)去執(zhí)行然后合并結(jié)果即可。

而Selenium也想到了這點(diǎn),所以有了Selenium Grid的出現(xiàn),它就是解決分布式執(zhí)行測(cè)試的痛點(diǎn)。

工作原理

Selenium Grid實(shí)際它是基于Selenium RC的,而所謂的分布式結(jié)構(gòu)就是由一個(gè)hub節(jié)點(diǎn)和若干個(gè)node代理節(jié)點(diǎn)組成。

Hub用來(lái)管理各個(gè)代理節(jié)點(diǎn)的注冊(cè)信息和狀態(tài)信息,并且接受遠(yuǎn)程客戶端代碼的請(qǐng)求調(diào)用,然后把請(qǐng)求的命令轉(zhuǎn)發(fā)給代理節(jié)點(diǎn)來(lái)執(zhí)行。

下面結(jié)合環(huán)境部署來(lái)理解Hub與node節(jié)點(diǎn)的關(guān)系。

環(huán)境部署

1)下載selenium-server-standalone-2.53.1.jar

下載地址:http://selenium-release.storage.googleapis.com/index.html

2)啟動(dòng)hub

使用快捷鍵WIN+R打開(kāi)運(yùn)行對(duì)話框,輸入cmd確定,進(jìn)入命令窗口

進(jìn)入selenium-server-standalone-2.53.1.jar包的位置,如E:\selenium;

啟動(dòng)hub,命令如下:

 java -jar selenium-server-standalone-2.53.1.jar -role hub -maxSession 10 -port 4444
參數(shù) 解釋
role hub 啟動(dòng)運(yùn)行hub
port 設(shè)置端口號(hào),hub的默認(rèn)端口是4444,這里使用的是默認(rèn)的端口,當(dāng)然可以自己配置
maxSession 最大會(huì)話請(qǐng)求,這個(gè)參數(shù)主要要用并發(fā)執(zhí)行測(cè)試用例,默認(rèn)是1,建議設(shè)置10及以上

瀏覽器打開(kāi)地址:http://localhost:4444/grid/console,出現(xiàn)如下圖表示hub啟動(dòng)成功。

啟動(dòng)hub后,就需要運(yùn)行節(jié)點(diǎn)啦,最少都要有一個(gè)node節(jié)點(diǎn),不然hub就成空頭司令了;而node節(jié)點(diǎn)可以與hub在同一臺(tái)機(jī)器上運(yùn)行,演示一個(gè)node節(jié)點(diǎn)與hub同機(jī),另一個(gè)node節(jié)點(diǎn)啟動(dòng)了一臺(tái)虛擬機(jī)。

名稱 IP
hub機(jī) 192.168.0.245
node1機(jī) 192.168.0.245
node2機(jī) 192.168.0.183

3)啟動(dòng)node節(jié)點(diǎn)1

node1節(jié)點(diǎn),配置firefox瀏覽器,運(yùn)行下面命令:

java -jar selenium-server-standalone-2.53.1.jar -role node -port 5555 -hub http://192.168.0.245:4444/grid/register -maxSession 5 -browser browserName=firefox,seleniumProtocol=WebDriver,maxInstances=5 ,platform=WINDOWS,version=45.0.2

沒(méi)有報(bào)錯(cuò)則再次刷新一下http://localhost:4444/grid/console的訪問(wèn)會(huì)發(fā)現(xiàn)node節(jié)點(diǎn)已經(jīng)顯示,表示啟動(dòng)成功;

參數(shù) 解釋
role node 啟動(dòng)運(yùn)行node節(jié)點(diǎn)
port 555 指定node節(jié)點(diǎn)端口
hub http://192.168.0.245:4444/grid/register hub機(jī)地址
maxSession 5 node節(jié)點(diǎn)最大會(huì)話請(qǐng)求
browser browserName=firefox,seleniumProtocol=WebDriver, maxInstances=5,platform=WINDOWS,version=45.0.2 這個(gè)就是設(shè)置瀏覽器的參數(shù)啦
browserName表示瀏覽器名字,maxInstances表示最大實(shí)例,可以理解為最多可運(yùn)行的瀏覽器數(shù),不能大于前面maxSession的值,否則可能會(huì)出錯(cuò);platform表示操作系統(tǒng);version表示瀏覽器版本。

4)啟動(dòng)node節(jié)點(diǎn)2

node2節(jié)點(diǎn),在目錄放了chromedriver.exe文件,這里要提示一下,這個(gè)chromedriver.exe文件前面說(shuō)過(guò)下載地址了,主要是版本需要與系統(tǒng)中安裝的chrome瀏覽器匹配;


運(yùn)行下面命令:

java -jar selenium-server-standalone-2.53.1.jar -role node -port 6666 -hub http://192.168.0.245:4444/grid/register -Dwebdriver.chrome.driver=chromedriver.exe -maxSession 5 -browser browserName=chrome,seleniumProtocol=WebDriver,maxInstances=5,platform=WINDOWS

沒(méi)有報(bào)錯(cuò)則再次刷新一下http://localhost:4444/grid/console的訪問(wèn)會(huì)發(fā)現(xiàn)node節(jié)點(diǎn)已經(jīng)顯示,表示啟動(dòng)成功;

如果使用的chromedriver.exe與selenium-server-standalone-2.53.1版本或者瀏覽器chrome版本不匹配都會(huì)報(bào)錯(cuò)提示,具體原因需要具體解決。

參數(shù) 解釋
Dwebdriver.chrome.driver=chromedriver.exe 瀏覽器插件,如果是其他瀏覽器就寫對(duì)應(yīng)的名字
如firefox: -Dwebdriver.firefox.driver=geckodriver.exe
注意多了這個(gè)參數(shù)注意是chromedriver.exe需要指定,而對(duì)于Webdriver2是支持geckodriver所以不需要指定geckodriver,但前提是firefox瀏覽器版本不能大于46,所以看到node節(jié)點(diǎn)1使用的是45版本的瀏覽器。

使用

當(dāng)實(shí)例化Hub遠(yuǎn)程時(shí),會(huì)根據(jù)配置去匹配Hub上注冊(cè)的node代理節(jié)點(diǎn),匹配成功后轉(zhuǎn)發(fā)給代理節(jié)點(diǎn),這時(shí)候代理節(jié)點(diǎn)會(huì)生成sessionid啟動(dòng)瀏覽器,然后響應(yīng)給Hub說(shuō)一切準(zhǔn)備就緒,Hub也會(huì)把這個(gè)sessionid響應(yīng)給客戶端,接下來(lái)的客戶端的代碼發(fā)來(lái)的請(qǐng)求都會(huì)被Hub轉(zhuǎn)發(fā)給這個(gè)代理節(jié)點(diǎn)來(lái)執(zhí)行。這里實(shí)際上整個(gè)流程與Selenium1.0的原理是一樣的,只是多了Hub這一層。

DesiredCapabilities capability = new DesiredCapabilities();
capability.setBrowserName("chrome");
capability.setPlatform(Platform.WINDOWS);
try {
WebDriver driver = new RemoteWebDriver(new URL("http://192.168.0.245:4444/wd/hub"), capability);
driver.get("http://www.baidu.com");
driver.quit();
} catch (MalformedURLException e) {
  e.printStackTrace();
}

根據(jù)上面代碼會(huì)發(fā)現(xiàn)node節(jié)點(diǎn)2執(zhí)行,如果把setBrowserName()方法里面的傳參改了firefox就會(huì)在node節(jié)點(diǎn)1執(zhí)行。表示Selenium Grid環(huán)境搭建完成。

另外客戶端還可以直接使用node節(jié)點(diǎn)運(yùn)行代碼,這樣的方式就與selenium1.0一模一樣啦,看下面代碼:

DesiredCapabilities capability = new DesiredCapabilities();
capability.setBrowserName("chrome");
capability.setPlatform(Platform.WINDOWS);
try {
WebDriver  driver = new RemoteWebDriver(new URL("http://192.168.0.183:6666/wd/hub"), capability);
driver.get("http://www.baidu.com");
driver.quit();
} catch (MalformedURLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

要注意實(shí)例化driver對(duì)象時(shí)填寫的服務(wù)地址是node節(jié)點(diǎn)的地址,這樣就會(huì)直接去節(jié)點(diǎn)運(yùn)行;還有一個(gè)注意的就是DesiredCapabilities配置,一定要設(shè)置該節(jié)點(diǎn)運(yùn)行參數(shù)正確的瀏覽器、瀏覽器版本、系統(tǒng),如果參數(shù)不對(duì)都會(huì)出現(xiàn)報(bào)錯(cuò)。

Selenium RC

Selenium RC(Remote Control)是Selenium家族的核心部分;
支持多種不同語(yǔ)言編寫的自動(dòng)化測(cè)試腳本,通過(guò)Selenium RC的服務(wù)器作為代理服務(wù)器去訪問(wèn)應(yīng)用,從而達(dá)到測(cè)試目的;

selenium RC 分為 Client Libraries 和 Selenium Server;
Client Libraries 庫(kù)主要主要用于編寫測(cè)試腳本,用來(lái)控制 selenium Server 的庫(kù)。
Selenium Server 負(fù)責(zé)控制瀏覽器行為;

總的來(lái)說(shuō),Selenium Server 主要包括 3 個(gè)部分
Launcher、Http Proxy、Core。

其中 Selenium Core 是被 Selenium Server 嵌入到瀏覽器頁(yè)面中的。
其實(shí) Selenium Core
就是一堆 JS 函數(shù)的集合,就是通過(guò)這些 JS 函數(shù),我們才可以實(shí)現(xiàn)用程序?qū)g覽器進(jìn)行操作。

Launcher 用于啟動(dòng)瀏覽器,把 selnium Core 加載到瀏覽器頁(yè)面當(dāng)中,并把瀏覽器的代理設(shè)置為 Selenium Server 的Http Proxy。

Selenium 2.0

Selenium 2.0 是把 WebDriver 加入到了這個(gè)家族中;
簡(jiǎn)單用公式表示為:

selenium 2.0 = selenium 1.0 + WebDriver

在 selenium 2.0 中主推的是 WebDriver ,WebDriver 是 selenium RC 的替代品;
那么 selenium RC 與 webdriver 主要有什么區(qū)別呢?

Selenium RC 在瀏覽器中運(yùn)行 JavaScript 應(yīng)用,使用瀏覽器內(nèi)置的 JavaScript 翻譯器來(lái)翻譯和執(zhí)行
selenium 命令)。

WebDriver 通過(guò)原生瀏覽器支持或者瀏覽器擴(kuò)展直接控制瀏覽器。

WebDriver 針對(duì)各個(gè)瀏覽器而開(kāi)發(fā),取代了嵌入到被測(cè) Web 應(yīng)用中的 JavaScript。與瀏覽器的緊密集成支持創(chuàng)建更高級(jí)的測(cè)試,避免了JavaScript 安全模型導(dǎo)致的限制。
除了來(lái)自瀏覽器廠商的支持,WebDriver 還利用操作系統(tǒng)級(jí)的調(diào)用模擬用戶輸入。

WebDriver原理

WebDriver是按照Server-Client的設(shè)計(jì)模式設(shè)計(jì)的;

Server端就是Remote Server,可以是任意的瀏覽器;
當(dāng)我們的腳本啟動(dòng)瀏覽器后,該瀏覽器就是Remote Server,職責(zé)就是等待Client發(fā)送請(qǐng)求并做出響應(yīng);

Client端簡(jiǎn)單理解就是測(cè)試代碼;
腳本的行為是以http請(qǐng)求的方式發(fā)送給被測(cè)試的瀏覽器,瀏覽器接受請(qǐng)求,執(zhí)行相應(yīng)操作,并在response中返回執(zhí)行狀態(tài),返回等信息;

WebDriver的工作流程:

  • WebDriver啟動(dòng)目標(biāo)瀏覽器,并綁定到指定端口;啟動(dòng)的瀏覽器實(shí)例將作為WebDriver的Remote Server;
  • Client端通過(guò)CommandExcuter發(fā)送HTTPRequest給Remote Server 的偵聽(tīng)端口;
  • Remote Server需要依賴原生的瀏覽器組件來(lái)轉(zhuǎn)化瀏覽器的native調(diào)用;

小結(jié)

斷斷續(xù)續(xù)的看了這本書很久很久,還是因?yàn)樽约簯校?/p>

看完這本書,對(duì)selenium的api至少留個(gè)印象,最重要的還是找控件的8種方式,一般如果Jb去面試,也會(huì)問(wèn)這個(gè)問(wèn)題,當(dāng)然也會(huì)有selenium的原理以及2.0跟1.0的區(qū)別,不過(guò)后面幾點(diǎn)只是做簡(jiǎn)單了解,不做任何評(píng)價(jià);

可別忘記了,selenium對(duì)于是可以獲取JS加載后的頁(yè)面內(nèi)容,如果想爬一個(gè)網(wǎng)頁(yè),而且內(nèi)容是JS加載的,如果不想研究JS,就用selenium吧,賊好用的玩意;

立個(gè)flag,之前太忙了,導(dǎo)致一個(gè)月沒(méi)更新博客,感覺(jué)人都廢了,因此立一個(gè),一周更新一篇的flag,看能堅(jiān)持多久吧~

最后,貼這個(gè)這本書電子版的鏈接:

https://pan.baidu.com/s/1b8T6Iq?errno=0&errmsg=Auth%20Login%20Sucess&&bduss=&ssnerror=0&traceid=

這里說(shuō)明下,電子版跟實(shí)體書還是有點(diǎn)不一樣,如果只是用來(lái)查api,看這篇文章或者看電子書就好了,主要的內(nèi)容沒(méi)什么變化;

謝謝大家~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容