首先写这个辅助的想法来源于之前微信小游戏“跳一跳”的辅助,使用到了adb
来获得手机的截图等,而直播答题碰到不会的题遇到的困难主要就是没时间去打字搜索,如果用同样的方法得到了题目相关的文字信息自动去搜索,成功率不就大大提高了嘛!再有很自然想到的一点就是一道题就3个选项,3台手机不就肯定能保证对一道吗?因此如何快速操作多个手机模拟器也需要考虑。
Ref
思路
开启多个手机模拟器,题目出现时启动Python程序,截图并进行OCR识别出文字,获得各个选项,接着对问题以及各个选项分别进行搜索,获得前几条搜索结果或者页面数量等进行判断。
手机模拟器
在这里我用的是夜神手机模拟器,需要用到一个分辨率、DPI较高的模拟器用于OCR识别,剩下的配置尽量调低降低硬件开销。
adb
由于需要操作多个模拟器,我们首先需要知道如何连接各个模拟器。下载好adb包之后将其配置到环境变量中,然后adb devices
查询在线的模拟器即可。然而有时候模拟器抽风,这个命令不起作用,这个时候需要先adb connect
之后adb devices
才有用(神设定),查看对应安装目录,Nox\bin\debug.bat
发现查找对应模拟器的端口的方法是通过查找配置文件的5555端口实现的,依葫芦画瓢,来到Nox\bin\BignoxVMS
查看各个副本的配置文件即可得到端口。这里总结一下用到的adb命令
//输入adb可查看相关命令用法
//存在多个模拟器时需要用-s 指定操作的目标
adb -s 127.0.0.1:62001 shell screencap -p /sdcard/n.png //截屏
adb -s 127.0.0.1:62001 pull /sdcard/n.png ./n.png //传到电脑
//输入adb shell input可查看相关命令用法
adb -s 127.0.0.1:62001 shell input tap x y //点击(x,y)位置 用以点击问题的选项
确定题目与选项位置
经过测试题目选项出现的位置是固定的,所以这里的问题主要是将分辨率较高的模拟器上选项的位置对应到低分辨率模拟器上。
从以上3张图可以得到,分辨率相同的话,题目区域大小与DPI成正比;DPI相同的话,整个题目占用的像素点是相同的,题目区域大小与分辨率成反比。所以假如在480X800 DPI 160
的模拟器上位置为(x,y)
,在240X400 DPI 60
的模拟器上位置为(x,y*2*60/160)=(x,0.75y)
OCR
从固定位置中裁剪出问题的图像,再OCR识别为文字。这里用到的是tesseract-ocr,同样需要配置环境变量。
系统变量下添加变量TESSDATA_PREFIX
值为E:\Tesseract-OCR\tessdata
PATH
下添加E:\Tesseract-OCR
还需要下载一个中文的数据包chi_sim.traineddata
放到E:\Tesseract-OCR\tessdata
cmd下输入tesseract test.png out.txt -l chi_sim
生成的out.txt即为test.png的识别结果。需要注意这个test.png需要裁剪出待识别的区域,否则效果很差。
以上步骤成功后就可以用Python的pytesseract
库来返回结果了。
# 保存问题
cropped_img = im[y_start:y_end, x_start:x_end]
cv2.imwrite("total.bmp" , cropped_img)
# 这里直接使用cropped_img不行,需要保存后再用PIL.Image打开
text_result=pytesseract.image_to_string(Image.open("total.bmp"), lang="chi_sim"a
搜索策略
文本拿到就很好做了。预处理一下,然后用jieba
获取问题的关键词,再进行搜索就行啦。这里我用的是根据搜索结果的数目来判断的。这里请参考Wikipedia ——Pointwise mutual information
divided=text.replace(" ","").split("\n")
for i in divided:
if (i=="" or i=="\n"):
divided.remove(i)
question=divided[0]
del divided[0]
print("Question: %s"%question)
keywords=jieba.analyse.extract_tags(question,topK=2,withWeight=True)
print("Keywords:")
for i in keywords:
print i
totalweight=0
print ("Options: ")
print("\n".join(divided))
至于说获得搜索结果的数量,用一个Xpath来提取就好啦,相信大家写过爬虫之后很容易搞定的。
def get_search_nums(word):
# url="https://www.baidu.com/s?wd="+unicode(word,"utf-8")
#需要先转为utf8才能进行quote
url="https://www.baidu.com/s?wd="+urllib2.quote(word.encode("utf8"))
myheaders = {
'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"} # 浏览器请求头
request=urllib2.Request(url,headers=myheaders)
response=urllib2.urlopen(request)
source=response.read()
html=etree.HTML(source)
result=html.xpath('''//*[@id="container"]/div[2]/div/div[2]/text()''')
result=result[0]
pattern=re.compile(u".*约([\d,]+)个")
#!!!注意这里编码和网页返回的编码要对应才可以进行查找!!大坑!
num=re.findall(pattern,result)
num=int(num[0].replace(",",""))
return num
结果
嗯…用这种PMI算法得到的结果还是不太靠谱,按照整个问题来搜索AC选项差异不大,按照“电影”这个关键词应该选B……(正确答案是C)所以还是需要把搜索结果的前几条给展示出来的。获得正确答案之后朝着各个模拟器发送一个对应位置的adb shell input
去点击选项即可。
改进
- OCR效果太差导致搜索出错。如上图的“第六艺术”识别为“第大艺术”
(啥玩意儿啊)在Ref中的第二篇文章中用到了百度的OCR,不知道效果如何。 - 搜索多个页面由于网络速度的限制还是比较费时间的。
- “跳一跳”中曾出现直接抓包分析并提交结果的方法,不知道在西瓜视频里面能否用上。
- 使用多个模拟器测试的过程中发现各个问题在模拟器上的出现是有时差的,延后出现对作答是有利的,能否在可接受的范围内加大这个时差?