正方驗證碼智能識別及教務系統模擬登錄

前言

之前寫了一個正方教務系統登錄的程序,當時驗證碼使用的是手動輸入,覺得太煞筆了,這種用途的腳本怎麼可以讓這種小小驗證碼攔住呢!︿( ̄︶ ̄)︿,我們要的可是完全不要人工的爬蟲啊!你可知道!┑( ̄Д  ̄)┍

有想法很好,我們就開始做吧!

一、驗證碼數據集獲取

這裏我從github上下載了數據集,大概五百張圖片,非常感謝其提供者。

數據集我放在我的源碼裏面了,需要的話,可以自行下載,github鏈接在底部,此處不贅述。

二、圖像處理

2.1 驗證碼圖片轉灰度、二值化、去噪點
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
2.2 對圖像進行切割

在訓練之前,我們需要獲取每個字符的數據,作爲訓練集,所以我們把驗證碼中的字符切割開來。之前我還使用算法進行檢測切割位置,後來發現完全沒有必要啊!(=。=),驗證碼中總共四個字符,我用手指一比,其中每個字母都處於大致區間內,這就簡單了我們的工作,我們只要在固定的區間內進行切割,就能保證四個字符能夠完好的被分開。

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
2.3 批量處理圖片數據

下面的是數據集文件夾。

首先我們寫一個函數獲取該文件夾下驗證碼文件的文件名,作爲標籤。

在這裏插入圖片描述

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)

三、圖像特徵提取

機器如何能夠識別圖像的呢?當然是轉化爲機器識別的數字啊!這時我們將經圖像處理後的圖片進行特徵提取,將各個像素的通道值提取到一個列表中。

3.1 所有圖像特徵值提取
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
3.2 單張圖片特徵值提取
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

感謝支持!敬禮!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章