由于自己的专业不是学计算机的,所以只能利用课余时间自学Python。从上个暑假开始,写了大大小小的Python小程序,虽然大多数都比较简陋,但确实在每一次写代码的时候都能感受到编程的乐趣。
最近微信的小游戏占据了很多人的朋友圈,像跳一跳、头脑王者。自从上次体验了知乎大神写的跳一跳辅助程序之后,自己就有了做一个头脑王者答题助手的念头,一开始也是希望能够实现全自动答题,仿照跳一跳那个adb+Python的模式。看了网络上的一些教程,大多数教程都是比较简单的,没有完整代码,仅仅提供一个思路,那就自己动手丰衣足食吧~~
1.OCR文字识别
一开始接触到的就是OCR,经过百度谷歌之后,Python识别图片的文字需要pytesseract和PIL两个库,还需要识别引擎tesseract-ocr。前面两个库通过命令行安装就好了,然后tesseract可以在github下载,在安装的过程中记得要选择下载简体中文的语言包。安装完成之后,需要修改一下配置才能正常使用,找到你Python的安装路径,打开Python\Lib\site-packages\pytesseract\pytesseract.py,打开之后,作以下修改:
#tesseract_cmd = 'tesseract'
tesseract_cmd = 'C:/Program Files (x86)/Tesseract-OCR/tesseract.exe'
成功之后我们找一道题来试一下:
from PIL import Image
import pytesseract
question = pytesseract.image_to_string(Image.open('test.jpeg'),lang='chi_sim')
question = question.replace(' ','') #去除空格
print(question)
OK~虽然有一些错别字,但是至少是识别出来了。看到成功识别出题目之后,我就兴奋地去写接下去的代码,可是后来用不同的题目来测试代码的时候,才发现识别率是真的低,除了一开始兵马俑那道题,其他题测试出来全是乱码。无奈只能去谷歌提高识别率的方法,网络上都是说黑白图片、高分辨率图片的识别率会高一点。后来就加了一段修改图片的代码,都是运用了PIL这个库。
修改图片模式:
from PIL import Image
img = img.convert('1')
裁剪图片(从手机截图裁剪题目的部分):
from PIL import Image
p = Image.open(picname)
p_size = p.size #获得图片尺寸
t = p.crop((0,int(p_size[1])*0.25,p_size[0],int(p_size[1])*0.45)) #截取题目部分的图片,后两个数字要比前两个大
但是修改之后,识别率并没有明显的变化,大多数图片识别出来还是乱码,在停滞了一段时间之后(主要还是因为学习期末很多事做- -),突然想到修改图片的背景颜色和字体的颜色,经过多次检验,发现黄底黑字的识别率最高,颜色改了之后,大多数的题目都能识别出来了。
图片修改颜色:
from PIL import Image
t2 = t1.convert('RGB') #转rgb模式
for i in range(0,t2.size[0]):
for j in range(0,t2.size[1]):
r = t2.getpixel((i,j))[0]
g = t2.getpixel((i,j))[1]
b = t2.getpixel((i,j))[2]
if b>r and b>g and (r,g<100)and (b<210):
r=255
g=255
b=154 #背景蓝色变黄
elif (r,g,b>=180):
b=0 #白色字变黑
g=0
r=0
t2.putpixel((i,j), (r,g,b))
代码大概的思路是用ADB命令实时截取头脑王者的图片,然后处理图片,识别出题目和四个选项,用百度知道搜索题目,再用爬虫抓下答案,根据四个选项在答案中的出现次数,得出最佳选项。
完整代码:
from PIL import Image
import pytesseract
import requests
from bs4 import BeautifulSoup as BS
from urllib import parse
import datetime
import os
def open_pic(picname):
p = Image.open(picname)
p_size = p.size #获得图片尺寸
t = p.crop((0,int(p_size[1])*0.25,p_size[0],int(p_size[1])*0.45)) #截取题目部分的图片,后两个数字要比前两个大
t.save('./first_change.png')
t_size = t.size #获得截取后的图片尺寸
return t_size,p,t
def get_question(picsize,firstpic):
new_x = 0
new_y = 0
t = firstpic
for i in range(0,picsize[0]):
last_pixel = t.getpixel((i,0))[2]
for j in range(0,picsize[1]):
now_pixel = t.getpixel((i,j))[2]
if last_pixel < 190 and now_pixel > 200:
new_x = i-50
new_y = j-150
break
if new_x:
break #找到背景和文字刚刚转换的像素点
#背景变黄色,字体变黑色 t1 = t.crop((new_x,new_y,new_x+894,new_y+280))
t2 = t1.convert('RGB') #转rgb模式
for i in range(0,t2.size[0]):
for j in range(0,t2.size[1]):
r = t2.getpixel((i,j))[0]
g = t2.getpixel((i,j))[1]
b = t2.getpixel((i,j))[2]
if b>r and b>g and (r,g<100)and (b<210):
r=255
g=255
b=154 #背景蓝色变黄
elif (r,g,b>=180):
b=0 #白色字变黑
g=0
r=0
t2.putpixel((i,j), (r,g,b))
t2.save("./second_change.png")
question = pytesseract.image_to_string(Image.open('second_change.png'),lang='chi_sim') #分析题目
question = question.replace(' ','') #去除空格
question = question.replace('\n','') #去除换行
print(question)
return question
def get_choice(oldpic):
p = oldpic
p_size = p.size
c = p.crop((250,int(p_size[1])*11/20,850,int(p_size[1])*8/9)) #截取选项部分的图片,后两个数字要比前两个大
c1 = c.crop((0,0,600,691*1/6))
c2 = c.crop((0,160,600,300))
c3 = c.crop((0,360,600,500))
c4 = c.crop((0,550,600,691))
cc = [c1,c2,c3,c4]
choices = []
for h in cc:
for i in range(0,h.size[0]):
for j in range(0,h.size[1]):
r = h.getpixel((i,j))[0]
g = h.getpixel((i,j))[1]
b = h.getpixel((i,j))[2]
if b>r and b>g and (r,g<100)and (b<220):
r=0
g=0
b=0 #蓝色字变黑
elif (r,g,b>=160):
b=154 #白色背景变黄
g=255
r=255
h.putpixel((i,j), (r,g,b))
h.save("./ana_choice.png")
choice = pytesseract.image_to_string(Image.open("ana_choice.png"), lang='chi_sim') # 分析选项
choice = choice.replace(' ','')
#解决选项中有英文大写字母0的识别错误 if '0' in choice:
choice=choice.replace('0','O')
print (choice)
choices.append(choice)
return choices
def search_answer(question,choices):
ll = [0,10,20]
answer = []
for p in ll:
b = parse.quote(question.encode('gbk')) #转gbk码
url = 'https://zhidao.baidu.com/search?word=' + b + '&ie=gbk&site=-1&sites=0&date=0&pn=' + str(p) r = requests.get(url)
r.encoding = 'gbk' #网址转gbk编码
soup = BS(r.text, 'html.parser')
want = soup.find('div', id='wgt-list')
wants = want.find_all('dl', class_='dl')
for i in wants:
ans = i.find('dd', class_='dd answer').text
answer.append(ans)
choiceset = {}
choiceset['A'] = choices[0]
choiceset['B'] = choices[1]
choiceset['C'] = choices[2]
choiceset['D'] = choices[3]
for i in choiceset:
account = []
for j in answer:
if choiceset[i] in j:
account.append(j)
a = 0
for k in account:
a += 1
print('选' + i + '的可能性是' + str('%.2f' % (a * 100 / 30)) + '%')
def main(filename):
picsize = open_pic(filename)[0]
oldpic = open_pic(filename)[1]
firstpic = open_pic(filename)[2]
question = get_question(picsize,firstpic)
choices = get_choice(oldpic)
search_answer(question,choices)
if __name__ == '__main__':
start = datetime.datetime.now()
your = input('准备好了按y:')
if your == 'y':
os.system('adb shell screencap -p /sdcard/auto.png')
os.system('adb pull /sdcard/auto.png')
img = Image.open('auto.png')
img.convert('RGB')
img.save('auto.png')
main('auto.png')
end = datetime.datetime.now()
print ('本次一共花了'+str((end-start).seconds)+'秒')
尝试运行一下,发现运行时间太太太太长了,估计是图片识别会占用很长时间,每当我5个题目答完,第一题才刚刚分析出来,虽然过程中花了很多心思,但是这种效果肯定是没有实用性的,让人心酸。
2.Fiddler抓包
正打算放弃这个程序的时候,发现了Fiddler这个抓包工具,之前学爬虫的时候就听到过,但是那时候没认真研究。应用到这里刚刚好,通过Fiddler实时抓取头脑王者传输的数据,把数据保存下来给Python分析,接下来的事就简单得多了。
Fiddler手机抓包的教程网上有很多,重点是把传输的数据自动保存下来。使用Fiddler时最后设置成只看含有‘quiz’的url,不然会冒出很多无关的数据。
设置完之后玩一局游戏,软件中出现了五个新的数据,里面就包含了每一道题的信息。原来之前辛辛苦苦弄图片识别,现在这么容易就把题目和选项拿到手了。
接下来就是最重要的自动保存json数据,在软件中的‘FiddlerScript’--‘OnBeforeResponse’修改一下代码:
在原有的基础上加这段代码:
if(oSession.host == 'question.hortor.net'){
oSession.utilDecodeResponse(); //Decoding HTTP request in case it's gzip //Saving full request object (Including HTTP headers) oSession.SaveResponse('C:\\Users\\XXXX\\Desktop\\data\\response.txt',true);
//Saving just body oSession.SaveResponseBody('C:\\Users\\XXXX\\Desktop\\data\\responsebody.txt');
}
有了数据文件,接下来的事就交给Python了,直接贴代码:
import json
import time
from urllib import parse
import requests
from bs4 import BeautifulSoup as BS
def get_appinf(filename):
f = open(filename, 'r', encoding='utf-8')
try:
j = json.loads(f.read())
#判断数据文件是否有题目和选项 if 'quiz' in j['data'] and 'options' in j['data']:
num = j['data']['num']
quiz = j['data']['quiz']
print(('第'+str(num)+'题:'+quiz).center(50,'*')+'\n')
cho = j['data']['options']
else:
pass
return quiz,cho
except:
pass
f.close()
def search(question,choice):
pagenum = [0,10,20]
answer = []
for i in pagenum:
q = parse.quote(question.encode('gbk')) # 转gbk码
url = 'https://zhidao.baidu.com/search?word=' + q + '&ie=gbk&site=-1&sites=0&date=0&pn=' + str(i) requests.packages.urllib3.disable_warnings() # 忽视网页安全性问题
r = requests.get(url, verify=False) # 不验证证书
r.encoding = 'gbk' # 网址转gbk编码
soup = BS(r.text, 'html.parser')
want = soup.find('div', id='wgt-list')
wants = want.find_all('dl', class_='dl')
for i in wants:
ans = i.find('dd', class_='dd answer').text
answer.append(ans)
choiceset = {}
choiceset['A'] = choice[0]
choiceset['B'] = choice[1]
choiceset['C'] = choice[2]
choiceset['D'] = choice[3]
#计算四个选项在爬取百度答案中的出现次数 results = {}
for i in choiceset:
account = []
for j in answer:
if choiceset[i] in j:
account.append(j)
result = len(account)/30
results[i] = result
if i == 'D':
print(('选' + i + '的可能性是:%.2f%%' % (result * 100 )).center(50)+'\n')
else:
print(('选' + i + '的可能性是:%.2f%%' % (result * 100 )).center(50))
#选出数值最大元素的对应键 bestchoice = max(results.items(), key=lambda x: x[1])[0]
print (('此题最好选'+bestchoice).center(50,'-')+'\n\n\n')
def main():
try:
que,cho = get_appinf('C:/Users/XXXX/Desktop/data/responsebody.txt') #修改成你自己的保存位置
search(que,cho)
except:
pass
if __name__ == '__main__':
while True:
main()
time.sleep(2)
这次的程序实际效果比之前的好多了,在手机上的题目出来之前,Fiddler就能抓取到数据并通过Python找到答案,但是问题也是很明显,稍微复杂一点的题目百度也搜索不出来,还有反向题目(‘不属于’、‘不包括’‘不是’)的识别率也不高,偶尔也会被答题大神吊打,但是拿来娱乐一下其实也足够了,毕竟头脑王者不同什么登顶大会,答对题没有奖金。程序出来之后,花了大半个小时上了王者。