验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写。
是一种区分用户是计算机还是人的公共全自动程序。可以防止:恶意破解密码、刷票、论坛灌水,
有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试。
这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答CAPTCHA的问题,
所以回答出问题的用户就可以被认为是人类
对图像验证码来讲,这类验证码大多是数字、字母的组合,国内也有使用汉字的。在这个基础上增加噪点、干扰线、变形、重叠、不同字体颜色等方法来增加识别难度。
相应的这种验证码的识别大概分为以下几个步骤:
1.灰度处理
2.二值化
3.降噪
4.识别
滑动验证码也可以叫做行为验证,其中最出名的就是极验。现在极验验证码已经更新到了 3.0 版本,截至 2017 年 7 月全球已有十六万家企业正在使用极验,每天服务响应超过四亿次,广泛应用于直播视频、金融服务、电子商务、游戏娱乐、政府企业等各大类型网站。
滑动验证码的原理就是使用机器学习中的深度学习技术,根据一些特征来区分是否为正常用户。
比如,可以通过记录用户的滑动平均速度,还有每一小段时间的瞬时速度,用户鼠标点击情况,以及滑动后的匹配程度来识别。而且,不是说滑动到正确位置就是验证通过,而是根据特征识别来区分是否为真用户,滑到正确位置只是一个必要条件。
常见的点击类验证码都是给出一张包含文字的图片,通过文字提醒用户点击图中相同字的位置进行验证。
Python传统的图像处理库`PIL`(Python Imaging Library ),可以说基本上是Python处理图像的标准库,功能强大,使用简单。
但是由于`PIL`不支持Python3,而且更新缓慢。所以有志愿者在`PIL`的基础上创建了一个分支版本,命名为`Pillow`,`Pillow`目前最新支持到python3.6,更新活跃,并且增添了许多新的特性。所以我们安装Pillow即可。
Pillow
的安装比较的简单,直接pip安装即可:
pip install Pillow
但是要注意的一点是,Pillow
和PIL
不能共存在同一个环境中,所以如果安装的有PIL
的话,那么安装Pillow
之前应该删除PIL
。
由于是继承自PIL
的分支,所以Pillow
的导入是这样的:
import PIL
# 或者
from PIl import Image
Image
是Pillow中最为重要的类,实现了Pillow中大部分的功能。要创建这个类的实例主要有三个方式:
一般来说,我们都是都过从文件加载图像来实例化这个类,如下所示:
from PIL import Image
picture = Image.open('happy.png')
如果没有指定图片格式的话,那么Pillow
会自动识别文件内容为文件格式。
Pillow
新建空白图像使用new()
方法, 第一个参数是mode即颜色空间模式,第二个参数指定了图像的分辨率(宽x高),第三个参数是颜色。
#FF0000
表示红色。picture = Image.new('RGB', (200, 100), 'red')
picture.show()
保存图片的话需要使用save()
方法:
picture.save('happy.png')
保存的时候,如果没有指定图片格式的话,那么Pillow
会根据输入的后缀名决定保存的文件格式。
在Pillow中,用的是图像的左上角为坐标的原点(0,0),所以这意味着,x轴的数值是从左到右增长的,y轴的数值是从上到下增长的。
我们处理图像时,常常需要去表示一个矩形的图像区域。Pillow
中很多方法都需要传入一个表示矩形区域的元祖参数。
这个元组参数包含四个值,分别代表矩形四条边的距离X轴或者Y轴的距离。顺序是(左,顶,右,底)
。其实就相当于,矩形的左上顶点坐标为(左,顶)
,矩形的右下顶点坐标为(右,底)
,两个顶点就可以确定一个矩形的位置。
右和底坐标稍微特殊,跟python列表索引规则一样,是左闭又开的。可以理解为[左, 右)
和[顶, 底)
这样左闭右开的区间。比如(3, 2, 8, 9)就表示了横坐标范围[3, 7];纵坐标范围[2, 8]的矩形区域。
PIL.Image.filename
图像源文件的文件名或者路径,只有使用open()
方法创建的对象有这个属性。
类型:字符串
PIL.Image.format
图像源文件的文件格式。
PIL.Image.mode
图像的模式,一般来说是“1”, “L”, “RGB”, 或者“CMYK” 。
PIL.Image.size
图像的大小
PIL.Image.width
图像的宽度
PIL.Image.height
图像的高度
PIL.Image.info
图像的一些信息,为字典格式
Image
使用crop()
方法来裁剪图像,此方法需要传入一个矩形元祖参数,返回一个新的Image
对象,对原图没有影响。前两个表示左上顶点,后两个表示右下顶点
croped_im = im.crop((100, 100, 200, 200))
复制图像使用copy()
方法:
copyed_im = im.copy()
粘贴图像使用paste()
方法:
croped_im = im.crop((100, 100, 200, 200))
im.paste(croped_im, (0, 0))
im对象调用了paste()
方法,第一个参数是被裁剪下来用来粘贴的图像,第二个参数是一个位置参数元祖,这个位置参数是粘贴的图像的左顶点。
调整图像大小使用resize()
方法:
resized_im = im.resize((width, height))
resize()
方法会返回一个重设了大小的Image
对象。
旋转图像使用rotate()
方法,此方法按逆时针旋转,并返回一个新的Image
对象:
# 逆时针旋转90度
im.rotate(90)
im.rotate(180)
im.rotate(20, expand=True)
旋转的时候,会将图片超出边界的边角裁剪掉。如果加入expand=True
参数,就可以将图片边角保存住。
翻转图像使用transpose()
:
# 水平翻转
im.transpose(Image.FLIP_LEFT_RIGHT)
# 垂直翻转
im.transpose(Image.FLIP_TOP_BOTTOM)
使用getpixel
(xy)方法可以获取单个像素位置的值:
im.getpixel((100, 100))
传入的xy需要是一个元祖形式的坐标。
如果图片是多通道的,那么返回的是一个元祖。
split()
可以将多通道图片按通道分割为单通道图片:
R, G, B = im.split()
split()
方法返回的是一个元祖,元祖中的元素则是分割后的单个通道的值。
getchannel()
可以获取单个通道的数据:
R = im.getchannel("R")
我们可以使用load()
方法加载图片所有的数据,并比较方便的修改像素的值:
pixdata = im.load()
pixdata[100,200] = 255
此方法返回的是一个PIL.PyAccess
,可以通过这个类的索引来对指定坐标的像素点进行修改。
此方法会删除图片对象并释放内存
im.close()
sudo apt-get install tesseract -ocr
sudo apt-get install libtesseract-dev
https://www.cnblogs.com/jianqingwang/p/6978724.html
详细安装配置pytesseract
https://blog.csdn.net/yzh_2017/article/details/78840566
安装pytesseract
pip install pytesseract
测试,图片自备:
from PIL import Image
import pytesseract
im = Image.open("fonts_test.png")
#识别
text = pytesseract.image_to_string(im)
print(text)
from PIL import Image
image = Image.open("test.jpg")
#灰度处理
im = image.convert("L")
图像的二值化就是将图像上的像素点的灰度值设置为0或者255,也就是将整个图像呈现出明显的只有黑和白的视觉效果。
def binarizing(img,threshold=127):
"""传入image对象进行灰度、二值处理"""
img = img.convert("L") # 转灰度
pixdata = img.load()
w, h = img.size
# 遍历所有像素,大于阈值的为黑色
for y in range(h):
for x in range(w):
if pixdata[x, y] < threshold:
pixdata[x, y] = 0
else:
pixdata[x, y] = 255
return img
根据一个点A的RGB值,与周围8个RGB值进行比较,设定一个值N(0 通过查看网页可以发现滑动验证码的图片由两张图片组成。def Noise_reduction(img):
pixdata = img.load()
w,h = img.size
for y in range(1,h-1):
for x in range(1,w-1):
count = 0
if pixdata[x,y-1] > 245:#上
count = count + 1
if pixdata[x,y+1] > 245:#下
count = count + 1
if pixdata[x-1,y] > 245:#左
count = count + 1
if pixdata[x+1,y] > 245:#右
count = count + 1
if count > 4:
pixdata[x,y] = 255
return img
6、滑动型验证码破解
极验-验证码结构
需要注意的是在查看图片是可以发现每张图片是由52张小图片组合而成。
而每一张小图片其实都是一样的,通过偏移拼接出了正常的图片。逻辑实现
1.获取完整的图片及带缺口的图片
background_images=driver.find_elements_by_xpath(div_path)
location['x']=int(re.findall("background-image: url\(\"(.*)\"\); background-position: (.*)px (.*)px;",background_image.get_attribute('style'))[0][1])
location['y']=int(re.findall("background-image: url\(\"(.*)\"\); background-position: (.*)px (.*)px;",background_image.get_attribute('style'))[0][2])
image_url=re.findall("background-image: url\(\"(.*)\"\); background-position: (.*)px (.*)px;",background_image.get_attribute('style'))[0][0]
im = image.open(filename)
new_im = image.new('RGB', (260,116))
im_list_upper=[]
im_list_down=[]
for location in location_list:
if location['y']==-58:
im_list_upper.append(im.crop((abs(location['x']),58,abs(location['x'])+10,166)))
if location['y']==0:
im_list_down.append(im.crop((abs(location['x']),0,abs(location['x'])+10,58)))
new_im = image.new('RGB', (260,116))
x_offset = 0
for im in im_list_upper:
new_im.paste(im, (x_offset,0))
x_offset += im.size[0]
x_offset = 0
for im in im_list_down:
new_im.paste(im, (x_offset,58))
x_offset += im.size[0]
2. 计算缺口的值
pixel1=image1.getpixel((x,y))
pixel2=image2.getpixel((x,y))
for i in range(0,3):
if abs(pixel1[i]-pixel2[i])>=50:
return False
return True
image1.save('1.jpg')
image2.save('2.jpg')
i=0
for i in range(0,260):
for j in range(0,116):
if is_similar(image1,image2,i,j)==False:
return i
3. 根据缺口的位置模拟x轴移动的轨迹
# 移动的轨迹列表
track = []
# 当前位移
current = 0
# 减速阈值
mid = distance * 4 / 5
# 计算间隔
t = 0.2
# 初速度
v = 0
while current < distance:
if current < mid:
# 加速度为正2
a = 2
else:
# 加速度为负3
a = -3
# 初速度v0
v0 = v
# 当前速度v = v0 + at
v = v0 + a * t
# 移动距离x = v0t + 1/2 * a * t^2
move = v0 * t + 1 / 2 * a * t * t
# 当前位移
current += move
# 加入轨迹
track.append(round(move))
for i in range(5):
track.append(-1)
开始滑动
print("第一步,点击元素")
ActionChains(driver).click_and_hold(on_element=element).perform()
time.sleep(0.15)
print("第二步,拖动元素")
for track in track_list:
ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform()
ActionChains(driver).move_by_offset(xoffset=-1, yoffset=0).perform()
time.sleep(1)
print("第三步,释放鼠标")
ActionChains(driver).release(on_element=element).perform()
time.sleep(10)
补充
click_and_hold 点击鼠标左键,按住不放
move_by_offset 鼠标移动到距离当前位置(x,y)