Golang使用selenium操作Chrome
1.需求
解決自動化登錄的問題,順便可以解決爬蟲問題。
2.基本概念
selenium: Selenium 是一個用於 Web 應用程序測試的工具,Selenium 測試直接自動運行在瀏覽器中,就像真正的用戶在手工操作一樣。
webdriver: chromeDriver是谷歌爲網站開發人員提供的自動化測試工具。
selenium和webdriver其實原來是兩個不同的開源項目,後來selenium2就把selenium1(RC)和webdriver合併到一起,還是用selenium的名字,但是實現方式和協議基本沿用的是webdriver的。可以看做一樣。
簡單來說,需要通過chromedriver調用chrome,進行模擬瀏覽器操作。
3.安裝
-
下載chromedriver。chrome和chromedriver 要版本對應,chromedriver版本下載,放到相應目錄。
-
下載golang代碼包。selenium的golang代碼包
go get -t -d github.com/tebeka/selenium
- 注意點: 本人Win10,調用chrome報錯。主要涉及3個點: 1.將chrome添加至環境變量path中,可以通過cmd直接運行chrome.exe確定是否運行成功;2.修改程序的權限,讓執行賬戶獲取所有權,避免權限提升 3.如果提示沒有找到google-chrome時,拷貝一份chrome.exe重命名爲google.chrome.exe。
4.源碼分析
4.1 selenium執行流程分析
經過我的理解和思考,我認爲selenium 主要運行模式如下(個人理解,僅供參考)。
程序需要調用selenium庫執行相應的函數, 後臺調用chrome瀏覽器,然後將操作元素的請求給下方的瀏覽器驅動。瀏覽器驅動再轉發這個請求給瀏覽器。最後將結果返回。
4.2 源碼文件分析selenium-golangdoc
因爲selenium源碼包不是很大,同時因爲是chrome進行實戰,所以我將源碼進行刪減然後進行添加註釋。godoc包應該沒有寫完整,比如webdriver,webelement只講了接口,並沒有將實現細節,我們可以根據selenium操作進行腦補。可以參考python中selenium中的實現,比如自動化測試。
先繼續畫個圖進行包的講解的吧。
源碼簡略分析:
// 第一類 雜項
//刪除會話
func DeleteSession(urlPrefix, id string) error
//開啓關閉debug調試
func SetDebug(debug bool)
//設置代理
func (c Capabilities) AddProxy(p Proxy)
//設置日誌級別
func (c Capabilities) SetLogLevel(typ log.Type, level log.Level)
// 第二類 seleium後臺服務
//服務實例的可選項
type ServiceOption func(*Service) error
//添加chrome路徑信息,返回的是serviceOption類型
func ChromeDriver(path string) ServiceOption
//服務的結構體,包含隱藏類型
type Service struct {
// contains filtered or unexported fields
}
//啓動chrome瀏覽器的服務器,返回service類型指針
func NewChromeDriverService(path string, port int, opts ...ServiceOption) (*Service, error)
//關閉服務,記得defer關閉
func (s *Service) Stop() error
// 第三類 chrome操作相關
//設置瀏覽器兼容性,map類型,比如chrome瀏覽器兼容性。
//caps := selenium.Capabilities{"browserName": "chrome"}
type Capabilities map[string]interface{}
//通過調用函數添加chrome兼容性
func (c Capabilities) AddChrome(f chrome.Capabilities)
//啓動webdriver實例
func NewRemote(capabilities Capabilities, urlPrefix string) (WebDriver, error)
//通過WebDriver接口可以看出具體頁面的實現的方法,是接口,接口裏面是實現的方法。
type WebDriver interface {
//返回服務器環境的版本信息
// Status returns various pieces of information about the server environment.
Status() (*Status, error)
//創建新的session
// NewSession starts a new session and returns the session ID.
NewSession() (string, error)
//創建新的session(已廢棄)
// SessionId returns the current session ID
// Deprecated: This identifier is not Go-style correct. Use SessionID
// instead.
SessionId() string
//獲取新的ssionid
// SessionID returns the current session ID.
SessionID() string
//切換session
// SwitchSession switches to the given session ID.
SwitchSession(sessionID string) error
//返回兼容性
// Capabilities returns the current session's capabilities.
Capabilities() (Capabilities, error)
//設置異步腳本執行時間
// SetAsyncScriptTimeout sets the amount of time that asynchronous scripts
// are permitted to run before they are aborted. The timeout will be rounded
// to nearest millisecond.
SetAsyncScriptTimeout(timeout time.Duration) error
//設置等待搜索元素的時間,目的: 如果頁面結果返回較慢,就需要等待頁面內容完整返回,然後再進行頁面元素操作。
// SetImplicitWaitTimeout sets the amount of time the driver should wait when
// searching for elements. The timeout will be rounded to nearest millisecond.
SetImplicitWaitTimeout(timeout time.Duration) error
//設置等待頁面的時間
// SetPageLoadTimeout sets the amount of time the driver should wait when
// loading a page. The timeout will be rounded to nearest millisecond.
SetPageLoadTimeout(timeout time.Duration) error
//設置會話退出
// Quit ends the current session. The browser instance will be closed.
Quit() error
//獲取現在窗口句柄,一串序號,打開一個窗口一個句柄
// CurrentWindowHandle returns the ID of current window handle.
CurrentWindowHandle() (string, error)
//獲取現在所有打開窗口句柄,獲取所有窗口句柄
// WindowHandles returns the IDs of current open windows.
WindowHandles() ([]string, error)
//返回當前頁面連接的URL
// CurrentURL returns the browser's current URL.
CurrentURL() (string, error)
//獲取當前頁面的標題
// Title returns the current page's title.
Title() (string, error)
//返回當前頁面的所有內容
// PageSource returns the current page's source.
PageSource() (string, error)
//關閉現在的窗口
// Close closes the current window.
Close() error
//切換frame,frame裏面內嵌一個完整html,如果操作裏面的內容需要進入iframe中。switchframe(nil),返回到頂層
// SwitchFrame switches to the given frame. The frame parameter can be the
// frame's ID as a string, its WebElement instance as returned by
// GetElement, or nil to switch to the current top-level browsing context.
SwitchFrame(frame interface{}) error
切換windows到指定窗口
// SwitchWindow switches the context to the specified window.
SwitchWindow(name string) error
//關閉窗口
// CloseWindow closes the specified window.
CloseWindow(name string) error
//設置最大化窗口
// MaximizeWindow maximizes a window. If the name is empty, the current
// window will be maximized.
MaximizeWindow(name string) error
//設置窗口尺寸
// ResizeWindow changes the dimensions of a window. If the name is empty, the
// current window will be maximized.
ResizeWindow(name string, width, height int) error
//通過url導航至相應界面。主要選項,就是打開url地址。
// Get navigates the browser to the provided URL.
Get(url string) error
//向前翻
// Forward moves forward in history.
Forward() error
//向後翻
// Back moves backward in history.
Back() error
//刷新
// Refresh refreshes the page.
Refresh() error
//查找定位一個html元素。
// FindElement finds exactly one element in the current page's DOM.
FindElement(by, value string) (WebElement, error)
//查找定位多個的html元素
// FindElement finds potentially many elements in the current page's DOM.
FindElements(by, value string) ([]WebElement, error)
//獲取當前焦點元素
// ActiveElement returns the currently active element on the page.
ActiveElement() (WebElement, error)
//解碼元素響應
// DecodeElement decodes a single element response.
DecodeElement([]byte) (WebElement, error)
//解碼多個元素響應
// DecodeElements decodes a multi-element response.
DecodeElements([]byte) ([]WebElement, error)
//獲取所有cookie
// GetCookies returns all of the cookies in the browser's jar.
GetCookies() ([]Cookie, error)
//獲取指定cookie
// GetCookie returns the named cookie in the jar, if present. This method is
// only implemented for Firefox.
GetCookie(name string) (Cookie, error)
//添加cookie到jar
// AddCookie adds a cookie to the browser's jar.
AddCookie(cookie *Cookie) error
//刪除所有cookie
// DeleteAllCookies deletes all of the cookies in the browser's jar.
DeleteAllCookies() error
//刪除指定cookie
// DeleteCookie deletes a cookie to the browser's jar.
DeleteCookie(name string) error
//敲擊鼠標按鈕
// Click clicks a mouse button. The button should be one of RightButton,
// MiddleButton or LeftButton.
Click(button int) error
//雙擊鼠標按鈕
// DoubleClick clicks the left mouse button twice.
DoubleClick() error
//按下鼠標
// ButtonDown causes the left mouse button to be held down.
ButtonDown() error
//擡起鼠標
// ButtonUp causes the left mouse button to be released.
ButtonUp() error
//發送更改到活動元素(已丟棄)
// SendModifier sends the modifier key to the active element. The modifier
// can be one of ShiftKey, ControlKey, AltKey, MetaKey.
//
// Deprecated: Use KeyDown or KeyUp instead.
SendModifier(modifier string, isDown bool) error
//將按鍵順序序列發送到活動元素
// KeyDown sends a sequence of keystrokes to the active element. This method
// is similar to SendKeys but without the implicit termination. Modifiers are
// not released at the end of each call.
KeyDown(keys string) error
//釋放發送的元素
// KeyUp indicates that a previous keystroke sent by KeyDown should be
// release
KeyUp(keys string) error
//拍攝快照
// Screenshot takes a screenshot of the browser window.
Screenshot() ([]byte, error)
//日誌抓取
// Log fetches the logs. Log types must be previously configured in the
// capabilities.
//
// NOTE: will return an error (not implemented) on IE11 or Edge drivers.
Log(typ log.Type) ([]log.Message, error)
//解除警報
// DismissAlert dismisses current alert.
DismissAlert() error
//接受警報
// AcceptAlert accepts the current alert.
AcceptAlert() error
//返回現在警報內容
// AlertText returns the current alert text.
AlertText() (string, error)
//發送警報內容
// SetAlertText sets the current alert text.
SetAlertText(text string) error
//執行腳本
// ExecuteScript executes a script.
ExecuteScript(script string, args []interface{}) (interface{}, error)
//異步執行腳本
// ExecuteScriptAsync asynchronously executes a script.
ExecuteScriptAsync(script string, args []interface{}) (interface{}, error)
//執行源腳本
// ExecuteScriptRaw executes a script but does not perform JSON decoding.
ExecuteScriptRaw(script string, args []interface{}) ([]byte, error)
//異步執行源腳本
// ExecuteScriptAsyncRaw asynchronously executes a script but does not
// perform JSON decoding.
ExecuteScriptAsyncRaw(script string, args []interface{}) ([]byte, error)
//等待條件爲真
// WaitWithTimeoutAndInterval waits for the condition to evaluate to true.
WaitWithTimeoutAndInterval(condition Condition, timeout, interval time.Duration) error
//等待時間
// WaitWithTimeout works like WaitWithTimeoutAndInterval, but with default polling interval.
WaitWithTimeout(condition Condition, timeout time.Duration) error
//等待
//Wait works like WaitWithTimeoutAndInterval, but using the default timeout and polling interval.
Wait(condition Condition) error
}
//對相關元素進行後續執行,接口類型,裏面是實現方法
type WebElement interface {
// click選中的元素
// Click clicks on the element.
Click() error
//發送數據到選中元素
// SendKeys types into the element.
SendKeys(keys string) error
//提交按鈕
// Submit submits the button.
Submit() error
//清空按鈕
// Clear clears the element.
Clear() error
//移動元素到相應的座標
// MoveTo moves the mouse to relative coordinates from center of element, If
// the element is not visible, it will be scrolled into view.
MoveTo(xOffset, yOffset int) error
// 查找子元素
// FindElement finds a child element.
FindElement(by, value string) (WebElement, error)
//查找多個子元素
// FindElement finds multiple children elements.
FindElements(by, value string) ([]WebElement, error)
//返回標籤名稱
// TagName returns the element's name.
TagName() (string, error)
//返回元素內容
// Text returns the text of the element.
Text() (string, error)
//元素被選中返回真
// IsSelected returns true if element is selected.
IsSelected() (bool, error)
//如果元素啓用返回真
// IsEnabled returns true if the element is enabled.
IsEnabled() (bool, error)
//如果元素顯示返回真
// IsDisplayed returns true if the element is displayed.
IsDisplayed() (bool, error)
//獲取元素的名稱
// GetAttribute returns the named attribute of the element.
GetAttribute(name string) (string, error)
//範圍元素的位置
// Location returns the element's location.
Location() (*Point, error)
//滾動後返回元素的位置
// LocationInView returns the element's location once it has been scrolled
// into view.
LocationInView() (*Point, error)
//返回元素的大小
// Size returns the element's size.
Size() (*Size, error)
//返回css優先級
// CSSProperty returns the value of the specified CSS property of the
// element.
CSSProperty(name string) (string, error)
//返回屬性滾動的快照
// Screenshot takes a screenshot of the attribute scroll'ing if necessary.
Screenshot(scroll bool) ([]byte, error)
}
4.3 說明
-
可能需要了解Html,Css,JavaScript的基本概念。菜鳥教程
-
可能需要了解Dom結構。html Dom
-
可能需要了解XPATH,CSSSelector 。Xpath和CSS選擇器的使用詳解
-
可以參考python中selenium操作實現,畢竟python案例多。selenim操作日常記錄
- 參考學習白月黑羽的自動化教程,b站視頻同步,強推。自動化測試
5.基礎操作
操作之前,先分享一個快速獲取CSS Selector和xpath的方法。
大家用chrome瀏覽器訪問網頁,按F12後,點擊調試頁左上角Elements箭頭,然後鼠標移動到目的位置,即可顯示頁面對應的HTML 元素。
右鍵選中的元素,選擇copy,此時可以根據直接選擇ID,CLASS,CSS Selector或Xpath的地址.
參考別人的案例,寫了幾個小案例,僅供參考。亮代碼吧。
-
打開百度,自動搜索。
package main import ( "fmt" "github.com/tebeka/selenium" "time" ) const ( //設置常量 分別設置chromedriver.exe的地址和本地調用端口 seleniumPath = `H:\webdriver\chromedriver.exe` port = 9515 ) func main() { //1.開啓selenium服務 //設置selium服務的選項,設置爲空。根據需要設置。 ops := []selenium.ServiceOption{} service, err := selenium.NewChromeDriverService(seleniumPath, port, ops...) if err != nil { fmt.Printf("Error starting the ChromeDriver server: %v", err) } //延遲關閉服務 defer service.Stop() //2.調用瀏覽器 //設置瀏覽器兼容性,我們設置瀏覽器名稱爲chrome caps := selenium.Capabilities{ "browserName": "chrome", } //調用瀏覽器urlPrefix: 測試參考:DefaultURLPrefix = "http://127.0.0.1:4444/wd/hub" wd, err := selenium.NewRemote(caps, "http://127.0.0.1:9515/wd/hub") if err != nil { panic(err) } //延遲退出chrome defer wd.Quit() //3.對頁面元素進行操作 //獲取百度頁面 if err := wd.Get("https://www.baidu.com/"); err != nil { panic(err) } //找到百度輸入框id we, err := wd.FindElement(selenium.ByID, "kw") if err != nil { panic(err) } //向輸入框發送“” err = we.SendKeys("天下第一") if err != nil { panic(err) } //找到百度提交按鈕id we, err = wd.FindElement(selenium.ByID, "su") if err != nil { panic(err) } //點擊提交 err = we.Click() if err != nil { panic(err) } //睡眠20秒後退出 time.Sleep(20 * time.Second) }
-
內嵌iframe切換。
package main import ( "fmt" "github.com/tebeka/selenium" "time" ) const ( //設置常量 分別設置chromedriver.exe的地址和本地調用端口 seleniumPath = `H:\webdriver\chromedriver.exe` port = 9515 ) func main() { //1.開啓selenium服務 //設置selium服務的選項,設置爲空。根據需要設置。 ops := []selenium.ServiceOption{} service, err := selenium.NewChromeDriverService(seleniumPath, port, ops...) if err != nil { fmt.Printf("Error starting the ChromeDriver server: %v", err) } //延遲關閉服務 defer service.Stop() //2.調用瀏覽器 //設置瀏覽器兼容性,我們設置瀏覽器名稱爲chrome caps := selenium.Capabilities{ "browserName": "chrome", } //調用瀏覽器urlPrefix: 測試參考:DefaultURLPrefix = "http://127.0.0.1:4444/wd/hub" wd, err := selenium.NewRemote(caps, "http://127.0.0.1:9515/wd/hub") if err != nil { panic(err) } //延遲退出chrome defer wd.Quit() //3.對頁面元素進行操作 //獲取測試網頁 if err := wd.Get("http://cdn1.python3.vip/files/selenium/sample2.html"); err != nil { panic(err) } //4.切換到相應的frame上去 //wd.SwitchFrame(可以id或者frame獲取的webelement),我們使用二種方式分別實現。 //4.1 通過frame的id查找 此時id=frame1 /* err = wd.SwitchFrame("frame1") if err != nil { panic(err) } // 此時定位到iframe的html中,再像使用bycssselector即可 // 因爲animal包含多個對象,我們使用findelements wes, err := wd.FindElements(selenium.ByCSSSelector, ".animal") if err != nil { panic(err) } //循環獲取每個元素的信息 for _,we := range wes { text, err := we.Text() if err != nil { panic(err) } fmt.Println(text) } */ //4.2 frame獲取的webelement,通過切換webelement實現。 // 找到ifname的webelement對象 element, err := wd.FindElement(selenium.ByCSSSelector, "#frame1") // 不同的獲取element方式 //element, err := wd.FindElement(selenium.ByCSSSelector, `iframe[name="innerFrame"]`) if err != nil { panic(err) } //切換到iframe中 err = wd.SwitchFrame(element) if err != nil { panic(err) } // 此時定位到iframe的html中,再像使用bycssselector即可 // 因爲animal包含多個對象,我們使用findelements wes, err := wd.FindElements(selenium.ByCSSSelector, ".animal") if err != nil { panic(err) } //循環獲取每個元素的信息 for _, we := range wes { text, err := we.Text() if err != nil { panic(err) } fmt.Println(text) } //5.切換回頂層frame,因爲切換中frame中是不能操作外層值元素的,所以我們要切換出來 //frame=nil是切換回頂層frame err = wd.SwitchFrame(nil) if err != nil { panic(err) } //根據class name選擇元素 we, err := wd.FindElement(selenium.ByCSSSelector, ".baiyueheiyu") if err != nil { panic(err) } //查看頂層元素的內容 fmt.Println(we.Text()) //睡眠20秒後退出 time.Sleep(20 * time.Second) }
-
多windows切換。
package main import ( "fmt" "github.com/tebeka/selenium" "strings" "time" ) const ( //設置常量 分別設置chromedriver.exe的地址和本地調用端口 seleniumPath = `H:\webdriver\chromedriver.exe` port = 9515 ) func main() { //1.開啓selenium服務 //設置selenium服務的選項,設置爲空。根據需要設置。 ops := []selenium.ServiceOption{} service, err := selenium.NewChromeDriverService(seleniumPath, port, ops...) if err != nil { fmt.Printf("Error starting the ChromeDriver server: %v", err) } //延遲關閉服務 defer service.Stop() //2.調用瀏覽器實例 //設置瀏覽器兼容性,我們設置瀏覽器名稱爲chrome caps := selenium.Capabilities{ "browserName": "chrome", } //調用瀏覽器urlPrefix: 測試參考:DefaultURLPrefix = "http://127.0.0.1:4444/wd/hub" wd, err := selenium.NewRemote(caps, "http://127.0.0.1:9515/wd/hub") if err != nil { panic(err) } //延遲退出chrome defer wd.Quit() //3.打開多頁面chrome實例 //目前就想到兩種方式可以打開, //第一種就是頁面中有url連接,通過click()方式打開 //第二種方式就是通過腳本方式打開。wd.ExecuteScript if err := wd.Get("http://cdn1.python3.vip/files/selenium/sample3.html"); err != nil { panic(err) } //第一種方式,找到頁面中的url地址,進行頁面跳轉 we, err := wd.FindElement(selenium.ByTagName, "a") if err != nil { panic(err) } we.Click() //第二種方式,通過運行通用的js腳本打開新窗口,因爲我們暫時不需要操作獲取的結果,所有不獲取返回值。 wd.ExecuteScript(`window.open("https://www.qq.com", "_blank");`, nil) wd.ExecuteScript(`window.open("https://www.runoob.com/jsref/obj-window.html", "_blank");`, nil) //這一行是發送警報信息,寫這一行的目的,主要是看當前主窗口是哪一個 wd.ExecuteScript(`window.alert(location.href);`, nil) //查看當前窗口的handle值 handle, err := wd.CurrentWindowHandle() if err != nil { panic(err) } fmt.Println(handle) fmt.Println("--------------------------") //查看所有網頁的handle值 handles, err := wd.WindowHandles() if err != nil { panic(err) } for _, handle := range handles { fmt.Println(handle) } fmt.Println("--------------------------") //4.跳轉到指定的網頁 //我們雖然打開了多個頁面,但是我們當前的handle值,還是第一個頁面的,我們要想辦法搞定它。 //記得保存當前主頁面的handle值 //mainhandle := handle //通過判斷條件進行相應的網頁 //獲取所有handle值 handles, err = wd.WindowHandles() if err != nil { panic(err) } //遍歷所有handle值,通過url找到目標頁面,判斷相等時,break出來,就是停到相應的頁面了。 for _, handle := range handles { wd.SwitchWindow(handle) url, _ := wd.CurrentURL() if strings.Contains(url, "qq.com") { break } } //查看此頁面的handle handle, err = wd.CurrentWindowHandle() if err != nil { panic(err) } fmt.Println(handle) //這一行是發送警報信息,寫這一行的目的,主要是看當前主窗口是哪一個 wd.ExecuteScript(`window.alert(location.href);`, nil) //切換回第一個頁面 //wd.SwitchWindow(mainhandle) //睡眠20秒後退出 time.Sleep(20 * time.Second) }
-
單選,多選框操作。
package main import ( "fmt" "github.com/tebeka/selenium" "time" ) const ( //設置常量 分別設置chromedriver.exe的地址和本地調用端口 seleniumPath = `H:\webdriver\chromedriver.exe` port = 9515 ) func main() { //1.開啓selenium服務 //設置selenium服務的選項,設置爲空。根據需要設置。 ops := []selenium.ServiceOption{} service, err := selenium.NewChromeDriverService(seleniumPath, port, ops...) if err != nil { fmt.Printf("Error starting the ChromeDriver server: %v", err) } //延遲關閉服務 defer service.Stop() //2.調用瀏覽器實例 //設置瀏覽器兼容性,我們設置瀏覽器名稱爲chrome caps := selenium.Capabilities{ "browserName": "chrome", } //調用瀏覽器urlPrefix: 測試參考:DefaultURLPrefix = "http://127.0.0.1:4444/wd/hub" wd, err := selenium.NewRemote(caps, "http://127.0.0.1:9515/wd/hub") if err != nil { panic(err) } //延遲退出chrome defer wd.Quit() // 3單選radio,多選checkbox,select框操作(功能待完善,https://github.com/tebeka/selenium/issues/141) if err := wd.Get("http://cdn1.python3.vip/files/selenium/test2.html"); err != nil { panic(err) } //3.1操作單選radio we, err := wd.FindElement(selenium.ByCSSSelector, `#s_radio > input[type=radio]:nth-child(3)`) if err != nil { panic(err) } we.Click() //3.2操作多選checkbox //刪除默認checkbox we, err = wd.FindElement(selenium.ByCSSSelector, `#s_checkbox > input[type=checkbox]:nth-child(5)`) if err != nil { panic(err) } we.Click() //選擇選項 we, err = wd.FindElement(selenium.ByCSSSelector, `#s_checkbox > input[type=checkbox]:nth-child(1)`) if err != nil { panic(err) } we.Click() we, err = wd.FindElement(selenium.ByCSSSelector, `#s_checkbox > input[type=checkbox]:nth-child(3)`) if err != nil { panic(err) } we.Click() //3.3 select多選 //刪除默認選項 //選擇默認項 we, err = wd.FindElement(selenium.ByCSSSelector, `#ss_multi > option:nth-child(3)`) if err != nil { panic(err) } we.Click() we, err = wd.FindElement(selenium.ByCSSSelector, `#ss_multi > option:nth-child(2)`) if err != nil { panic(err) } we.Click() //睡眠20秒後退出 time.Sleep(20 * time.Second) }
結束語
個人理解,僅供參考。如有錯誤,歡迎指正。
一直在路上,默默前行。