Golang实战教程-做一个农场游戏自动化操作

Golang实战教程-做一个农场游戏自动化操作

首页休闲益智输出牧场手机版更新时间:2024-06-03

本文为技术文章,阅读对象为对Golang感兴趣的朋友。

游戏:XX农场(H5)。

功能:使用Golang模拟登陆,并自动完成“收茶”和“浇水”操作。

游戏界面

这是一个H5的农场游戏,很常见的一个游戏。

游戏需要让玩家先收茶然后再浇水。

做一次收茶浇水操作之后,需要等一定的时间后,再做重复做这个动作。

我们在这里不讨论这个游戏项目,或者游戏体验等问题。

我们只关于如何使用Golang自动完成这些操作。

模拟登录

要模拟登录,就需要先分析这个游戏登录需要哪些条件。

H5游戏,那当然使用Chrome这个神器咯。

登录界面

我们从登录界面可以看到,这个表单主要有三个控件:登录账户、登录密码和记住账号密码。

我们使用Chrome来看看这个表单的具体情况。

从HTML代码中,我们可以发现,有两个隐藏域,这个表单控件的具体情况如下:

  1. __VIEWSTATE
  2. __VIEWSTATEGENERATOR
  3. tb_loguid
  4. tb_pwd
  5. checkboxDefault
  6. Button1

登录表单是使用Post方式提交,提交地址为:./login.aspx。

换句话说,我们模拟登录时,需要向对方服务器提交这些数据。

用户名和密码这两个参数,是我们自己填写的。

__VIEWSTATE和__VIEWSTATEGENERATOR这两个参数是服务器提供的,那么怎么处理呢?

方式倒也简单。先直接使用Golang访问这个登录页面,然后从页面中获取上面说的两个参数的值。

获取页面参数,我们使用goquery这个库,很酷的一个库,Golang仿jQuery的一个库,很好用。

goquery源码地址:github.com/PuerkitoBio/goquery

本程序,除了goquery这个库以外,其他的都使用标准库。

然后我们为了不让风控系统(如果有多话)那么容易发现我们是模拟登录,那么我们就把浏览器相关的参数都给到服务器。

登录之后,我使用标准库中的Cookiejar来维护Cookie。

游戏主页地址做了隐藏。

废话不多说,上代码:

const HomePageURL = "http://www.xxxxxxx.cn/wap/" // 首页 const LoginURL = HomePageURL "login.aspx" // 登录页面 const UserHome = HomePageURL "userindex.aspx" // 用户中心 const TianYuanURL = HomePageURL "tea.aspx" // 田园页面 const UserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" const Accept = "text/html,application/xhtml xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" const AcceptLanguage = "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6,mt;q=0.5,zh-TW;q=0.4,la;q=0.3,da;q=0.2,pl;q=0.1,lb;q=0.1,de;q=0.1" const ContentType = "application/x-www-form-urlencoded" // 请求客户端 type HttpClient struct { // 公共页面参数 ViewState string ViewStateGenerator string gCurCookies []*http.Cookie gCurCookieJar *cookiejar.Jar myHttpClient *http.Client // 请求客户端 } // 登录,获取Cookie func (h *HttpClient) Login(userName, password string) error { h.GetPageparams() params := url.Values{} params.Add("__VIEWSTATE", h.ViewState) params.Add("__VIEWSTATEGENERATOR", h.ViewStateGenerator) params.Add("tb_loguid", userName) params.Add("tb_pwd", password) params.Add("checkboxDefault", "0") params.Add("Button1", "确认登录") postParams := ioutil.NopCloser(strings.NewReader(params.Encode())) req, err := http.NewRequest("POST", LoginURL, postParams) if err != nil { return err } req.Header.Add("Content-Type", ContentType) req.Header.Add("Accept", Accept) req.Header.Add("Accept-Encoding", "gzip, deflate") req.Header.Add("Accept-Language", AcceptLanguage) req.Header.Add("Cache-Control", "max-age=0") req.Header.Add("Connection", "keep-alive") req.Header.Add("Content-Length", "217") req.Header.Add("DNT", "1") req.Header.Add("Origin", "http://www.xxxxxxx.cn") req.Header.Add("Referer", LoginURL) req.Header.Add("Upgrade-Insecure-Requests", "1") req.Header.Add("User-Agent", UserAgent) resp, err := h.myHttpClient.Do(req) if err != nil { return err } h.gCurCookies = h.gCurCookieJar.Cookies(req.URL) defer resp.Body.Close() return nil } // 获取页面及Cookie参数 func (h *HttpClient) GetPageParams() error { _myCookieJar, _ := cookiejar.New(nil) h.gCurCookieJar = _myCookieJar h.myHttpClient = &http.Client{ CheckRedirect: nil, Jar: h.gCurCookieJar, } if err := h.getViewState(LoginURL); err != nil { return err } return nil } // 获取页面参数 func (h *HttpClient) getViewState(url string) error { req, err := http.NewRequest("GET", url, nil) if err != nil { return err } res, err := h.myHttpClient.Do(req) if err != nil { return err } defer res.Body.Close() doc, err := goquery.NewDocumentFromReader(res.Body) if err != nil { return err } doc.Find("#__VIEWSTATE").Each(func(i int, s *goquery.Selection) { h.ViewState = s.AttrOr("value", "") }) doc.Find("#__VIEWSTATEGENERATOR").Each(func(i int, s *goquery.Selection) { h.ViewStateGenerator = s.AttrOr("value", "") }) if url == LoginURL { h.gCurCookies = h.gCurCookieJar.Cookies(req.URL) } return nil }

为了可以支持多账户,我们将用户名和密码作为参数传递给登录方法(Login)。

GetPageParams这个方法,用来初始化cookiejar这个对象。

getViewState这个方法,用来获取页面的公共参数。

这几行代码,可以完成模拟登录的操作。

golang发起http请求,使用的是标准库:net/http。

如果对这个库还不属性的朋友,可以查阅一下官方的文档。

大家可以使用单元测试进行尝试。

如果做单元测试,这里就不展开了,搜索一下,网上相关的资料很多。

退出登录

因为要支持多账户模拟,所以我们的程序中需要支持退出功能。

还是使用Chrome来分析这个游戏如何处理退出的。

从个人中心,拉到底部,我们发现了“退出登录”的按钮。查看源码,发现退出使用的Get方法。地址为:userindex.aspx?act=out。

那就更简单了,代码如下:

// 退出登录 func (h *HttpClient) Logout() error { req, err := http.NewRequest("GET", UserHome "?act=out", nil) if err != nil { return err } res, err := h.myHttpClient.Do(req) if err != nil { return err } defer res.Body.Close() if res.StatusCode != 200 { return errors.New("退出登录失败,请检查后重新操作") } return nil }

登录和退出完成了,我们现在开始干正事。

模拟游戏操作

对于这个游戏,主要的游戏操作有收茶和浇水。

说明:当前使用的这个账户,还没有种茶,当然界面上看不到茶树。

在收茶之前,我们需要先检查,是否有茶可收。

还是老规矩,分析页面代码。我们需要搞清楚收茶的动作是如何提交到服务器的。

我们在导航中点击“田园”按钮,进入游戏主界面。使用Chrome的开发者工具查看源码。

我们从源码中可以看到,这个游戏的开发者将整个页面都放到了一个表单中。

表单提交方法还是Post,表单提交地址为:./tea.aspx。

这个页面也存在着几个隐藏域,而且有的还有值。

我们不要管这几个隐藏域的参数做什么用,但是有一点我们很清楚:

在对游戏进行操作时,服务器需要这些参数。

你需要我给你就好了。

换句话说,我们可以这样理解:

这个游戏的任何操作,都是使用Post方法,向服务器提交相应的数据。

有了这个基础了解,那么我们就知道要怎么操作了。

还是使用getViewState这个方法获取页面的隐藏域参数。

其他操作也是如此。

检查是否可以收茶

我们前面提到过,要收茶,也需要有茶可收。

我们测试账号现在没有可收的茶叶。

如果有茶可以收的时候,服务器会在页面中加入一个HTML节点。这个节点有个Class属性,属性值为“qipao”。

那么我们就在页面中查找,如果出现了这个class值为qipao的节点,那么就可以操作收茶动作。

// 检查是否可以收茶 func (h *HttpClient) checkCanTakeTea() (bool, error) { if err := h.getViewState(TianYuanURL); err != nil { return false, err } req, err := http.NewRequest("GET", TianYuanURL, nil) if err != nil { return false, err } res, err := h.myHttpClient.Do(req) if err != nil { return false, err } defer res.Body.Close() doc, err := goquery.NewDocumentFromReader(res.Body) if err != nil { return false, err } // 如果可以收茶,则页面会有 .qipao 这个节点 if len(doc.Find(".qipao").Nodes) >= 1 { return true, nil } return false, nil }

这个方法还是使用net/http这个库。

模拟收茶操作

我们查看页面中的JS代码,JS代码也不多,作用也比较简单。

主要是页面倒计时处理和游戏动作的操作。

我们分析JS代码,可以知道,收茶动作其实就是向服务器提交这个页面的参数。

有茶可收的情况下,会有一个__EVENTTARGET这个参数的值为LinkButton10。

那么收茶操作的代码如下:

// 收茶操作 func (h *HttpClient) TakeTea() error { if err := h.getViewState(TianYuanURL); err != nil { return err } params := url.Values{} params.Add("__VIEWSTATE", h.ViewState) params.Add("__VIEWSTATEGENERATOR", h.ViewStateGenerator) params.Add("__EVENTTARGET", "LinkButton10") params.Add("__EVENTARGUMENT", "") postParams := ioutil.NopCloser(strings.NewReader(params.Encode())) req, err := http.NewRequest("POST", TianYuanURL, postParams) if err != nil { return err } req.Header.Add("Content-Type", ContentType) resp, err := h.myHttpClient.Do(req) if err != nil { return err } if resp.StatusCode != 200 { return errors.New("收茶操作失败!请检查并重新操作") } defer resp.Body.Close() return nil }

模拟浇水操作

这个游戏的规则为,收茶之后,还需要向茶树浇水。

浇水操作的原理和收茶类似,只是参数值不同。

这样的话,那么事情就简单了。

// 浇水操作 func (h *HttpClient) Watering() error { if err := h.getViewState(TianYuanURL); err != nil { return err } params := url.Values{} params.Add("__VIEWSTATE", h.ViewState) params.Add("__VIEWSTATEGENERATOR", h.ViewStateGenerator) params.Add("__EVENTTARGET", "") params.Add("__EVENTARGUMENT", "") params.Add("Button1", "确认浇水") postParams := ioutil.NopCloser(strings.NewReader(params.Encode())) req, err := http.NewRequest("POST", TianYuanURL, postParams) if err != nil { return err } req.Header.Add("Content-Type", ContentType) resp, err := h.myHttpClient.Do(req) if err != nil { return err } if resp.StatusCode != 200 { return errors.New("浇水操作失败!请检查并重新操作") } defer resp.Body.Close() return nil }

到这里,对这个游戏的主要的操作的模拟都有了。

接下来就是把这几个动作串联起来。

登录->检查是否可以收茶->收茶->浇水->退出登录

这一系列的动作,我们把它看成一个任务。放到一个方法中。

// 执行任务 func (h *HttpClient) TaskRun() error { for _, item := range users { // 1.登录 fmt.Println(tools.Timetostr(0), " 登录...") if err := h.Login(item["userName"], item["password"]); err != nil { return err } // 2.检查是否可以收茶 fmt.Println(Timetostr(0), " 检查...") status, err := h.checkCanTakeTea() if err != nil { return err } // 3.如果可以收茶,则收茶和浇水 if status { if err := h.TakeTea(); err != nil { return err } fmt.Println(Timetostr(0), item["userName"] " 收茶成功...") // 延时2分钟 time.Sleep(time.Duration(120) * time.Second) if err := h.Watering(); err != nil { return err } fmt.Println(Timetostr(0), item["userName"] " 浇水成功...") } // 延时50秒 time.Sleep(time.Duration(50) * time.Second) // 4.操作完毕,退出登录 fmt.Println(Timetostr(0), " 退出...") if err := h.Logout(); err != nil { return err } } return nil } // 获取当前日期时间 func Timetostr(unixTime int64) string { if unixTime == 0 { unixTime = time.Now().Unix() } timer := time.Unix(unixTime, 0) return timer.Format(TimeLayoutLong) }

为了能更好地模拟人工操作,我们在执行完一个操作之后,加了一个延时。

同时将程序执行的情况输出到屏幕。

多账户定时执行

是用Golang做开发,有个很方便的地方就是有强大的标准库。

让Golang定时执行也是很方便的。

代码如下:

// 需要监控的账户 var users []map[string]string = []map[string]string{ { "userName": "18*******44", "password": "********", }, { "userName": "18*******20", "password": "********", }, } func main() { fmt.Println("开始定时任务...") // 10分钟执行一次 for range time.Tick(time.Duration(600) * time.Second) { httpClient := new(HttpClient) if err := httpClient.TaskRun(); err != nil { fmt.Println(err.Error()) } } }

这样,多个账户,定时执行就完成了。


更多精彩内容发布于公众号:代码乾坤 (CoderLand)

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved