【Python】tesseract+uiautomator2+夜神模拟器 悠长假期手游集市识别验证码自动购买

开宗明义:这篇文章实际上就是把我的操作记录一遍,以防以后忘记了,又要到处去搜罗。由于我是个python小白,所以这些操作都是各处学来拼到一起的,也因此我确信如果不赶紧记下来,很快就会忘掉。于是就趁热写了这随笔。


1 引言

1.1 背景

最近两个月一直在肝悠长假期这款海盗经营类的养老休(重)闲(肝)手游,说来伤心,真的我最初是冲着养老这俩字玩的,没想到啊没想到,现在竟然被动养成了生物钟,每天早上六点一醒,赶紧去收我的作物,然后就是一个小时的各种任务、配送和农活。回想以前七八点几个闹钟都喊不醒的我,现在意识到果然当一个“云农民”也不容易。

啊哈,闲话休提,今天我们要干什么呢?这就要说到这个游戏里的交易系统——集市。集市这个交易系统可谓是吐槽满满,此处不表,主要讲一下它是怎么运作的:在集市中,每个玩家可以上架各种多余的产品,也可以从架上购买其他玩家上架的自己需用的产品,就这样形成了供求。然而可想而知,不论在游戏还是在现实市场中,都会有稀缺的商品,而这种商品往往供不应求,这点在游戏中的市场尤其明显。在现实生活中,对此的解决办法是市场自动调节价格,直至稀缺的商品价格达到很高,便能自动实现供求平衡;但在这款游戏里,并不是如此,而是系统给每种产品都定好了价格,只能按照固定的价格交易,这样的话,由于基本上每个人都有购买的能力,而产品远远达不到每个人的需求,便产生了严重的供不应求。于是许多玩家专门“蹲”集市,每隔一定时间就刷新一下该产品的货架,一旦发现有上架立即抢之。然而想抢到货物总是要花费许多的时间和精力去等待,这对于我来说是严重不能忍受的(其实我还是忍受了很久,并且显然今后还要忍受)。

好吧,好多废话,其实引言可以略过不看的。这回真的回归正题,在这个集市中,我要做的事情是这样的:每隔一定时间刷新货架(因为系统有刷新冷却cd),并购买货物(注意也有购买间隔cd);当出现验证码时,自动填写验证码。

可以看见,其实前一个要求容易得很,随便一个自动点击器就可以轻松搞定;但是由于系统会定时对玩家要求填写一个简单的数字加法的验证码(这是游戏为了尝试应对集市中连点器泛滥的情况,但似乎并没有什么作用,反而让普通玩家更加艰难了),这对于一个普通的手机连点器似乎要求过高。我之前用的是auto.js,翻遍全网也没有找到相关的图片识别教程,似乎是不行的,如果有朋友能告诉我有关auto.js如何识别图片验证码的知识,实在感激不尽。于是我几经辗转用自学了不久的python希望实现这件事,但是我自己觉得自己的代码似乎颇为繁琐,尤其是识别效率堪忧,还请大家不吝赐教,帮助我提高一下自己的代码水平。

1.2 主要工具

用到了夜神模拟器;uiautomator2进行集市上的操作;tesseract进行文字识别;jupyter notebook, python 3.7.1

 

2 具体步骤

2.1 adb连接

在anaconda prompt中进入夜神模拟器bin目录,并输入

adb.exe devices

如图所示:

 进行adb连接操作:

adb connect 127.0.0.1:62026

 ok,接下来进入打开jupyter notebook,可以用python对模拟器进行操作了。

2.2 导入库

import pytesseract 
import PIL.ImageGrab
import time
import uiautomator2 as u2
d = u2.connect('127.0.0.1:62001') #进行连接

其中pytesseract用来进行验证码的识别;PIL.ImageGrab负责截取验证码;time用来控制点击之间的间隔;uiautomator2复杂在模拟器上进行点击和输入。

2.3 购买流程

由于我的需求是只对某一种商品进行循环刷新购买,因此先手动点到该界面。这是集市我要买的界面截图,此处我要买的是苹果。

下面是购买一次苹果需要的完整的操作,在之后对此操作循环即可。

 至此购买成功,一次购买结束;当然,也有可能该商品已经售出,只不过提示不同而已,如下。

 代码如下。

def buy_goods():
    d.click(1300,370)#第1步,点击苹果商品,这也要求了苹果一栏必须在最上面的位置
    time.sleep(0.1)
    #第2步,购买,连续买四格
    d.click(300,430)#第一格
    time.sleep(0.1)
    d.click(1000,600)#确认按钮
    time.sleep(0.1)
    d.click(500,430)#第二格
    time.sleep(0.05)
    d.click(1000,600)
    time.sleep(0.1)    
    d.click(700,430)#第三格
    time.sleep(0.1)
    d.click(1000,600)
    time.sleep(0.1)    
    d.click(930,430)#第4格
    time.sleep(0.1)
    d.click(1000,600)
    time.sleep(1)

有两点因为懒:一是automator2点击的是固定位置,这就要保持购买的商品应当在那个位置上:在我这里表现为,苹果一直处于商品栏最上面的一栏。另一是懒在连续买四格,这是因为可能有这种情况:前几格都被别人买了,但后面几格还是有货,所以就不能急着刷新(否则刷新完了或许反而没货了)。而如果第一格已经成功购买的话,也不会影响什么,只不过弹出几次冷却时间cd 的提示而已。当然,如果本来就是空的,就更是点空气了。

2.4验证码处理

终于说到最关键的部分了,昨天为了这头大了老长时间,现在再看看,哎,真是一言难尽。

如前面所说,系统隔一段时间会给你出个小学计算题玩一玩,实现了典型的“防君子不防连点器”。诺,就是这个图。

 

2.4.1 判断验证码出现

因为验证码不知道什么时候会弹出来,所以我们就在每次循环中都判断一下。怎么判断呢,在每次循环中对上面区域截图并识别文字,看识别结果是不是“输入正确答案完成验证”这几个字。如果是,那么就进入“识别输入验证码”环节,如果没有这几个字,就继续购买的循环。

pytesseract.pytesseract.tesseract_cmd = r"D:\tesseract-ocr\tesseract.exe"
#这一行是说清楚tesseract.exe在自己电脑上的位置,不然运行时可能会报错(好像是Failed to find tesseract.exe)。
flag = '输入正矗鲁案完咸验证\n\x0c' #笑死大牙 def if_input(): crop_img = PIL.ImageGrab.grab(bbox=(720,300,1200,360)) words = pytesseract.image_to_string(crop_img,lang='chi_sim') if words==flag: input_num() else: pass

·flag:好吧,它识别出来的结果是这几个字,我也不知道是怎么回事。不过好用就行,我发现不管它会妥妥地把“输入正确答案完成验证”几个字识别成上面的flag的,所以我们将错就错,也挺不错。

·PIL.ImageGrab.grab(),其中的bbox是裁剪矩形区域,分别是(左,上,右,下)。注意这里的坐标是基于电脑屏幕的坐标,而不是模拟器屏幕的坐标,这一点得和automator2的坐标操作区分开。

·pytesseract.image_to_string(),就是把截的图转化成字符串了,这里写的是语言是简体中文(这个简中的tesseract训练数据包应该还要单独去下载,不过网上好多教程都提供了下载链接,在此表示感谢)

 2.4.2 识别验证码并输入

那么我们就来识别验证码,说来惭愧,这么简单的一件事,我竟然搞了许久,还是因为自己的python基础太不扎实了,难受。代码如下。

def input_num():
    crop_img = PIL.ImageGrab.grab(bbox=(700,400,870,530))
    num = pytesseract.image_to_string(crop_img,lang='chi_sim')
    try:
        a = eval(num)
    except:
        num = pytesseract.image_to_string(crop_img)
    a = eval(num)
    time.sleep(1)
    d.click(900,370)#点击输入框
    time.sleep(1)
    d.send_keys(str(a))#输入答案
    time.sleep(1)
    d.click(960,540)#点击确认
    time.sleep(1)
    d.click(960,540)#再点一下,我也忘了为啥了
    time.sleep(3)

代码中加上try...except是因为,tesseract识别准确率真的有点那啥,把0识别成o还是小事,把3认作g我也理解,但是把5说是v就太欺负人了。当然,并不总是这样,否则这篇随笔也不会有面世之日。不过,我意外地发现,参数加上和不加上lang='chi_sim'(即中文识别)得到的结果有时会不一样,而且有时加上会正确,有时不加上会正确。所以我干脆两个一并招工了。那么,如果都不行呢……额,那就认栽呗,尽管这真是笨得很,想知道大家有啥建议木有,诚心求教。

另外提高识别准确率的方法倒也照网上的代码试过几个,结果就是,处理了半天,图片仍是跟普通一句的识别没区别,就没再用了。

 2.5 执行

最后很简单了,我写的是酱紫。

def start(n):
    i = 0
    while(i<n):
        buy_goods()
        if_input()
        i +=1
start(999)

循环几次可以自己定,顺便也可以统计一下平均循环几次能买到一次(因为可能点到了空气或者点到时已经售出)。

 

3 结语

我觉得如果python基础尚可的话,这些工作其实是一件很简单的事情,然而就因为我自学得零散又不成体系(而且基础应当算还没学完),所以真是事倍功半。不过自我安慰一下,毕竟花几个小时想这些代码也比几个小时蹲集市舒服的多,再说了,时间已经是沉默成本,无需再想什么了。


 

附 关于游戏集市交易机制的想法

挖个坑,对游戏集市交易机制的想法。说简单也简单,说复杂也复杂,别看许多游戏的发展路线很简单甚至很单一,但是由于游戏类型、游戏中商品性质等等的不同,会对游戏的交易机制具有很大的决定性作用。在Taptap上看见有老玩家对于悠长假期的一句评论,说“这个游戏是他见过的唯一一个毁在连点器上的”,深感触动,虽然可能又些主观情绪在内,但真的不无道理(不过现在还并没有毁哈)。改天有空结合一点经济学原理分析分析这些事情。

 


哦,我发现截图完全可以用uiautomator2来截图,根本不必导入一个PIL.ImageGrab,不过由于游戏是横屏的,处理时需要对截图旋转一下,于是用到了Image中的rotate。具体变动不补上去了。这样的话即使把模拟器最小化也照常工作不误,之前的对电脑屏幕截图实在是舍近求远,哈哈。

补一个实际效果图:

 

posted @ 2020-10-30 10:17  Amorius  阅读(1061)  评论(0编辑  收藏  举报