IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> 与打卡系统的斗智斗勇——从Selenium到Pytorch -> 正文阅读

[开发测试]与打卡系统的斗智斗勇——从Selenium到Pytorch

好久没写博客了,近期刚刚升级完自己写的自动打卡程序,来分享一下自己解决问题的思路。

起因

自2020年寒假爆发疫情以来,我们的生活都发生了不小的变化,尤其是学生,在家上了一个学期的网课。后续回复上学和生产以后,疫情依然挥之不去,所以有不少单位要求人员每天打卡登记信息。但是问题是,我们大部分时候信息都是没有变化的,所需要的就是每天点一下而已;除此之外,有很多人可能对此不够重视,导致经常遗忘(没错,正是本人)。所以,出于以上这些考虑,我决定写一个程序进行自动打卡,将该程序加入开机启动项,这样每次开机就可以自动打卡,再也不用担心忘记了。

最初的版本

实现思路

有两种思路实现,一种是直接发数据包,一种是控制浏览器。
我选择了后面一种,一来比较方便,二来比较炫酷。第一种方法也可以实现,我的室友实现了这种方法。

驱动准备

首先,需要选取浏览器驱动,我选择的是chrome浏览器,其实还可以选择一种后台驱动,就可以后台静默打卡,不过为了效果我就选择前者了。去chrome官网下载与你安装的chrome版本匹配的驱动(是驱动,不是浏览器本身,当然浏览器本身也要下载)
这里给出驱动的链接:chrome驱动
下载驱动以后,把它放在方便的目录下,比如D:/driver/,怎么方便怎么来
然后是配置环境变量,在系统变量的Path中添加上面你保存的路径,保存设置即可。

开始code

接下来就轮到我们的主角登场了,就是selenium库,这个库是web自动化工具库,专门用来处理这些问题。细节就不介绍了,直接说我的工作。

头文件

所需的头文件如下:

from selenium import webdriver
import selenium

import time
import sys
import socket

等待连接网络

众所周知,电脑启动以后还需要一定的准备时间才会连接网络完毕,所以我们需要采用轮询的方式反复尝试连接网络,直到可以连接上目标网络
首先是测试网络的函数,我们采用socket建立连接来判断是否联网,如果建立连接发生错误说明没联网,返回False,代码如下:

def is_online():
    try:
        host=socket.gethostbyname("your website")# 这里填入你的目标网络
        s=socket.create_connection((host,80),2)
        return True
    except Exception as e:
        return False

测试就写一个循环测试即可:

while(not is_online()):
    i=0

打开网页

打开网页分两步,先创建驱动器,再用驱动器打开网页,代码如下:

driver = webdriver.Chrome()
driver.get("your website")

定位元素与填入密码

这里需要观察网页源代码,用F12查看,查看用户名和密码框的id或者class
然后用find_element_by_id()或者find_element_by_name()找到对应元素,然后用send_keys()即可将文本填入其中:

driver.find_element_by_id("username").send_keys("your username")
driver.find_element_by_id("password").send_keys("your password")

点击按钮登录打卡

和上述方法一样找到登录按钮,然后调用click()即可实现点击:

driver.find_element_by_id("login").click()

这样就基本介绍完打卡的所有操作了,至于具体怎么打卡,就运用上面的知识操作即可,因为大家打卡网页都五花八门,就不细说了。

被验证码制裁——妥协

问题

好景不长(大概有一年),某一天,突然发现,如果不在学校内网打卡,在登录时需要输入验证码,被狠狠地制裁了一波。但是打卡还是得继续,开学前打卡缺少还会被批。必须得想办法解决。

粗糙的解决办法

当时有点懒,于是选择了手动输入验证码的方案。
程序输入用户密码后,用a=input()等待输入验证码,人工输入验证码后,在控制台随便属于一个字符即可继续。
有的人可能会觉得这样不是更麻烦了?其实单看一个打卡,这个程序可能确实也不方便。但是我在后续又添加了很多额外功能,比如自动检查邮箱是否有未读邮件以及检查图书馆借书是否过期,这样的话,这个程序的价值还是有的。

被验证码制裁——反击

提出解决方案

之前因为懒,被制裁了也就直接妥协,孬好还是有用的。但是最近因为兴致来了,我决定反击,彻底解决这个问题,不再妥协!
首先观察验证码,这个验证码可以说是非常的low,反复刷新得到如下图片:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
相信大家都可以看出其中的特性:

  1. 数字位置非常固定
  2. 数字几乎一样,没有变化

于是我想,能不能用ai去识别图片,从而实现自己填入验证码。
很nice的想法!
说干就干!

数据集准备

数据源获取

训练模型肯定需要数据集,那么数据从哪儿来呢?
验证码就是不愁缺!直接从登录网站上爬取即可。
这里我们需要另一个库requests用来获取图片:

for i in range(100):
    link="link"+str(i) #验证码链接
    img=requests.get(link)
    time.sleep(1)
    file_out=open("./data/img"+str(i)+".png","wb")
    file_out.write(img.content)
    file_out.close()

这样我们就可以获取大量图片:
在这里插入图片描述

数据处理

下载完图片还不够,我们的模型肯定只识别单个数字,如果直接把四个数字的图片放里面,就需要10000个分类,准备的数据也需要更多(不是改个数字的事儿?并不是,因为你需要自己打标签,自己要把所有图片看一遍并把结果写下来)。所以我们需要对图片进行切割。
除了切割,因为这里我们并不需要图片的颜色信息,所以可以转换成灰度图,这样更利于模型训练。所以我们对down下来的图片进行如上处理,其中的位置定位可以自己先用一张图片尝试:

# train
for i in range(20):
    img=Image.open("./data/img"+str(i)+".png")
    data=np.asarray(img.convert('L'))
    num1=data[:,25:47]
    num2=data[:,45:67]
    num3=data[:,67:89]
    num4=data[:,89:111]
    pic=Image.fromarray(num1)
    pic.save("./data/train/img"+str(count)+".png")
    count+=1
    pic=Image.fromarray(num2)
    pic.save("./data/train/img"+str(count)+".png")
    count+=1
    pic=Image.fromarray(num3)
    pic.save("./data/train/img"+str(count)+".png")
    count+=1
    pic=Image.fromarray(num4)
    pic.save("./data/train/img"+str(count)+".png")
    count+=1

count=0
# test
for i in range(20,25):
    img=Image.open("./data/img"+str(i)+".png")
    data=np.asarray(img.convert('L'))
    num1=data[:,25:47]
    num2=data[:,45:67]
    num3=data[:,67:89]
    num4=data[:,89:111]
    pic=Image.fromarray(num1)
    pic.save("./data/test/img"+str(count)+".png")
    count+=1
    pic=Image.fromarray(num2)
    pic.save("./data/test/img"+str(count)+".png")
    count+=1
    pic=Image.fromarray(num3)
    pic.save("./data/test/img"+str(count)+".png")
    count+=1
    pic=Image.fromarray(num4)
    pic.save("./data/test/img"+str(count)+".png")
    count+=1

这里处理了80个训练集,20个测试集。
差不多就可以了,这个识别容易,不用这么多,多了自己打标签太麻烦。
处理后的效果如下:

在这里插入图片描述

打标签

接下来就是数据准备的最后一步了,就是打标签。
自己把所有图片识别一遍,把答案写下来,如:
在这里插入图片描述
至此,我们的数据就准备完毕了。

模型准备

模型的话,采取的时VGGNet,实现的模型如下:

class MyNet(nn.Module):
    
    def __init__(self):
        super(MyNet,self).__init__()
        self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
        self.conv1 = nn.Conv2d(1,64,3,padding=1)        # 64*32*32
        self.conv2 = nn.Conv2d(64,64,3,padding=1)       # 64*32*32
        self.pool1 = nn.MaxPool2d(2, 2)                 # 64*16*16
        self.bn1 = nn.BatchNorm2d(64)                   # 64*16*16
        self.relu1 = nn.ReLU()                          # 64*16*16

        self.conv3 = nn.Conv2d(64,128,3,padding=1)      # 128*16*16
        self.conv4 = nn.Conv2d(128, 128, 3,padding=1)   # 128*16*16
        self.pool2 = nn.MaxPool2d(2, 2, padding=1)      # 128*9*9
        self.bn2 = nn.BatchNorm2d(128)                  # 128*9*9
        self.relu2 = nn.ReLU()                          # 128*9*9

        self.conv5 = nn.Conv2d(128,128, 3,padding=1)    # 128*9*9
        self.conv6 = nn.Conv2d(128, 128, 3,padding=1)   # 128*9*9
        self.conv7 = nn.Conv2d(128, 128, 1,padding=1)   # 128*11*11
        self.pool3 = nn.MaxPool2d(2, 2, padding=1)      # 128*6*6
        self.bn3 = nn.BatchNorm2d(128)                  # 128*6*6
        self.relu3 = nn.ReLU()                          # 128*6*6

        self.conv8 = nn.Conv2d(128, 256, 3,padding=1)   # 256*6*6
        self.conv9 = nn.Conv2d(256, 256, 3, padding=1)  # 256*6*6
        self.conv10 = nn.Conv2d(256, 256, 1, padding=1) # 256*8*8
        self.pool4 = nn.MaxPool2d(2, 2, padding=1)      # 256*5*5
        self.bn4 = nn.BatchNorm2d(256)                  # 256*5*5
        self.relu4 = nn.ReLU()                          # 256*5*5

        self.conv11 = nn.Conv2d(256, 512, 3, padding=1) # 512*5*5
        self.conv12 = nn.Conv2d(512, 512, 3, padding=1) # 512*5*5
        self.conv13 = nn.Conv2d(512, 512, 1, padding=1) # 512*7*7
        self.pool5 = nn.MaxPool2d(2, 2, padding=1)      # 512*4*4
        self.bn5 = nn.BatchNorm2d(512)                  # 512*4*4
        self.relu5 = nn.ReLU()                          # 512*4*4

        self.fc14 = nn.Linear(512*4*4,1024)             # 1*1024
        self.drop1 = nn.Dropout2d()                     # 1*1024
        self.fc15 = nn.Linear(1024,1024)                # 1*1024
        self.drop2 = nn.Dropout2d()                     # 1*1024
        self.fc16 = nn.Linear(1024,10)                  # 1*10

    def forward(self,x):
        x = x.to(self.device)  # 自加
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.pool1(x)
        x = self.bn1(x)
        x = self.relu1(x)


        x = self.conv3(x)
        x = self.conv4(x)
        x = self.pool2(x)
        x = self.bn2(x)
        x = self.relu2(x)

        x = self.conv5(x)
        x = self.conv6(x)
        x = self.conv7(x)
        x = self.pool3(x)
        x = self.bn3(x)
        x = self.relu3(x)

        x = self.conv8(x)
        x = self.conv9(x)
        x = self.conv10(x)
        x = self.pool4(x)
        x = self.bn4(x)
        x = self.relu4(x)

        x = self.conv11(x)
        x = self.conv12(x)
        x = self.conv13(x)
        x = self.pool5(x)
        x = self.bn5(x)
        x = self.relu5(x)
        # print(" x shape ",x.size())
        x = x.view(-1,512*4*4)
        x = F.relu(self.fc14(x))
        x = self.drop1(x)
        x = F.relu(self.fc15(x))
        x = self.drop2(x)
        x = self.fc16(x)

        return x

开始训练

下面就是训练模型了,训练的代码就不细说了,没什么技巧,就是典型的训练流程:

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 超参数设置
EPOCH = 30  #遍历数据集次数
pre_epoch = 0  # 定义已经遍历数据集的次数
BATCH_SIZE = 20      #批处理尺寸(batch_size)
LR = 0.01        #学习率

mean_v=0.4914
scale_v=0.2023

# normalize=transforms.Normalize(0.4914, 0.2023)
# 读取train数据
label_file=open("data/train/label.txt","r")
label_data=label_file.read()
y=label_data.split(',')
yy=[]
for i in range(len(y)//BATCH_SIZE):
    temp_y=[]
    for j in range(BATCH_SIZE):
        temp_y.append(int(y[i*BATCH_SIZE+j])) 
    yy.append(torch.tensor(temp_y))
x=[]
for i in range(80//BATCH_SIZE):
    temp_x=[]
    for j in range(BATCH_SIZE):
        img=Image.open("./data/train/img"+str(i*BATCH_SIZE+j)+".png")
        data=np.asarray(img)
        # data=torch.from_numpy(data)
        # data=data/255
        # data=normalize(data)
        temp_x.append(data.reshape((32,22,1)).tolist())
    temp=torch.tensor(temp_x)
    temp=temp/255
    temp=(temp-mean_v)/scale_v
    x.append(temp.permute(0,3,1,2))
    
train_data=list(map(list,zip(x,yy)))

# 读取test数据
label_file=open("data/test/label.txt","r")
label_data=label_file.read()
y=label_data.split(',')
for i in range(len(y)):
    y[i]=torch.tensor(int(y[i]))
x=[]
for i in range(20):
    img=Image.open("./data/test/img"+str(i)+".png")
    data=np.asarray(img)
    data=torch.from_numpy(data.reshape((1,32,22,1))).permute(0,3,1,2)
    data=data/255
    data=(data-mean_v)/scale_v
    x.append(deepcopy(data))

test_data=list(map(list,zip(x,y)))


# net = ResNet18().to(device)
net = MyNet().to(device)

if pre_epoch>0:
    net.load_state_dict(torch.load('model/net_%03d.pth' % (pre_epoch)))

# 定义损失函数和优化方式
criterion = nn.CrossEntropyLoss()  
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9, weight_decay=5e-4) 

for epoch in range(EPOCH):
    net.train()
    sum_loss = 0.0
    correct = 0.0
    total = 0.0
    print(len(train_data))
    for x,y in train_data:
        x=x.clone()
        y=y.clone()
        optimizer.zero_grad()
        x,y=x.to(device),y.to(device)
        outputs=net(x)
        loss=criterion(outputs,y)
        sum_loss+=loss.item()
        loss.backward()
        optimizer.step()
        _, predicted = torch.max(outputs.data, 1)
        total += y.size(0)
        correct += predicted.eq(y.data).cpu().sum()
        print('[epoch:%d] Loss: %.03f | Acc: %.3f%% '
              % (epoch + 1, sum_loss / (i + 1), 100. * correct / total))
    print("Waiting Test!")
    with torch.no_grad():
        correct = 0
        total = 0
        for images, labels in test_data:
            net.eval()
            images, labels = images.clone().to(device), labels.clone().to(device)
            outputs = net(images)
            # 取得分最高的那个类 (outputs.data的索引号)
            _, predicted = torch.max(outputs.data, 1)
            total += 1
            correct += (predicted == labels).sum()
        print('测试分类准确率为:%.3f%%' % (100 * correct / total))
        print('Saving model......')
        torch.save(net.state_dict(), 'model/net_%03d.pth' % (epoch + 1))
        
        
print("Training Finished, TotalEPOCH=%d" % EPOCH)

训练可以发现,loss在稳步下降,测试集的正确率也上升到了100%。

实践尝试

首先把我们的模型拉出来试试效果如何。
代码如下:

	mean_v=0.4914
    scale_v=0.2023
    
    link="link"#验证码链接
    img=requests.get(link)
    time.sleep(1)
    file_out=open("./data/img_test.png","wb")
    file_out.write(img.content)
    file_out.close()
    img=Image.open("./data/img_test.png")
    img.show()
    data=np.asarray(img.convert('L'))
    num1=data[:,25:47]
    num2=data[:,45:67]
    num3=data[:,67:89]
    num4=data[:,89:111]
    
    num1=torch.from_numpy(num1.reshape((1,32,22,1))).permute(0,3,1,2)
    num1=num1/255
    num1=(num1-mean_v)/scale_v
    
    num2=torch.from_numpy(num2.reshape((1,32,22,1))).permute(0,3,1,2)
    num2=num2/255
    num2=(num2-mean_v)/scale_v
    
    num3=torch.from_numpy(num3.reshape((1,32,22,1))).permute(0,3,1,2)
    num3=num3/255
    num3=(num3-mean_v)/scale_v
    
    num4=torch.from_numpy(num4.reshape((1,32,22,1))).permute(0,3,1,2)
    num4=num4/255
    num4=(num4-mean_v)/scale_v
    
    net=MyNet()
    net.load_state_dict(torch.load('model/net_%03d.pth' % (30)))
    
    text=""
    net.eval()
    ans=net(num1)
    _, predicted = torch.max(ans.data, 1)
    text+=str(predicted.numpy()[0])
    ans=net(num2)
    _, predicted = torch.max(ans.data, 1)
    text+=str(predicted.numpy()[0])
    ans=net(num3)
    _, predicted = torch.max(ans.data, 1)
    text+=str(predicted.numpy()[0])
    ans=net(num4)
    _, predicted = torch.max(ans.data, 1)
    text+=str(predicted.numpy()[0])
    print(text)

基本就是随机down一张验证码,显示图片,然后输出识别结果
经过我自己的反复测试,效果还是很好的,都是正确的。
接下俩就是把这个代码嵌入我们的打卡程序。

最终实现

接下来就是把这部分嵌入我们的打卡程序。我们把上面实践尝试的代码打包成一个函数,该函数返回验证码的字符串,然后在打卡程序中填写验证码时调用函数即可。具体代码为:

driver.find_element_by_id("validate").send_keys(get_validatecode(cookies[1]['value']) )

其中get_validatecode()就是获取验证码的函数,函数的内容就是之前的代码,此处不再赘述。但是还涉及一个问题,就是如何down图片,我们是不能单纯的访问链接去获取验证码,因为每次访问链接都会获得不同的图片。
不过,验证码的验证并不是在本地进行的,而是在云端进行的,所以实际上比较的是我们上传的 字符和云端最后一次给的图片是否一致,那么云端需要处理很多链接,怎么区分呢?我们打开burpsuite进行抓包,可以发现,在首次打开登录界面的时候,会收到一个jsessionid的cookie,接下来每次申请验证码时都会附上这个cookie。
在这里插入图片描述

依据这个原理,我们只需要获取cookie以后,附上这个cookie来请求验证码,这样就可以让获取到的验证码和云端验证的保持一致,这样就可以实现识别验证码。
加上这最后一步,就完成了整个程序,整个程序就可以实现整个打卡流程。

结语

不知道以后学校还会不会升级系统,有能力就会继续做下去。
这次写出这篇博客,并不是说代码有多复杂,这个网络有多难训练,实际上这些都没什么难度,主要是喜欢这个编程的过程,自己准备数据集,自己处理数据,coding,抓包分析,一个较为综合的解决问题的过程,所以在这里和大家分享这一过程。

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2022-03-08 22:52:24  更:2022-03-08 22:53:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/18 2:57:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码