Golang实战教程-做一个农场游戏自动化操作
zhezhongyun 2024-12-29 07:15 40 浏览
本文为技术文章,阅读对象为对Golang感兴趣的朋友。
游戏:XX农场(H5)。
功能:使用Golang模拟登陆,并自动完成“收茶”和“浇水”操作。
这是一个H5的农场游戏,很常见的一个游戏。
游戏需要让玩家先收茶然后再浇水。
做一次收茶浇水操作之后,需要等一定的时间后,再做重复做这个动作。
我们在这里不讨论这个游戏项目,或者游戏体验等问题。
我们只关于如何使用Golang自动完成这些操作。
模拟登录
要模拟登录,就需要先分析这个游戏登录需要哪些条件。
H5游戏,那当然使用Chrome这个神器咯。
我们从登录界面可以看到,这个表单主要有三个控件:登录账户、登录密码和记住账号密码。
我们使用Chrome来看看这个表单的具体情况。
从HTML代码中,我们可以发现,有两个隐藏域,这个表单控件的具体情况如下:
- __VIEWSTATE
- __VIEWSTATEGENERATOR
- tb_loguid
- tb_pwd
- checkboxDefault
- 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)
相关推荐
- 「layui」表单验证:验证注册
-
注册界面手动验证获取短信验证码代码原文<!DOCTYPEhtml><htmllang="zh"><head>&...
- Full text: Joint statement between China and Kenya on creating an inspiring example in the all-weather China-Africa community with a shared future for the new era
-
JointStatementBetweenthePeople'sRepublicofChinaandtheRepublicofKenyaonCreatinganInspi...
- 国际组织最新岗位信息送给你
-
国际刑警组织PostingTitleITLogisticsManagerGrade5DutyStationAbidjan,IvoryCoastDeadlineforApplicatio...
- 【新功能】Spire.PDF 8.12.5 支持设置表单域的可见与隐藏属性
-
Spire.PDF8.12.5已发布。该版本新增支持设置表单域的可见与隐藏属性、添加自定义的元数据以及给PDF文档的元数据添加新的命名空间。本次更新还增强了PDF到DOCX和图片的转换...
- AI curbs show Biden's rejection of cooperation
-
AIcurbsshowBiden'srejectionofcooperation:ChinaDailyeditorial-Opinion-Chinadaily.com.cnT...
- “煤气灯效应”上热搜,这几种有毒的“情感关系”也要注意了……
-
近日,“煤气灯效应”(theGaslightEffect)再次进入公众视野并登上热搜,引发网友广泛关注。那么,什么是“煤气灯效应”?以“爱”之名进行情绪控制在心理学中,通过“扭曲受害者眼中的真实”...
- Qt编写推流程序/支持webrtc265/从此不用再转码/打开新世界的大门
-
一、前言在推流领域,尤其是监控行业,现在主流设备基本上都是265格式的视频流,想要在网页上直接显示监控流,之前的方案是,要么转成hls,要么魔改支持265格式的flv,要么265转成264,如果要追求...
- 写给运维的Nginx秘籍
-
要说Web服务器、代理服务器和调度服务器层面,目前使用最大的要数Nginx。对于一个运维工程师日常不可避免要和Nginx打交道。为了更好地使用和管理Nginx,本文就给大家介绍几个虫虫日常常用的秘籍。...
- 突破亚马逊壁垒,Web Unlocker API 助您轻松获取数据
-
在数据驱动决策的时代,电商平台的海量数据是十足金贵的。然而,像亚马逊这样的巨头为保护自身数据资产,构建了近乎完美的反爬虫防线,比如IP封锁、CAPTCHA验证、浏览器指纹识别,常规爬虫工具在这些防线面...
- 每日一库之 logrus 日志使用教程
-
golang日志库golang标准库的日志框架非常简单,仅仅提供了print,panic和fatal三个函数对于更精细的日志级别、日志文件分割以及日志分发等方面并没有提供支持.所以催生了很多第三方...
- 对比测评:为什么AI编程工具需要 Rules 能力?
-
通义灵码ProjectRules在开始体验通义灵码ProjectRules之前,我们先来简单了解一下什么是通义灵码ProjectRules?大家都知道,在使用AI代码助手的时候,有时...
- python 面向对象编程
-
Python的面向对象编程(OOP)将数据和操作封装在对象中,以下是深度解析和现代最佳实践:一、核心概念重构1.类与实例的底层机制classRobot:__slots__=['...
- Windows系统下常用的Dos命令介绍(一)
-
DOS是英文DiskOperatingSystem的缩写,意思是“磁盘操作系统”。DOS主要是一种面向磁盘的系统软件,说得简单些,DOS就是人给机器下达命令的集合,是存储在操作系统中的命令集。主要...
- 使用 Flask-Admin 快速开发博客后台管理系统:关键要点解析
-
一、为什么选择Flask-Admin?Flask-Admin是Flask生态中高效的后台管理框架,核心优势在于:-零代码生成CRUD界面:基于数据库模型自动生成增删改查功能-高度可定制...
- Redis淘汰策略导致数据丢失?
-
想象一下,你的Redis服务器是一个合租宿舍,内存就是床位。当新数据(新室友)要住进来,但床位已满时,你作为宿管(淘汰策略)必须决定:让谁卷铺盖走人?Redis提供了8种"劝退"方案,...
- 一周热门
- 最近发表
-
- 「layui」表单验证:验证注册
- Full text: Joint statement between China and Kenya on creating an inspiring example in the all-weather China-Africa community with a shared future for the new era
- 国际组织最新岗位信息送给你
- 【新功能】Spire.PDF 8.12.5 支持设置表单域的可见与隐藏属性
- AI curbs show Biden's rejection of cooperation
- “煤气灯效应”上热搜,这几种有毒的“情感关系”也要注意了……
- Qt编写推流程序/支持webrtc265/从此不用再转码/打开新世界的大门
- 写给运维的Nginx秘籍
- 突破亚马逊壁垒,Web Unlocker API 助您轻松获取数据
- 每日一库之 logrus 日志使用教程
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- HTML 参考手册 (28)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML常用标签 (29)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)