中⼭⼤学⽻⽑球场馆⾃动订场(Python+selenium+百度aip)双鸭⼭南校⼈太多,⼩伙伴们⽇常约球抢不到室内的场馆,只好去室外打。所以趁考完试有时间写了⼀个⾃动抢⽻⽑球场的脚本,⽹好的时候20秒订场⽆压⼒。下⾯来分享⼀波这个脚本的⼀些技术细节(重点讲⼀下图像降噪和百度云的⽂字识别接⼝),也算是对我这两天学习的⼀个总结。
其实在⼤概在⼀年之前在⾃动操作浏览器上selenium+phantomjs还是标配,但是随着17年8⽉firefox和chrome相继推出了⽆头模式的浏览器完美地取代了phantomjs,所以selenium 3.8.1以上的版本都不再⽀持phantomjs。这个脚本⽤的是headless firefox,与以往Selenium不同的是需要额外加载selenium firefox 的官⽅Webdriver:Geckodriver,不然会报错。另外⼀个要注意的是如果想⽤headless firefox需要电脑本⾝已经安装了firefox(注意⾄少是56.0以上的版本,否则不⽀持⽆头模式)。
⾸先配置好driver并获取鸭⼤NetID登录页⾯信息:
from selenium import webdriver
driver = webdriver.Firefox(executable_path='.../geckodriver')#根据geckodriver所在路径配置driver
<("gym.sysu.edu/product/show.html?id=61")#获取页⾯信息
driver.maximize_window()#最⼤化窗⼝
driver.find_element_by_xpath("//a[contains(text(),'登录')]").click()#跳转到登录页⾯
配置好driver后,我们就可以⽤python在后台操作⽹页啦,⼀些常⽤的操作:
<("gym.sysu.edu/index.html")#打开⽹页
driver.find_element_by_xpath("//a[@href='/product/index.html']").click()#⽤click()点击⽤xpath定位到的⽹页元素
driver.find_element_by_xpath("//input[@id='txt_name']").send_keys('英东⽻⽑球场')#⽤send_keys()向输⼊框中填⼊数据
driver.save_screenshot(screenshotadd)#保存当前页⾯截图
driver.page_source()#获取当前页⾯源代码
driver.current_url#当前页⾯的url
我们需要对登录页⾯截图,根据验证码的xpath定位验证码在整张截图的位置并截取保存验证码到本地:
def Convertimg():
imglocation = ("//img[@name='captchaImg']")#验证码的xpath地址
driver.save_screenshot(screenshotadd)
im = Image.open(screenshotadd)
left = driver.find_element_by_xpath(imglocation).location['x']
top = driver.find_element_by_xpath(imglocation).location['y']
right = driver.find_element_by_xpath(imglocation).location['x'] + driver.find_element_by_xpath(imglocation).size['width']
bottom = driver.find_element_by_xpath(imglocation).location['y'] + driver.find_element_by_xpath(imglocation).size['height']
im = im.crop((left, top, right, bottom))
im.save(codeadd)
原始的验证码是这样的:
虽然现在百度⾕歌阿⾥都有各⾃的将图像识别成⽂字的python接⼝,但是这些接⼝在图像有噪⾳时普遍很差。⽐如这⾥的验证码有多条直线穿过,如果直接⽤像pytesseract这样的图像识别接⼝来识别的话基本上成功率只有百分之⼏,也就是说可能要试100次才能成功登陆⼏次,那我这卡着12点抢场地的脚本肯定要凉。所以必须对验证码进⾏降噪再进⾏图像识别才⾏。事实上经过降噪处理的验证码识别⼀次成功率达到了80%以上。
但是我发现我们学校验证码上的噪⾳线都是⿊⾊的,⽽字符通常都是彩⾊的,这可是个⼤bug啊哈哈哈。所以我的降噪流程是:
1.将⿊⾊以及接近⿊⾊的噪⾳线(R,G,B值都接近0)替换为⽩⾊(R,G,B值都是255)
2.将第⼀步得到的图像进⾏⼆值处理变成⿊⽩图像,灰度阈值设为160都可,也就是低于这个阈值的点都填⽩⾊,⾼于这个阈值的点都填⿊⾊
3.提取边缘,使得字母和数字更清晰(虽然在这张图不太明显,但是经过⼏⼗张验证码的测试,这⼀步还是有必要的):
4.把图⽚适当变⼤⼀点,提⾼⽂字识别的精度
降噪部分的代码:
def clearimage(originadd):
img = Image.open(originadd)#读取系统的内照⽚
#将⿊⾊⼲扰线替换为⽩⾊
width = img.size[0]#长度
height = img.size[1]#宽度
for i in range(0,width):#遍历所有长度的点
for j in range(0,height):#遍历所有宽度的点
data = (pixel((i,j)))#打印该图⽚的所有点
if (data[0]<=25 and data[1]<=25 and data[2]<=25):#RGBA的r,g,b均⼩于25
img.putpixel((i,j),(255,255,255,255))#则这些像素点的颜⾊改成⽩⾊
img = vert("RGB")#把图⽚强制转成RGB
img.save(repadd)#保存修改像素点后的图⽚
#灰度化
Grayimg = cv2.cvtColor(cv2.imread(repadd), cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(Grayimg, 160, 255,cv2.THRESH_BINARY)
cv2.imwrite(greyadd, thresh)
#提取边缘
edimg = Image.open(greyadd)
conF = edimg.filter(ImageFilter.CONTOUR)
conF.save(edadd)
#改变图⽚尺⼨
def ResizeImage(filein, fileout, width, height, type):
img = Image.open(filein)
out = size((width, height),Image.ANTIALIAS)
out.save(fileout, type)
其实按照图像识别的套路,在正式识别之前是要把图⽚的字符切成⼀个⼀个的然后分别识别的,但是我降噪做得⽐较⼲净不切⽚也能识别的很好加上学校⽹站允许填⼊的验证码之间有空格所以就没有切⽚后分别识别,这样运⾏速度也快⼀点。
在图像降噪上也有⼀些流⾏的算法,但是我们对我鸭⼤的验证码效果都不太好,降完后识别率依然很低所以我就弃⽤了。但是这⾥也把这些算法放上来给⼤家参考:
#去除多余的点和多余的⼲扰线
im = Image.open(rgbadd)
data = im.getdata()
w, h = im.size
try:
for x in range(1, w - 1):
if x > 1 and x != w - 2:
# 获取⽬标像素点左右位置
left = x - 1
right = x + 1
for y in range(1, h - 1):
# 获取⽬标像素点上下位置
up = y - 1
down = y + 1
if x <= 2 or x >= (w - 2):
data.putpixel((x, y), 255)
elif y <= 2 or y >= (h - 2):
data.putpixel((x, y), 255)
pixel((x, y)) == 0:
if y > 1 and y != h - 1:
# 以⽬标像素点为中⼼点,获取周围像素点颜⾊
# 0为⿊⾊,255为⽩⾊
up_color = pixel((x, up))
down_color = pixel((x, down))
left_color = pixel((left, y))
left_down_color = pixel((left, down))
right_color = pixel((right, y))
right_up_color = pixel((right, up))
right_down_color = pixel((right, down))
# 去除竖线⼲扰线
if down_color == 0:
if left_color == 255 and left_down_color == 255 and \
right_color == 255 and right_down_color == 255:                            data.putpixel((x, y), 255)
# 去除横线⼲扰线
elif right_color == 0:
if down_color == 255 and right_down_color == 255 and \                                up_color == 255 and right_up_color == 255:
data.putpixel((x, y), 255)
# 去除斜线⼲扰线
if left_color == 255 and right_color == 255 \
and up_color == 255 and down_color == 255:
data.putpixel((x, y), 255)
else:
pass
except:
pass
另⼀种降噪⽅法:
# 根据⼀个点A的RGB值,与周围的8个点的RBG值⽐较,设定⼀个值N(0 <N <8),当A的RGB值与周围8个点的RGB相等数⼩于N时,此点为噪点
# G: Integer 图像⼆值化阀值
# N: Integer 降噪率 0 <N <8
# Z: Integer 降噪次数
# 输出
#  0:降噪成功
#  1:降噪失败
def clearNoise(image, N, Z):
for i in range(0, Z):
t2val[(0, 0)] = 1
t2val[(image.size[0] - 1, image.size[1] - 1)] = 1
for x in range(1, image.size[0] - 1):
for y in range(1, image.size[1] - 1):
nearDots = 0
L = t2val[(x, y)]
if L == t2val[(x - 1, y - 1)]:
nearDots += 1
导航页源码
if L == t2val[(x - 1, y)]:
nearDots += 1
if L == t2val[(x - 1, y + 1)]:
nearDots += 1
if L == t2val[(x, y - 1)]:
nearDots += 1
if L == t2val[(x, y + 1)]:
nearDots += 1
if L == t2val[(x + 1, y - 1)]:
nearDots += 1
if L == t2val[(x + 1, y)]:
nearDots += 1
if L == t2val[(x + 1, y + 1)]:
nearDots += 1
if nearDots < N:
t2val[(x, y)] = 1
下⾯⼀步就是对降噪完成的图⽚进⾏⽂字识别得到验证码啦,这⾥⼀定要吹⼀波百度的⽂字识别接⼝baidu-aip,私以为做得相当不错,在中⽂的图像识别上有碾压⾕歌的pytesseract的趋势。来张⽆噪声的图感受⼀下:
百度baidu-aip接⼝识别结果:
蒹葭
先秦:佚名
蒹葭苍苍,⽩露为霜。所谓伊⼈,在⽔⼀⽅。
溯洄从之,道阻且长。溯游从之,宛在⽔中央。
蒹葭萋萋,⽩露未晞。所谓伊⼈,在⽔之湄。
溯洄从之,道阳且跻。溯游从之,宛在⽔中坻。
蒹葭采采,⽩露未已。所谓伊⼈,在⽔之涘。
溯洄从之,道阻且右。溯游从之,宛在⽔中沚。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。