前言:
近期学校要求强制去微信公众号小程序进行:安全知识答题,连续5天,每次25题,时间由10分钟逐级递减,感觉题量有点大,而且每天都要答题,所以我就在想有没有什么办法能进行自动答题,同时也可以来锻炼自己的技术,经过不断踩坑失败后,终于成功了。效果如下图所示:
前期思路:
1.selenium自动化:首先我想到的就是,能不能分享小程序链接,在网页做,可以用selenium来实现,不过我发现这个小程序链接根本就分享不了,这种方法直接pass。
2.抓包:可以用一些抓包软件来抓包解析请求分析参数,但是难度肯定极高,而且说不定一堆反爬,鉴于我逆向基础较差这种方法pass
3.用模拟器模拟手机,然后登陆微信进行操作,最后发现模拟器登陆微信打不开这个做题小程序,不知道是不是反扒措施,非常恼火。
解决办法:
山穷水尽疑无路,柳暗花明又一村。经过一次次碰壁,我最终选择了一个最笨的办法:
1.使用投屏软件将手机投屏到电脑屏幕上
2.使用python的pyautogui库,将电脑截屏,刚好把题目截下来
3.使用百度api图片文字识别技术,将截屏的问题识别出来
4.获取题库,采用正则表达式将问题和答案保存进字典里
5.根据问题和字典获取答案
解决过程及代码:
1.使用投屏软件将手机投屏到电脑屏幕上
这里我使用的软件是todesk,手机和电脑各下载一个,就可以投屏了
2.使用python的pyautogui库,将电脑截屏,刚好把题目截下来
其中(x1,y1)是左上角坐标, (x2,y2)是右下角坐标
import pyautogui
# 获取屏幕截图
def save_screen(x1, y1, x2, y2):
screenshot = pyautogui.screenshot()
cropped_screenshot = screenshot.crop((x1, y1, x2, y2))
cropped_screenshot.save('cropped_screenshot.png')
save_screen(66, 400, 730, 1500)
3.使用百度api图片文字识别技术,将截屏的问题识别出来
这里需要去百度智能云-登录注册账号,创建应用,把下面的api_key和secret_key换成自己的就行了
import base64
import urllib
import requests
API_KEY = "您的API_KEY "
SECRET_KEY = "您的SECRET_KEY "
def get_text(filaPath):
url = "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate?access_token=" + get_access_token()
image = get_file_content_as_base64(filaPath, True)
payload = f'image={image}'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
return response.text
def get_file_content_as_base64(path, urlencoded=False):
"""
获取文件base64编码
:param path: 文件路径
:param urlencoded: 是否对结果进行urlencoded
:return: base64编码信息
"""
with open(path, "rb") as f:
content = base64.b64encode(f.read()).decode("utf8")
if urlencoded:
content = urllib.parse.quote_plus(content)
return content
def get_access_token():
"""
使用 AK,SK 生成鉴权签名(Access Token)
:return: access_token,或是None(如果错误)
"""
url = "https://aip.baidubce.com/oauth/2.0/token"
params = {"grant_type": "client_credentials", "client_id": API_KEY, "client_secret": SECRET_KEY}
return str(requests.post(url, params=params).json().get("access_token"))
get_text("images/2.png")
4.获取题库,采用正则表达式将问题和答案保存进字典里
这是一份题库,里面涵盖了绝大部分原题,不会写代码的把文字复制进文档,按ctrl+f搜索题也可以查到题和答案。
也可以将下列文字保存进txt文件,然后使用正则表达式进行解析,由于题库格式问题我踩了不少坑,我列举一下我踩过的坑:
我开始发现的规律是每道题都以 题号(数字)+、开头 ,以 ()。结尾,所以我开始写的正则表达式就是 res = re.findall('\d{1,3}、(.*?)()。', text, re.S) 但是很快我就发现,匹配的少了几十道题,误差较大,在我仔细核对后发现:
第一个坑:
问题:有些题不以()。为结尾,这就导致很多题没匹配上
解决方法:在我的一番观察后发现,虽然很多题空不在最后,但是每到题结束后肯定有选项吧,第一个选项就是选项A,改进后的代码: res = re.findall('\d{1,3}、(.*?)A', text, re.S) 确实多匹配到很多数据,但是还是有遗漏。
第二个坑:
问题:有些题不以 题号(数字)+、开头,比如有的是题号(数字)+. 或者题号(数字)+空格
解决方法:res = re.findall('\d{1,3}[、\s \\.](.*?)A', text, re.S),这样不论是以 点 顿号 还是空格开头,都能匹配上了。
第三个坑:
问题:有些题的选项部分甚至没有ABCD,只有简单的换行,而且题目还有几十个,看到这我一下子就懵了,没办法题库质量太差了。
解决方法:res = re.findall('\d{1,3}[、\s \\.](.*?)答案', text, re.S),这样即使有的题没有abcd选项,至少有“参考答案”这几个字是全的(有的参考两个字都没有,所以我匹配的是答案)这样一下就能直接匹配一整道题,答案的匹配就容易很多了answer= re.findall('答案.*?(\w{1,7}\s', text, re.S)因为答案有单选和多选,而且有的多选很恶心中间放空格。
第三个坑:
问题:有些问题没有题号,导致无法匹配
解决方法:这下真的没办法了,还好不是很多,只能一个一个去加了,如果大家有什么好办法欢迎来评论区讨论。
5.根据问题和字典获取答案
这里我将识别文本重新拼接了一下,可以直接返回第count张图片的问题
def get_question(count):
url = "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate?access_token=" + get_access_token()
image = get_file_content_as_base64(f"images/{count}.png", True)
payload = f'image={image}'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
data = json.loads(response.text)
words_result = data['words_result']
question = ''
flag = False
for result in words_result:
words = result['words']
if '【判断】' in words or '【多选】' in words or '【单选】' in words:
flag = True
if flag:
question += words
if '()' in words:
flag = False
print('问题:', question)
return question
结果如下:
主函数如下:
1.首先加载保存好的字典
2.为了能够持续识别,采用while True循环
3.屏幕区域截图
4.通过截图获取问题
5.为了防止截图识别到的文字和题库有细微差别,这里我选取了识别到的问题中两个关键词,判断题库里的问题是否同时包含这俩关键词,如果同时包含,说明大概率就是这道题,输出问题以及结果就行。
优化思路:可以选用机器学习里的知识,求 截图问题的文本 和 所有题库里的问题 做相关性分析,进行排序找到相似度最高的,输出答案。
def main():
# 加载题库
dan_xuan = load_answer('构建题库/单选.json')
duo_xuan = load_answer('构建题库/多选.json')
pan_duan = load_answer('构建题库/判断.json')
x1, y1, x2, y2 = 66, 400, 730, 1500
count = 0
while True:
s = input('是否继续?')
count += 1
save_screen(x1, y1, x2, y2, count)
q = get_question(count)
s1 = q[8:11]
ss = q[-8:-4]
print('关键词: ', s1, ss)
print()
if '【单选】' in q:
for k, v in dan_xuan.items():
if ss in k and s1 in k:
print('搜索结果:'+k, '答案:'+v)
if '【多选】' in q:
for k, v in duo_xuan.items():
if ss in k and s1 in k:
print('搜索结果:' + k, '答案:' + v)
if '【判断】' in q:
for k, v in pan_duan.items():
if ss in k and s1 in k:
print('搜索结果:'+k, '答案:'+v)
print('\n\n\n')
全部代码:
import base64
import json
import urllib
import pyautogui
import requests
API_KEY = "您的API_KEY "
SECRET_KEY = "您的SECRET_KEY "
def get_file_content_as_base64(path, urlencoded=False):
with open(path, "rb") as f:
content = base64.b64encode(f.read()).decode("utf8")
if urlencoded:
content = urllib.parse.quote_plus(content)
return content
def get_access_token():
url = "https://aip.baidubce.com/oauth/2.0/token"
params = {"grant_type": "client_credentials", "client_id": API_KEY, "client_secret": SECRET_KEY}
return str(requests.post(url, params=params).json().get("access_token"))
# 获取屏幕截图
def save_screen(x1, y1, x2, y2, i):
screenshot = pyautogui.screenshot()
cropped_screenshot = screenshot.crop((x1, y1, x2, y2))
cropped_screenshot.save(f'images/{i}.png')
#加载json文件读取为字典
def load_answer(fileName):
with open(fileName, 'r') as file:
data_dict = json.load(file)
return data_dict
#获取第i张图片的 问题 的文本
def get_question(count):
url = "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate?access_token=" + get_access_token()
image = get_file_content_as_base64(f"images/{count}.png", True)
payload = f'image={image}'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
data = json.loads(response.text)
words_result = data['words_result']
question = ''
flag = False
for result in words_result:
words = result['words']
if '【判断】' in words or '【多选】' in words or '【单选】' in words:
flag = True
if flag:
question += words
if '()' in words:
flag = False
print('问题:', question)
return question
def main():
# 加载题库
dan_xuan = load_answer('构建题库/单选.json')
duo_xuan = load_answer('构建题库/多选.json')
pan_duan = load_answer('构建题库/判断.json')
x1, y1, x2, y2 = 66, 400, 730, 1500
count = 0
while True:
s = input('是否继续?')
count += 1
save_screen(x1, y1, x2, y2, count)
q = get_question(count)
s1 = q[8:11]
ss = q[-8:-4]
print('关键词: ', s1, ss)
print()
if '【单选】' in q:
for k, v in dan_xuan.items():
if ss in k and s1 in k:
print('搜索结果:'+k, '答案:'+v)
if '【多选】' in q:
for k, v in duo_xuan.items():
if ss in k and s1 in k:
print('搜索结果:' + k, '答案:' + v)
if '【判断】' in q:
for k, v in pan_duan.items():
if ss in k and s1 in k:
print('搜索结果:'+k, '答案:'+v)
print('\n\n\n')
if __name__ == '__main__':
main()