教务系统成绩自动提醒及机器学习在验证码识别中的简单探索
Posted 中统特工局
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了教务系统成绩自动提醒及机器学习在验证码识别中的简单探索相关的知识,希望对你有一定的参考价值。
对于我们这样一个学渣来说,其实是没有教务成绩提醒这种需求的。但是某些高分大佬可能比较关注。以下是一个简单的教务成绩自动提醒的实现框架。原理是每隔一小时登入教务系统,刷新页面信息。利用页面解析器抓取需要的信息,若存在,则发送邮件至指定邮箱,若没有,则忽略。
原理上比较容易。但是实现上有两个问题,一个是网络问题,学校的教务网站有时候是不允许外网访问的,但是寒假实测可以,所以这个问题就不存在了。第二个是学校的系统一般需要填写验证码,验证码的处理是爬虫中常见的也是棘手的问题。常见的解决思路有通过Input函数在需要填写验证码是手动输入。第二种是使用tesseract或者百度AI的图像识别接口来将验证码转化为字符,这种一般要求验证码很清晰、规整、易识别。第三种是使用机器学习的方法对验证码数据进行训练,这种方式的优点是可以针对特定网站或者一类的验证码进行训练,对于特定的网站验证码识别度较高,但缺点是要求的数据量较大和通用性较差。
以下将使用sklearn库搭建一个简易的使用KNN分类算法的验证码识别框架,并将其运用到教务系统成绩查询和自动提醒中。
验证码识别机器学习框架
原理
验证码识别的原理与图片识别的原理相似,其实质是一个多分类的问题。图像的实质是由众多的像素点组成,对于一张图片,假如他的size为9*20,可以想象为由9行,20列180个像素点构成。在每个点为0-255的不同数字代表不同颜色,0为黑色,255为白色,中间的其他数字代表了黑色到白色之间过渡的色谱上其他的各种颜色。当我们有足够多的训练数据(验证码的图片数据)和标签(他代表的字符)时,我们可以将他们进行拟合。最终在训练集上数据和标签对应的误差值最小时,我们得到了我们的模型,他反映的是当这180个数据点存在某一种分布时,他最可能对应的字符是哪些。验证码的字符通常就是字母和数字,所以也就是将数据分类为这些字母和数字。
数据来源及介绍
通过访问验证码对应的网站过下载了66张验证码,将验证码手动进行打码,将命名改为验证码实际对应的字符。之后对验证码进行灰度化,即将验证码转化为黑白色,转化的方法是小于某个阈值但不为0的像素点转化为0,即黑色。再对验证码进行分割。将验证码分割为单个字符,这样我们的分类会更为简单,因为如果对整个验证码进行识别,以一个纯数字的4位验证码为例,如果分割开来进行模型训练,其实是一个10分类的问题,但是如果合在一起,那么是一个10*10*10*10 =10000分类的问题,要求的数据量将会极为庞大,因为对于单个标签(分类中的单个分类),其对应的训练数据根据实际情况有不同要求,但可想而知肯定是必须有一个以上的训练数据的。
验证码灰度化及分割
1def convert_image(image):
2 image=image.convert('L')
3 image2=Image.new('L',image.size,255)
4 for x in range(image.size[0]):
5 for y in range(image.size[1]):
6 pix=image.getpixel((x,y))
7 if pix<120:
8 image2.putpixel((x,y),0)
9 return image2
10
11def cut_image(image):
12 inletter=False
13 foundletter=False
14 letters=[]
15 start=0
16 end=0
17 for x in range(image.size[0]):
18 for y in range(image.size[1]):
19 pix=image.getpixel((x,y))
20 if(pix==0):
21 inletter=True
22 if foundletter==False and inletter ==True:
23 foundletter=True
24 start=x
25 if foundletter==True and inletter==False:
26 end=x
27 letters.append((start,end))
28 foundletter=False
29 inletter=False
30 images=[]
31 for letter in letters:
32 img=image.crop((letter[0],0,letter[1],image.size[1]))
33 img = img.resize((9, 20), Image.BILINEAR)
34 images.append(img)
35 return images
模型搭建
以上我们介绍了数据来源和对其的一些基本的处理,通过获取到的66张验证码并进行灰度化和分割我们得到了264个训练数据,通过手动打码我们得到了以上264个训练数据对应的标签数字。接下来我们队数据进行训练集和测试集的划分。一般训练集占2/3,我们将前164个数据和标签划为训练集,使用sklearn的K近邻算法进行分类拟合。
KNN是通过测量不同特征值之间的距离进行分类。它的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别,其中K通常是不大于20的整数。KNN算法中,所选择的邻居都是已经正确分类的对象。该方法在定类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。
并将模型使用joblib保存到本地,然后使用后面100个数据作为测试集,运用训练好的模型来对测试集上的数据来进行预测,并与真实的标签来进行比较,测试其正确率。模型在测试集上的结果表现非常好,只有一个预测错误,正确率达到了99%。
K近邻验证码识别模型
1from Office.get_path import getPath
2import os
3import requests
4import numpy as np
5from PIL import Image
6import math
7from sklearn import neighbors
8from sklearn.externals import joblib
9
10traindata,trainlabel,testdata,testlabel = data[:164],label[:164],data[164:],label[164:]
11print(len(traindata),len(trainlabel),len(testdata),len(testlabel))
12knn =neighbors.KNeighborsClassifier(algorithm='kd_tree',n_neighbors=3)
13knn.fit(traindata,trainlabel)
14joblib.dump(knn,'yzm.pkl')
15p_result=[int(knn.predict([i])) for i in testdata]
16rel_result = [int(i) for i in testlabel]
17count = 0
18for i in range(len(p_result)):
20 if p_result[i] == rel_result[i]:
21 count+=1
22acc_rate = count/len(p_result)
23print('模型的准确率为:',acc_rate)
实战测试
以上我们验证了模型在测试集上的效果,那么在实际网站的验证码识别上表现如何呢,接下来我们选择一张新下载的验证码图片,加载保存在本地的模型对他进行识别,当然识别前我们依然对该图片进行去杂质和分割处理。最终模型正确识别出了验证码的数字。
验证码识别实测效果
1def read_yzm(yzm_path):
2 knn = joblib.load('yzm.pkl')
3 image=Image.open(yzm_path)
4 image = convert_image(image)
5 images = cut_image(image)[:4]
6 string = []
7 for j in images:
8 m = np.matrix(j.getdata()).tolist()[0]
9 yzm = knn.predict([m])
10 string.append(str(int(yzm)))
11 return ''.join(string)
12
13print(read_yzm('captcha.jpeg'))
待识别验证码
识别结果
教务网站成绩自动提醒
接下来我们将模型运用于教务网站的自动登录和成绩查询提醒。这里有两种思路,一是使用request库的post构造请求数据和请求头,因为涉及到网站链接的跳转,需要使用到session来构造一个会话以保持登录状态。第二种是使用selenium进行模拟点击和输入。第一种在网页构造上较为繁琐,第二种较为直观简洁,但在速度上比第一种要稍慢一些。以下我们采用selenium来进行操作。使用xpath进行输入框的定位,使用selenium的网页截屏及PIL库进行验证码的裁剪,然后调用我们训练的模型进行识别。值得说的是在十几次测试中,我们训练的模型都在几毫秒内全部正确地读取出了验证码。登录后转到成绩单页面,使用pandas读取成绩单表格构造课程成绩字典,对于需要查询的课程,若成绩不是出于隐藏状态,使用之前介绍过的发送邮件的函数发送到邮箱(参见:),最后调用schedule函数,使程序每小时自动执行一次。
成绩查询及自动通知
1def read_yzm(image):
2 knn = joblib.load('yzm.pkl')
3 #image=Image.open(yzm_path)
4 image = convert_image(image)
5 images = cut_image(image)[:4]
6 string = []
7 for j in images:
8 m = np.matrix(j.getdata()).tolist()[0]
9 yzm = knn.predict([m])
10 string.append(str(int(yzm)))
11 return ''.join(string)
12
13def get_yzm(self):
14 driver.save_screenshot('screenshot.png')
15 imgelement = driver.find_element_by_xpath('//*[@id="Image1"]') # 定位验证码
16 location = imgelement.location # 获取验证码x,y轴坐标
17 size = imgelement.size
18 rangle = (int(location['x']), int(location['y']), int(location['x'] + size['width']),
19 int(location['y'] + size['height']))
20 i = Image.open("screenshot.png") # 打开截图
21 captcha = i.crop(rangle)
22 return captcha
23
24class GetAndInform_grade():
25 def __init__(self,username,password,url):
26 self.username = username
27 self.password = password
28 self.url = url
29
30 def get_grade(self):
31 driver.get(url)
32 yzm_pic = get_yzm(self)
33 yzm = read_yzm(yzm_pic)
34 driver.find_element_by_xpath('//*[@id="username"]').clear()
35 driver.find_element_by_xpath('//*[@id="username"]').send_keys(username)
36 driver.find_element_by_xpath('//*[@id="password"]').clear()
37 driver.find_element_by_xpath('//*[@id="password"]').send_keys(password)
38 driver.find_element_by_xpath('//*[@id="code1"]').clear()
39 driver.find_element_by_xpath('//*[@id="code1"]').send_keys(yzm)
40 driver.find_element_by_xpath('//*[@id="ImageButton2"]').click()
41 driver.implicitly_wait(0.5)
42 driver.get('http://yjsy.cufe.edu.cn/PostGraduate/WitMis_CourseScoreView.aspx')
43 driver.implicitly_wait(0.5)
44 res = driver.page_source
45 return res
46
47 def parse_page(self,res):
48 df = pd.read_html(res)[-2]
49 course_name = df.ix[:,1].values
50 course_score = df.ix[:,8].values
51 course_result = {i[0]:i[1] for i in zip(course_name,course_score) if i[0]}
52 need_inform_course = ['国际经济学(Ⅱ)','英语','二外日语']
53 for course in need_inform_course:
54 if course_result[course] != '隐藏':
55 inform_result = '你的课程:' + course + '考试结果已出,你的成绩为:' + course_result[course]
56 print(inform_result)
57 sendmail('课程成绩提醒',inform_result)
58 else:
59 '此课程当前成绩尚未出'
60
61
62def main():
63 username =
64 password = ''
65 url = 'http://yjsy.cufe.edu.cn/login.aspx'
66 mycourse_result = GetAndInform_grade(username,password,url)
67 grade = mycourse_result.get_grade()
68 inform = mycourse_result.parse_page(grade)
69
70if __name__=='__main__':
71 schedule.every().hour.do(main)
结果可见,在目标三门课中目前只有二外日语成绩已出。
推荐阅读:
以上是关于教务系统成绩自动提醒及机器学习在验证码识别中的简单探索的主要内容,如果未能解决你的问题,请参考以下文章