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中的实现,比如[自动化测试](http://www.python3.vip/tut/auto/selenium/01/)。
先继续画个图进行包的讲解的吧。
源码简略分析:
// 第一类 杂项
//删除会话
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)
}
```
结束语
个人理解,仅供参考。如有错误,欢迎指正。
一直在路上,默默前行。