最近在學python,正好遇到學校需要選宿舍,就用python寫了一個搶宿舍的軟件。其中有一個模塊是用來登陸的,登陸的時候需要輸入驗證碼,不過后來發現了直接可以繞過驗證碼直接登陸的bug。不過這是另外的話題,開始的時候我並沒有發現這個隱藏起來的秘密,所以我就寫了這個python代碼段用來實現解析驗證碼的功能。
我們學校的驗證碼是最簡單的驗證碼,形式大概如下:
其中這個圖片的大小是60X24像素的,大概每個數字的大小是15X24像素。
觀察這個驗證碼之后可以發現,驗證碼中只有數字而且數字的字體很規范,只不過每個數字的顏色不同而已。
當時有2個思路
1.將整張照片平均切片成四分,每個數字一個圖片,然后掃描每張照片的每個像素,為每個數字初始化一個特征碼buff,大小為15X24的byte,即總共45Byte。
先取背景色,可以知道(0,0)位置是背景色。然后掃描數字的每個像素和背景色對比如果相同則為1不同則為0。然后分析出0-9這10個字符的特征值。等需要解析驗證碼的時候直接將驗證碼圖片分片取特征值跟標准特征值對比就可以了。
2.我們可以想象0-9這10個字符每個字符的字形都不一樣,則有可能比如9這個數字在像素(2,12)(1,13)這個位置是獨有的,也就是說分片圖片中假如(2,12)位置的像素點和背景色一致,則該分片圖片一定不是9否則一定是9。
上面兩種方法有一個bug就是這個圖片的第一個數字有一定的偏移,比如其他位置的數字是從第3列開始的,它可能從第4列,這個我就沒具體分析了。不過這個也有辦法解決,我用的辦法就是從第一列非背景色的地方算起。不管什么圖片怎么偏移,它x軸向對於自己最左邊的點的x方向的差值是不變的。
最后我的實現方法就是按第二種,因為這種方法是最快的,只需要取特征像素處的點就可以。
我的方法是這樣的,首先選用材料圖片三張,包含0-9這10個字符,然后校驗他們每個像素與背景色是否一致,如果一致則把這個數字放到對應這個像素的hash表里面。
最后分析這個hash表找出哪個像素是1個數字獨有的,哪個像素是2個數字獨有的,哪個像素3個數字獨有的,最后解析這個表。
找到可以唯一確定一個數字的方法,比如(0,18),(0,19)這兩個數字可以唯一確定數字1。
然后得出一個hash字典:
NumberKeyPixel={ 0: [(7,10),(0,12),(0,10),(0,11),(0,8),(1,14),(1,15)], 1: [(4,8)], 2 :[(0,18),(0,19)], 3 :[], 4 :[(5,7)], 5 :[(0,4),(0,10)], 6 :[(2,6)], 7 :[(2,16)], 8 :[(0,12)], 9 :[(2,13)] }
使用的時候,只需依次比對這些像素點就可以判斷這張圖片的驗證碼值了。
下面介紹具體代碼
1.首先是分析的時候的代碼,用來獲得數字的特征像素:
from PIL import Image import os #存放材料圖片的路徑 path="C:\\vaildpic\\" #取得材料圖片 images=os.listdir(path) 存放數字的切片,0-9的圖片 nubimgs=[] #存放背景色 backpixels=[] #存放像素對應表 pixDir={} #首非背景色偏移值 pixBlankEndPos=[] #這個函數用來取得這個圖片中數字結構的偏移值 def GetLastBlankPosition(materialPic,x=0): bc=materialPic.getpixel((0,0)) for i in range(15): for j in range(24): if materialPic.getpixel((i+x,j))!=bc: return i #因為只是解析沒有寫的很嚴謹,這個地方 #取得目標文件夾的圖片 for image in images: if os.path.isdir(path+image): continue image=Image.open(path+image) #對於每張圖片切成四份,存到字典中,取得相應的背景色,首非背景色偏移x,接下來計算用 for i in range(4): ma=image.crop((i*15,0,(i+1)*15,24)) nubimgs.append(ma) backpixels.append(image.getpixel((0,0))) pixBlankEndPos.append(GetLastBlankPosition(ma)) print pixBlankEndPos #對於每個數字圖片的每個像素,如果對應位置非背景色,將該圖片放到該位置的字典中,其結構如下,接下來用下面的數據統計來取得每個數字的特征像素 ''' pixDir[pixel(x-x_offset,y),imgSeq]=picture
''' for i in range(15): for j in range(24): ai=None aj=None pixDir[(i,j)]={} for imgNum in range(nubimgs.__len__()): if(nubimgs[imgNum].getpixel((i,j))!=backpixels[imgNum]): pixDir[(i-pixBlankEndPos[imgNum],j)][imgNum]=nubimgs[imgNum] """nubimgs[0].putpixel ((i,j),nubimgs[imgNum].getpixel((i,j)))""" '''下面將只有n個數字有的像素存到對應的文件夾中''' for pix in pixDir.items(): if pix[1].__len__()<=6: print pix i=0 for pic in pix[1].items(): i+=1 if not os.path.exists(path+str(pix[1].__len__())): os.mkdir(path+str(pix[1].__len__())) pic[1].save(os.path.join(path+str(pix[1].__len__()),str(pix[0][0])+"_"+str(pix[0][1])+"__"+str(i)+".bmp"))
材料圖片:
解析結果如下
對應的文件夾中就放着n個圖片共享的像素,接下來的分析我是手動分析的,其實也可以用程序寫,不過要預先告訴程序哪個片段是什么數字,可以通過把圖片名起為對應驗證碼來解析。因為這是后想到的,就沒有實現了。
2.接下來就是使用得到的特征值來解析驗證碼
下面的方法用來取得背景色,方法同上面解析一樣,沿圖片最上面一層取顏色,因為最上面不繪制
def getBackColors(bmp): list=[] for i in range(60): if bmp.getpixel((i,0)) not in list: list.append(bmp.getpixel((i,0))) return list
同上面解析一樣,取得首繪偏移值
def GetLastBlankPosition(materialPic,x=0): bc=getBackColors(materialPic) for i in range(15): for j in range(24): if materialPic.getpixel((i+x,j)) not in bc: return i
解析驗證碼,利用特征嗎判斷
def GetVaildJpgNumber(bmp): print 'GetVaildJpgNumber' vaildStr=""; backColors=getBackColors(bmp)
#對於一個驗證碼的4個數字分別驗證,其x范圍為n*15~(n+1)*15 for pos in range(4):
#取得對應位置的首繪偏移值 offset=GetLastBlankPosition(bmp,pos*15)
#對於0-9,分別判斷對應的特征是否為背景色,如果不是解析完成,是背景色則判斷下一個數字,因為3的像素基本和其他圖像共享,所以如果最后沒有找到特定的數字,就是3 for nr in range(0,10): isthisNr=True for pix in NumberKeyPixel[nr]: if pix[0]+offset>=15: isthisNr=False break if bmp.getpixel((pix[0]+offset+pos*15,pix[1])) in backColors : isthisNr=False break; if isthisNr and NumberKeyPixel[nr].__len__()!=0 : vaildStr+=str(nr) break if vaildStr.__len__()==pos: vaildStr+='3' print vaildStr return vaildStr
從網絡抓取驗證碼,使用的是httplib,其中我們學校名我已替代為myschool
def GetVaildJpg (): print 'GetVaildJpg' headers={ 'Accept': 'image/png, image/svg+xml, image/*;q=0.8, */*;q=0.5', 'Referer': 'http://zcc.myschool.edu.cn/', 'Accept-Language': 'zh-Hans-CN,zh-Hans;q=0.8,en-US;q=0.5,en;q=0.3', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko', 'Accept-Encoding': 'gzip, deflate', 'Host': 'zcc.myschool.edu.cn', 'DNT': '1', 'Connection': 'Keep-Alive', 'Cookie': sessionId } httpClient=httplib.HTTPConnection('zcc.myschool.edu.cn',80,timeout=300) httpClient.request("GET",'http://zcc.myschool.edu.cn/image.jsp',None,headers) response=httpClient.getresponse() '''print response.getheaders()''' stBmp=response.read() bmp=Image.open(BytesIO(stBmp)) bmp.save('D:\PROJECT\PYTHON\catchDorm\catch.bmp') '''bmp.show()''' return GetVaildJpgNumber(bmp)
好的,現在一切OK,幾十次試驗都正確判斷。