前言
最近在爬取数据的过程当中遇到验证码问题,正好遇到的是比较容易的,是字母和数字的验证码。本文就描述一下常规破解的方式。
主要内容包括:
图片二值化
图片降噪
图片切割
矢量计算
验证码获取
要破解验证码,第一步需要将我们看到的验证码下载下来。
我们点击我们要破解的验证码,然后按下F12,看一下浏览器提交的请求,可以看到验证码的图片。
然后我们写一个captchaDownload.py的程序:
--------------------------------------------------------------------------------
/* ---示例代码----*/
import urllib.request
from PIL import Image
import io
imagePath = 'D:/Yonyou/验证码yy/'
for count in range(0,1000):
checkCodeUrl = 'https://euc.yonyoucloud.com/images/getValiImage?ts=1502786693000289491'
headers = {'Referer':'https://euc.yonyoucloud.com/usercenter/emailMOD'}
headers['Host'] = 'euc.yonyoucloud.com'
headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36'
headers['Connection'] = 'keep-alive'
request = urllib.request.Request(checkCodeUrl)
res = urllib.request.urlopen(request).read()
#print(res)
image = Image.open(io.BytesIO(res))
fullName = imagePath+'CheckCode'+str(count)+'.jpg'
image.save(fullName)
count += 1
/* ---示例代码----*/
--------------------------------------------------------------------------------
运行以上代码以后,在验证码yy文件夹下就下载了100个验证码图片。如下图:
这样,验证码图片就准备好了。
降噪及切割
可以看到字母和数字的背后有很多噪音,首先要考虑去除这些噪音。
可以参考prepareCaptcha.py文件。
--------------------------------------------------------------------------------
/* ---示例代码----*/
from PIL import Image
imagePath = 'D:/验证码yy/'
count = 0
for i in range(339,984):
imageFile = 'CheckCode'+str(i)+'.jpg'
image = Image.open(str(imagePath+imageFile))
imageGray = image.convert('L') #转化为灰度
#降低噪音,同时转化成二进制
threshold = 75
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
out = imageGray.point(table,'1')
#转回RGB格式
img2 = out.convert('L')
#截取字符
inletter = False
foundletter = False
start = 0
end = 0
letters = []
for x in range(0,img2.width):
for y in range(0,img2.height):
pix = img2.getpixel((x,y))
if pix != 255:
inletter = True
if foundletter == False and inletter == True:
foundletter = True
start = x
if foundletter == True and inletter == False:
foundletter = False
end = x
letters.append((start,end))
inletter = False
#只保留切割后最大的四个图片
lens = []
if len(letters)>4:
for letter in letters:
temp = letter[1]-letter[0]
lens.append(temp)
#对宽度进行排序
lens.sort(reverse=True)
#小于第四个宽度的则删除
for letter in letters:
if (letter[1]-letter[0])
savePath = 'D:/验证码yy/切割后图片/'
for letter in letters:
tmpImg = img2.crop((letter[0],0,letter[1],img2.height))
fullName = 'D:/验证码yy/切割后图片/'+str(count)+'.jpg'
tmpImg.save(fullName)
count += 1
print(count)
/* ---示例代码----*/
--------------------------------------------------------------------------------
程序运行后,可以看到,切割好的图片。
字符匹配
因为这些字符采用都是标准字符,没有变形,所以我们可以将标准的文字库与截取的图片进行对比,对比的方式就是将截取图片和标准图片处理成两个向量,然后计算两个向量的相似度。计算的方式就是将每个图片与标准库中的每个图片计算cos值,最小的就是最接近的。
使用的是 AI与向量空间图像识别 将标准图片转换成向量坐标a,需要识别的图片字段为向量坐标b,cos(a,b)值越大说明夹角越小,越接近重合。
空间向量的计算公式:
核心代码如下:
--------------------------------------------------------------------------------
/* ---示例代码----*/
# 夹角公式
class VectorCompare:
# 计算矢量大小
# 计算平方和
def magnitude(self, concordance):
total = 0
for word, count in concordance.items():
total += count ** 2
return math.sqrt(total)
# 计算矢量之间的 cos 值
def relation(self, concordance1, concordance2):
topvalue = 0
for word, count in concordance1.items():
if word in concordance2:
# 计算相乘的和
topvalue += count * concordance2[word]
return topvalue / (self.magnitude(concordance1) * self.magnitude(concordance2))
# 将图片转换为矢量
def buildvector(im):
d1 = {}
count = 0
for i in im.getdata():
d1[count] = i
count += 1
return d1
/* ---示例代码----*/
--------------------------------------------------------------------------------