之前写了一个正方教务系统登录的程序,当时验证码使用的是手动输入,觉得太煞笔了,这种用途的脚本怎么可以让这种小小验证码拦住呢!︿( ̄︶ ̄)︿,我们要的可是完全不要人工的爬虫啊!你可知道!┑( ̄Д  ̄)┍
有想法很好,我们就开始做吧!
这里我从github上下载了数据集,大概五百张图片,非常感谢其提供者。
数据集我放在我的源码里面了,需要的话,可以自行下载,github链接在底部,此处不赘述。
def GrayscaleAndBinarization(image):
'''
灰度并二值化图片
:param image:
:return:
'''
threshold = 17 # 需要自己调节阈值,17效果不错哦!
tmp_image = image.convert('L') # 灰度化
new_image = Image.new('L', tmp_image.size, 0)
# 初步二值化
for i in range(tmp_image.size[1]):
for j in range(tmp_image.size[0]):
if tmp_image.getpixel((j, i)) > threshold:
new_image.putpixel((j, i), 255)
else:
new_image.putpixel((j, i), 0)
# 去噪,去除独立点,将前后左右等九宫格中灰度通道值均为255的像素点的通道值设为255
for i in range(1, new_image.size[1] - 1):
for j in range(1, new_image.size[0] - 1):
if new_image.getpixel((j, i)) == 0 and new_image.getpixel((j - 1, i)) == 255 and new_image.getpixel((j + 1, i)) == 255 and \
new_image.getpixel((j, i - 1)) == 255 and new_image.getpixel((j - 1, i - 1)) == 255 and new_image.getpixel((j + 1, i - 1)) == 255 and \
new_image.getpixel((j, i + 1)) == 255 and new_image.getpixel((j - 1, i + 1)) == 255 and new_image.getpixel((j + 1, i + 1)) == 255:
new_image.putpixel((j, i), 255)
return new_image
在训练之前,我们需要获取每个字符的数据,作为训练集,所以我们把验证码中的字符切割开来。之前我还使用算法进行检测切割位置,后来发现完全没有必要啊!(=。=),验证码中总共四个字符,我用手指一比,其中每个字母都处于大致区间内,这就简单了我们的工作,我们只要在固定的区间内进行切割,就能保证四个字符能够完好的被分开。
def SplitImage(image):
'''
切割图像并保存,关键在于寻找切割位置
:param image:
:return:
'''
splitSite = [0, 16, 28, 41] # 这个位置非常好,nice
splitSite.append(54)
# 对图片进行切割
new_image = []
for index in range(1, len(splitSite)):
box = (splitSite[index - 1], 0, splitSite[index], image.size[1])
new_image.append(image.crop(box))
return new_image
下面的是数据集文件夹。
首先我们写一个函数获取该文件夹下验证码文件的文件名,作为标签。
def GetFileName(filePath):
'''
返回指定文件夹下文件的文件名
:param filePath:
:return:
'''
filenames = []
for filename in os.listdir(filePath):
filenames.append(filename)
return filenames
接下来我们批处理所有图片
def SplitAllImage():
'''
分割所有图片
:return:
'''
# dict 记录每个字符的数目
dict = {
'0': 0, '1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0, '7': 0, '8': 0, '9': 0,
'a': 0, 'b': 0, 'c': 0, 'd': 0, 'e': 0, 'f': 0, 'g': 0, 'h': 0, 'i': 0, 'j': 0, 'k': 0, 'l': 0, 'm': 0, 'n': 0,
'o': 0, 'p': 0, 'q': 0, 'r': 0, 's': 0, 't': 0, 'u': 0, 'v': 0, 'w': 0, 'x': 0, 'y': 0, 'z': 0
}
filenames = GetFileName('./images/data_biaoji')
for item in filenames: # 图片名称
image = Image.open('./images/data_biaoji/{}'.format(item))
tmp_image = GrayscaleAndBinarization(image)
data_image = SplitImage(tmp_image)
for i in range(len(data_image)): # 此处值为4,对切割后的四张图片进行处理
dict[item[i]] = dict[item[i]] + 1
data_image[i] = data_image[i].resize((14, 27))
data_image[i].save('./images/data/{}/{}.jpg'.format((item[i]), dict[item[i]])) # 总共获取了1884张图片
print(dict)
机器如何能够识别图像的呢?当然是转化为机器识别的数字啊!这时我们将经图像处理后的图片进行特征提取,将各个像素的通道值提取到一个列表中。
def returnDataAndLabel(path='./images/data'):
'''
返回全部数据
:param path: 图片路径
:return: data 和 label
'''
raw = []
datas = [] # 特征
labels = [] # 标记
for item1 in os.listdir(path): # 文件路径
label = item1
for item2 in os.listdir(path + '/' + item1):
data = []
image = Image.open(path + '/' + item1 + '/' + item2).resize((14, 27))
for i in range(image.size[1]): # 读取像素通道值
for j in range(image.size[0]):
data.append(image.getpixel((j, i)))
data.append(label) # 一张图片的数据
if len(data) == (14 * 27 + 1): # 图片尺寸
raw.append(data)
for i in range(len(raw)):
tmp_data = raw[i][: -1]
tmp_label = raw[i][-1]
labels.append(tmp_label)
datas.append(tmp_data)
return datas, labels
def featuretransfer(image):
'''
返回特征向量,预测时用
:param image: 图像
:param label: 图像所属标签
:return: 特征向量
'''
features = []
image = image.resize((14, 27))
for i in range(image.size[1]):
for j in range(image.size[0]):
features.append(image.getpixel((j, i)))
return features
集成学习较一般学习器往往有较好的效果,此处使用了随机森林算法
def trainModel(datas, labels, isSave=True, path='./model/clf1.model'):
'''
训练模型
:param datas: 数据集
:param labels: 标签
:return: 模型
'''
X_train, X_test, y_train, y_test = train_test_split(datas, labels, test_size=0.3, random_state=30)
clf = RandomForestClassifier(n_estimators=500, max_depth=10, min_samples_split=10, random_state=0)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
score = accuracy_score(y_test, y_pred)
print('Accuracy score:', score)
if isSave:
joblib.dump(clf, path)
return clf
ps: 最后发现效果不错,单个字符识别正确率能达到84%多,模型没有调参,有兴趣的同学可以继续调节使准确率更高。但这里我又想到了一点,单个字符识别率有84%,但正方验证码有四个字符,准确率那就是84%*84%*84%*84% = 0.49,妈呀!识别率一下降了这么多,能用吗??
测试主要使用正方教务系统进行登录进行实际测试,这里有关爬虫,就不细说了,登录代码摆上!!!
import requests
from pyquery import PyQuery as pq
from urllib import parse
from sklearn.externals import joblib
from ProcessingImage import * # 自定义模块
import re
import json
class SCHOOL(object):
def __init__(self):
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) Gecko/20100101 Firefox/60.0',
}
self.username = '**********' # 学号
self.password = '**********' # 密码
self.name = ''
self.url_base = 'http://***.***.***.***/' # 这里换成自己学校的地址
self.raw_url = self.url_base + 'default2.aspx'
self.url_1 = ''
self.login_url = ''
self.real_url = ''
self.session = requests.Session()
self.get_real_url()
self.login()
def get_real_url(self):
'''
获取真实的登录url
:return:
'''
response1 = self.session.get(self.raw_url, headers=self.headers)
self.login_url = response1.url
self.real_url = re.match('(.*)\/default2.aspx', self.login_url).group(1)
self.url_1 = self.real_url + '/xs_main.aspx?xh=' + self.username
def loadModel(self, filename):
return joblib.load(filename)
def login(self):
'''
登录过程
:return:
'''
response2 = self.session.get(self.login_url, headers=self.headers)
text = response2.text
doc = pq(text)
get_captcha_src = doc.find('#icode').attr("src")
_VIEWSTATE = doc.find('#form1 input').attr("value")
_VIEWSTATEGENERATOR = doc.find("#form1 input[name='__VIEWSTATEGENERATOR']").attr('value')
captcha_url = self.real_url + '/' + get_captcha_src
captcha = self.session.get(captcha_url).content
# 保存验证码
with open('./test/captcha.jpg', 'wb') as f:
f.write(captcha)
model = self.loadModel('./model/clf1.model') # 导入模型
img = Image.open('./test/captcha.jpg')
new_img = SplitImage(GrayscaleAndBinarization(img))
# 模型预测验证码
result_pred = []
i = 0
for item in new_img:
tmp = []
item.save(str(i) + '.jpg')
i = i + 1
img_after_split = item.resize((14, 27))
tmp.append(featuretransfer(img_after_split))
result_pred.append(model.predict(tmp)[0])
captcha = ''.join(result_pred)
print('此次预测结果为:', captcha)
# 登录过程
post_data = {
'__VIEWSTATE': _VIEWSTATE,
'__VIEWSTATEGENERATOR': _VIEWSTATEGENERATOR,
'Button1': '',
'hidPdis': '',
'hidsc': '',
'IbLanguage': '',
'RadioButtonList1': '%D1%A7%C9%FA',
'Textbox1': '',
'TextBox2': self.password,
'txtSecretCode': captcha,
'txtUserName': self.username
}
response3 = self.session.post(self.login_url, headers=self.headers, data=post_data)
self.name = pq(response3.text).find('#xhxm').text().replace('同学', '')
if '欢迎您:' in response3.text:
print('登录成功!', self.name)
else:
print('登录失败!正在尝试重新登录')
此处我进行了一百次测试,结果还不错哦!虽然准确率只有大概50%,但经不住程序跑的快啊,对于这个模型,基本3次以内就能成功识别验证码。
Github链接:https://github.com/kingdowliu/IdentificationCodesOfZhengFang
感谢支持!敬礼!!