简介
前段时间打算用谷歌driver自动登录学校的网站,方便后面继续开发脚本,主要还是想着以后也许会用到爬虫爬取图像等数据用来训练。
这个项目从最开始使用 pytorch 搭模型、爬验证码数据、以及训练改进,到后来觉得登录学校网站不用手打验证码挺方便的,就不断优化程序最后完成了一个快速登录的可执行文件,下面是一个demo演示。
这里打算开个坑写写训练的过程和用谷歌driver登录网站的设计思路,以及最后的一些优化日志。
验证码数据集获取
解析验证码网址
我们打开网址的登录页面,检查页面源代码,定位到验证码的位置,可以看到一个验证码的网页。
打开这个网页,显示的就是验证码的图片,但这个验证码不是原来的验证码,说明验证码是动态加载的,尽管网址一样但是内容是不相同的。可以使用爬虫工具下载这个图片,为了观察这个网址以便批量下载,我们刷新登录页面,并再次打开一个新的验证码网址。可以看到唯一的变化就是网址后缀上的 uuid 变化了。uuid 是一种标识码,后端算法会根据这个 uuid 生成一个验证码。
所以可以先随机生成一个 uuid 标识,然后拼接成完整的验证码网址,通过爬虫下载图片。
批量下载验证码
使用python(环境:python3.7)提供一系列的操作:
生成uuid并拼接网址
1
2
3import uuid #该库用于生成uuid,有多种方式
uuidx=str(uuid.uuid4())
url = 'https://jaccount.sjtu.edu.cn/jaccount/captcha?uuid='+uuidx通过爬虫下载验证码图片
1
2
3
4
5
6
7
8
9
10import requests
origin_path='captcha-sjtu/origin.jpg'
# 构造请求头
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.57'}
# 发送请求
res = requests.get(url=url,headers=headers)
# 把获取的二进制写成图片
with open(origin_path, 'wb') as f:
f.write(res.content)获得了一张图片后,我们需要将它命名为它的验证码识别结果,作为训练数据和测试数据的标签。但我们不可能人眼去识别和手动修改,这样工作量太大了,因此可以选择外接库来帮我们识别(本意不是识别出验证码,而是学习自己搭网络来训练,因此尽管有外接库,还是希望自己能完成一个网络)。这里选择ddddocr这个库,用法很简单
1
2
3
4
5
6
7
8
9
10
11
12
13
14import ddddocr
import os
ocr = ddddocr.DdddOcr(use_gpu=True) #实例化一个识别器,使用gpu
with open(origin_path, 'rb') as f:
img_bytes = f.read() #读入二进制数据
try: #可能上面的uuid拼接的url弄的不是一个验证码
res = ocr.classification(img_bytes) #识别的结果
except:
continue #如果url不是验证码就跳过,无所谓。不用try语句的话会中断
newname='captcha-sjtu/train/'+res+'.jpg'
try:
os.rename(origin_path, newname) #可能已经有一个同名的了,为了不让程序中断,还是使用try
except:
continue至此就处理完了一张图片,下面为批量处理的完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import requests
import time
import uuid
import os
import ddddocr
ocr = ddddocr.DdddOcr(use_gpu=True)
origin_path='captcha-sjtu/origin.jpg'
for i in range(10000):
# 每爬取500个,歇1秒,确保服务器不会受影响
if i%500 == 0:
print("-----",i/1000,'组-----------')
time.sleep(1)
# 生成随机数
uuidx=str(uuid.uuid4())
url = 'https://jaccount.sjtu.edu.cn/jaccount/captcha?uuid='+uuidx
# 构造请求头
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.57'}
# 发送请求
res = requests.get(url=url,headers=headers)
# 把获取的二进制写成图片
with open(origin_path, 'wb') as f:
f.write(res.content)
# 再读取回来
with open(origin_path, 'rb') as f:
img_bytes = f.read()
try:
res = ocr.classification(img_bytes)#可能上面的uuid拼接的url弄的不是一个验证码
except:
continue
newname='captcha-sjtu/train/'+res+'.jpg'
try:
os.rename(origin_path, newname)#可能已经有一个同名的了
except:
continue
pytorch 模型搭建
构建 Dataset
将数据集分成9:1,并预留一些数据用于验证。首先定义一些参数设置,写在setting.py内。通过观察验证码,可以得到图片的宽度和高度,以及内容和长度。这里验证码的内容都是小写字母,因此我们只需要小写字母的字母表;而验证码长度是4或者5,对于CNN网络来说,需要接受一个固定大小的输入,然后输出一个固定大小的标签等等。
因此我们没办法同时识别长度为4和长度为5的验证码,但根据观察发现这两种长度的验证码出现的频率几乎是1:1的,那么在登录验证时只需要不断 try 尝试即可,而且并不会造成很多的时间浪费。以下为setting的内容。
1 | # setting.py |
随后,我们要重写Dataset,大部分工作在重写_getitem_()方法,返回处理后的图像和标签,于是我们需要先考虑这个标签的形式。我们似乎可以使用一个字母表大小(里面也可以包括数字等等)的一维向量,比如全是小写字母那么我的向量长度就为26,然后将验证码图片出现的字母映射在向量对应的索引处:如果字母出现则为1,不出现则为0。但这种方法,一方面没办法表示验证码的顺序,一方面没办法识别有重复字母的验证码。
因此考虑对每个字母都建立一个长度为26的向量进行映射,因此向量的总长度就是验证码长度×字母表长度。
对于一个图片,前面使用了它的名称作为验证码结果,因为我们下载的时候并没有区别长度,因此这里长度不一致的数据要剔除,执行一次continue即可。然后,我们把验证码字符串的每一个字符都映射到一个向量上,在python中可以使用str.find(),这里的str即为我们的字母表。然后我们把这些向量都拼接起来,就构成了label。
make_dataset()函数会返回图片路径、图片label,最后我们Dataset中使用它重写_getitem_()方法。对应的dataset.py文件如下。
1 | # datasets.py |
构建CNN model
接着,我们搭建一个CNN网络,这个网络不能太大,因为我们的标签向量本身很大,如果只是用自己机器训练的话,gpu内存可能不够用(全连接层的参数尤其多)。要查看gpu的占用,可以在任务管理器–性能–GPU1处实时监控专用GPU内存利用率(GPU0一般是处理器的而非显卡),如下图
我最后选择了一个可以训练的模型,使用四层卷积和两层全连接层,每次卷积后使用一个2×2的最大池化,接着批归一化,最后使用ReLU激活函数。因为学校网址的验证码本身不是很复杂,训练后可以针对长度4和长度5的验证码都可以达到96%的准确率。
实际上,并不需要多高的准确率,因为try一次的时间甚至不到1秒钟(当然调整网络结构优化识别性能是一件很有趣的事情)。model.py的代码如下。
1 | # model.py |
训练模型
训练代码如下,每一轮迭代都会保存模型参数到给定文件夹。最终观察表现最好的参数模型,手动删除其他不好的,并可以修改一下命名,以防之后重新训练覆盖了这个参数模型。
1 | #train.py |
模型效果测试
最后我们可以看看我们模型的效果,下面是一些预测的示例。
预测部分的代码如下,至此,整个模型训练部分就完成了,登录网站的部分见下一篇博客。
1 | #predict.py |