python+selenium实现疫情期间全自动打卡

前言

因为疫情缘故,学校搞了个每日限时打卡的系统,要求学生在每天0-9点完成当日体温和在京状况的打卡。就这样手动打卡了两个多月,北京还是迟迟不开学,目测开学已经要到5月底了。打卡期间忘过无数次,每次都被班长提醒,学校还往家长手机里发送作者没有打卡的短信,神烦。
于是乎,决定使用 定时开关机软件 + python 实现一个全自动定时打卡的脚本,省却我接下来一个月的劳神费力。
学校的打卡系统登录界面是这样的:
在这里插入图片描述
这里是填写界面(左侧菜单栏需要依次点击数据采集和学生每日上报才能出现表格,否则是空白页面。表格很长,注意有纵向和横向滚动条)
在这里插入图片描述


问题分析

手动操作时的步骤是这样的:
进入学生登录界面–>输入账号密码
–>进入填报界面–>点击左侧菜单栏–>点击数据采集–>点击学生每日上报
–>进入表格–>点击表格左下角“与昨日情况一致”
–>手动填写当日体温–>滑动横向滚动条–>手动填写当日在京情况(一直在京)

因此在使用selenium时,其大致步骤与上面描述的无二。


网页源码分析与代码实现

一、加载火狐驱动

selenium需要模拟打开浏览器,这里一般使用的浏览器驱动(Driver)是谷歌或者火狐,笔者先尝试了谷歌的驱动,发现在源码分析的时候效果不是很理想,于是用了火狐的。
(关于驱动的下载与安装,这里不做赘述,百度即可查看。)
驱动加载并进入后,脚本会自动根据当前的驱动打开对应的火狐浏览器,并链接到提供url的网页。

driver = webdriver.Firefox()  # 利用火狐浏览器
# 填写疫情上报系统的url
url = r"http://tb.bucea.edu.cn:8075/WebReport/ReportServer?op\=fs_load&cmd=fs_signin&_=1586929099201"
driver.get(url)
# 最大化浏览器窗体
driver.maximize_window()

二、输入账号密码并提交

想要实现填写指定数据并自动填写到对应的位置,或者点击按钮提交到对应位置,就需要通过网页的各种标签的id或者class来定位相对应的元素,学过web编程的同学应该比较好理解这点。
例如下面这段用于填写用户名的代码

<div class="fs-login-input fs-login-input-username">
    <input tabindex="1" class="fs-login-username" type="text" placeholder="用户名" title="用户名">
</div>

我们可以看到,这部分的核心在于<input>标签,此标签没有指定id,而是给了class,因此可以使用selenium提供的driver.find_element_by_class_name()方法,通过class的值来定位元素,进而调用send_keys()方法来实现数据填充。
类似的,对于“点击”操作,只需要获取<button><radio><a>标签的class或者id,进而调用onclick()方法达到点击目的。
关于如何定位元素,可以参考这篇文章《史上最全!Selenium元素定位的30种方式》
笔记本上使用Fn+F12可以启用源代码查看器,点击页面中某个部分,便能自动锁定相应代码,非常好用。我们的页面是下图这样的,因此可以通过这段代码来定位“输入用户名”这个元素并设置数据:

elem = driver.find_element_by_class_name('fs-login-username')
elem.send_keys(stu_number)

在这里插入图片描述

# 疫情打卡系统url
url = r"http://tb.bucea.edu.cn:8075/WebReport/ReportServer?op=fs_load&cmd=fs_signin&_=1586929099201"
driver.get(url)
driver.maximize_window()
# 填写用户名和密码
elem = driver.find_element_by_class_name('fs-login-username')
elem.send_keys(stu_number)
elem = driver.find_element_by_class_name('fs-login-password')
elem.send_keys(stu_password)
# 提交表单
driver.find_element_by_xpath("//*[@id='fs-login-btn']").click()

三、进入打卡界面并点击左侧菜单栏

点击提交且用户验证成功后,会跳转到另外一个url,这个界面用于填写当日身体情况。依次点击左侧菜单栏的数据采集->学生每日上报,可以进入表格界面,如图:
在这里插入图片描述在第二节的分析中,笔者已经确定了定位元素的基本流程,按照selenium给定的方法,便可依次点开左侧菜单栏(这里每执行一步后,尽量使用time.sleep()让程序休息一小段时间,否则页面可能会卡住或者代码执行无效)。
但是! 随之而来的是困扰了我一天的地方。想要定位表格窗体的元素时,却怎么都定位不到,我试了好多地方,发现只有表格窗体中的元素是无法定位的。

1.iframe内元素的定位

学过web的同学知道,这种页面的header和菜单栏基本是固定的,通过内嵌<iframe>或者<frame>的方式,可以达到不同页面在相同url中切换、而指定部分(例如菜单栏 表头等)不变的目的。
于是我猜测元素定位不到可能与<iframe>的引入有关,也就是说,菜单栏和表格窗体构成的页面并非一个整体,而是两个模块的拼接。遂检索相关文章,发现果然如此。
如果页面使用了<iframe>,想要定位内嵌界面中的某个元素,在编写代码并调用selenium时,需要进入相应的<iframe>内!
知道了这点,就很容易解决了:Fn+F12查看页面源代码,把鼠标放在滚动条上就能查看到内嵌页面的<iframe>的id或者class,耐心网上翻一点,就能找到表格所在的iframe了:fs_tab_1587094181588
在这里插入图片描述
我兴冲冲的编写代码进入iframe并重启程序测试:

driver.switch_to.frame(driver.find_element_by_xpath("fs_tab_1587094181588"))

在这里插入图片描述

2.动态id/class的定位

what?找不到iframe?
不慌,盯着iframe的idfs_tab_1587094181588仔细思考了一下,后面一长串的数字像是动态生成的,而这种id一般前面的头部是不变的,即fs_tab_,于是我重新查看新页面的iframe的id,果然和上次打开的不一样了:fs_tab_1587094408952,表头也果然没变。
查看源码中以fs_tab_开头的id,发现只有一个,妙哉,可以使用了。
于是,通过driver.find_element_by_xpath("//iframe[starts-with(@id, 'fs_tab_')]")模糊匹配带有fs_tab_开头的id,并使用driver.switch_to.frame()进入相应的iframe,重新定位元素,成功了!

# 由于是动态的id和class,因此... 
driver.switch_to.frame(driver.find_element_by_xpath("//iframe[starts-with(@id, 'fs_tab_')]"))
# 点击“与昨日情况一致”radio
driver.find_elements_by_class_name('fr-group-span')[0].click()

四、表格填写

经过上面这些分析后,下面的步骤就变得轻松多了。
由于每天填写的表格行数都是在动态变化的,比如今天的某个单元格id是D21-0-0,明天就变成了D-22-0-0,因此,对于获取当前日期应该填写的表格id,可以这样做:
根据日期差值与编写代码时的表格id相加,便得到了当日动态表格id

def getDateDiff():
    # 2020-4-16编写,此时动态列数为21
    retire_day = datetime(2020, 4, 16)
    today = datetime.now()
    left_days = (retire_day - today).days  # 获取两个日期的天数差值
    return abs(left_days)

curColID = str(getDateDiff() + 20) + r'-0-0'
inputCol = ['D', 'AB']

点击“与昨日情况一致”后,还需要手动填写两个表格,分别是当日体温36度和“一直在京”,不怕,写个循环就搞定了:

 # 列ID形式为:列编号-0-0
curColID = str(getDateDiff() + 20) + r'-0-0'
inputCol = ['D', 'AB']
inputVal = ['36', '一直在京']
for i in range(len(inputCol)):
	  inputBox = driver.find_element_by_id(inputCol[i] + curColID)
      driver.execute_script("arguments[0].scrollIntoView();", inputBox)
      time.sleep(1)
      # 开始模拟鼠标双击操作,不然无法锁定表格填数据
      action_chains = ActionChains(driver)
      action_chains.double_click(inputBox).perform()
      time.sleep(1)
      # 填写表格
      driver.find_element_by_xpath("//input[contains(@class,'fr-texteditor')]").send_keys(inputVal[i])
      time.sleep(1)

【注】
在第三版代码中,作者将通过日期差定位元素的方式改动为根据文本定位,因为系统开发者会定期删除几行表格,导致通过日期差定位的方式不准确。

获取表格ID,可以先通过文字定位元素,再使用driver.get_attribute(‘id’)获取所在单元格的id


五、提交表格

一行代码:

# css选择器选择button .x-emb-submit 这是关键
print(driver.find_element_by_css_selector('.x-emb-submit').click())

全部代码

奉上全部代码,有需要的同学可以参考
第三版(已稳定运行一个多月未中断)

# encoding=utf8
# from datetime import datetime
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
import time

stu_number = ['201706020115', '201706020218', '201706020108', '201706040143', '201706020239', '201706020221']
stu_password = ['10200014', '08258612', '0320001X', '1026283X', '0711181X', '02113537']
#stu_number = ['201706020221']

# 授权操作
def operationAuth(driver):
    # 等待电脑亮屏
    # time.sleep(10)
    driver.implicitly_wait(30)
    try:
        # 疫情打卡系统url
        url = r"http://tb.bucea.edu.cn:8075/WebReport/ReportServer?op=fs_load&cmd=fs_signin&_=1586929099201"
        # url = r"http://tb.bucea.edu.cn/WebReport/ReportServer?op=fs"
        for j in range(len(stu_number)):
            driver.get(url)
            driver.maximize_window()
            # 填写用户名和密码
            WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, r'fs-login-username')))
            driver.find_element_by_class_name('fs-login-username').send_keys(stu_number[j])
            WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, r'fs-login-password')))
            driver.find_element_by_class_name('fs-login-password').send_keys(stu_password[j])
            # 提交表单
            WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, r'fs-login-btn')))
            driver.find_element_by_xpath("//*[@id='fs-login-btn']").click()
            # 没有睡眠时间就不行switch_to.frame()
            # 模拟点击进入填报表格

            driver.find_element_by_link_text("数据采集").click()
            driver.find_element_by_link_text("学生每日上报").click()
            # 这地方写博客记录,由于是动态的id和class,因此...

            # WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.XPATH,"//iframe[starts-with(@id, 'fs_tab_')]")))
            driver.switch_to.frame(driver.find_element_by_xpath("//iframe[starts-with(@id, 'fs_tab_')]"))
            # 点击“与昨日情况一致”radio
            time.sleep(2)
            driver.find_element_by_class_name('fr-group-span').click()

            inputVal = ['36']
            inputText = ['请填写体温度数']
            for i in range(len(inputText)):
                # 改动,根据文本定位,因为系统开发者会定期删除几行表格,导致日期差定位不准
                inputBox = driver.find_element_by_xpath("//*[text()='请填写体温度数']")
                # 填写"一直在京"的表格所对应的ID
                remarkID = 'AB' + inputBox.get_attribute("id")[1:]
                driver.execute_script("arguments[0].scrollIntoView();", inputBox)
                # 开始模拟鼠标双击操作,不然无法锁定表格填数据
                action_chains = ActionChains(driver)
                action_chains.double_click(inputBox).perform()
                # 填写体温度数
                driver.find_element_by_xpath("//input[contains(@class,'fr-texteditor')]").send_keys(inputVal[i])
                time.sleep(1)

                if stu_number[j] == '201706020221':
                    remarkID = 'AE' + inputBox.get_attribute("id")[1:]
                    inputBox = driver.find_element_by_id(remarkID)
                    driver.execute_script("arguments[0].scrollIntoView();", inputBox)
                    action_chains = ActionChains(driver)
                    action_chains.double_click(inputBox).perform()
                    time.sleep(1)
                    driver.find_element_by_xpath("//input[contains(@class,'fr-texteditor')]").send_keys('不在北京')
                time.sleep(1)
                remarkID = 'H' + inputBox.get_attribute("id")[1:]
                if stu_number[j] == '201706020221':
                    remarkID = 'H' + inputBox.get_attribute("id")[2:]
                inputBox = driver.find_element_by_id(remarkID)
                # 开始模拟鼠标双击操作,不然无法锁定表格填数据
                time.sleep(1)
                driver.execute_script("arguments[0].scrollIntoView();", inputBox)
                #inputBox.click()
                action_chains = ActionChains(driver)
                action_chains.double_click(inputBox).perform()
                # fr-trigger-btn-up
                tp = driver.find_element_by_class_name("fr-trigger-btn-up")
                tp.click()
                # time.sleep(1)
                inp_boxes = driver.find_elements_by_class_name("fr-combo-list-item")
                inp_boxes[1].click()

            # css选择器选择button .x-emb-submit 这是关键
            WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, '.x-emb-submit')))
            print(driver.find_element_by_css_selector('.x-emb-submit').click())
            time.sleep(1)
            #driver.close()

    except TimeoutException:
        # 报错后就强制停止加载
        # 这里是js控制
        driver.execute_script('window.stop()')
        #print(driver.page_source)
    # driver.close()

# 方法主入口
if __name__ == '__main__':
    # 加启动配置
    driver = webdriver.Firefox()  # 利用火狐浏览器
    operationAuth(driver)

第二版:
(改动为根据文本定位,因为系统开发者会定期删除几行表格,导致通过日期差定位的方式不准确)

# encoding=utf8
from datetime import datetime
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.common.exceptions import TimeoutException
import time

stu_number = ['stu1_num', 'stu2_num']
stu_password = ['stu1_pwd', 'stu2_pwd']

# 授权操作
def operationAuth(driver):
    time.sleep(5)
    try:
        # 疫情打卡系统url
        url = r"http://tb.bucea.edu.cn:8075/WebReport/ReportServer?op=fs_load&cmd=fs_signin&_=1586929099201"
        for j in range(len(stu_number)):
            driver.get(url)
            driver.maximize_window()
            # 填写用户名和密码
            elem = driver.find_element_by_class_name('fs-login-username')
            elem.send_keys(stu_number[j])
            elem = driver.find_element_by_class_name('fs-login-password')
            elem.send_keys(stu_password[j])
            # 提交表单
            driver.find_element_by_xpath("//*[@id='fs-login-btn']").click()
            # 没有睡眠时间就不行switch_to.frame()
            time.sleep(2)
            # 模拟点击进入填报表格
            driver.find_element_by_link_text("数据采集").click()
            driver.find_element_by_link_text("学生每日上报").click()
            time.sleep(2)
            # 这地方写博客记录,由于是动态的id和class,因此...
            driver.switch_to.frame(driver.find_element_by_xpath("//iframe[starts-with(@id, 'fs_tab_')]"))
            # 点击“与昨日情况一致”radio
            driver.find_elements_by_class_name('fr-group-span')[0].click()

            inputVal = ['36']
            inputText = ['请填写体温度数']
            for i in range(len(inputText)):
                # 改动,根据文本定位,因为系统开发者会定期删除几行表格,导致日期差定位不准
                inputBox = driver.find_element_by_xpath("//*[text()='请填写体温度数']")
                # 下面是当前日期在京状态单元格所在的ID
                # remarkID = 'AB' + inputBox.get_attribute("id")[1:]
                time.sleep(1)
                # 开始模拟鼠标双击操作,不然无法锁定表格填数据
                action_chains = ActionChains(driver)
                action_chains.double_click(inputBox).perform()
                time.sleep(1)
                # 填写体温度数
                driver.find_element_by_xpath("//input[contains(@class,'fr-texteditor')]").send_keys(inputVal[i])
                time.sleep(1)
            # css选择器选择button .x-emb-submit 这是关键
            print(driver.find_element_by_css_selector('.x-emb-submit').click())
            time.sleep(1)
            # driver.close()

    except TimeoutException:
        # 报错后就强制停止加载
        # 这里是js控制
        driver.execute_script('window.stop()')
        print(driver.page_source)

# 方法主入口
if __name__ == '__main__':
    # 加启动配置
    driver = webdriver.Firefox()  # 利用火狐浏览器
    operationAuth(driver)


第一版

# encoding=utf8
import sys
import math
from datetime import datetime
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import time

stu_number = ['stu1_number', 'stu2_number']
stu_password = ['stu1_pwd', 'stu2_pwd']

# 获得日期差值,由于表格数据是动态变化的,可根据编写日期的行号和日期差来确定当日行号
def getDateDiff():
    # 2020-4-16编写,此时动态列数为21
    retire_day = datetime(2020, 4, 16)
    today = datetime.now()
    left_days = (retire_day - today).days  # 获取两个日期的天数差值
    return abs(left_days)

# 授权操作
def operationAuth(driver):
    # time.sleep(10)
    try:
        # 疫情打卡系统url
        url = r"http://tb.bucea.edu.cn:8075/WebReport/ReportServer?op=fs_load&cmd=fs_signin&_=1586929099201"
        for j in range(len(stu_number)):
            driver.get(url)
            driver.maximize_window()
            # 填写用户名和密码
            elem = driver.find_element_by_class_name('fs-login-username')
            elem.send_keys(stu_number[j])
            elem = driver.find_element_by_class_name('fs-login-password')
            elem.send_keys(stu_password[j])
            # 提交表单
            driver.find_element_by_xpath("//*[@id='fs-login-btn']").click()
            # 没有睡眠时间就不行switch_to.frame()
            time.sleep(2)
            # 模拟点击进入填报表格
            driver.find_element_by_link_text("数据采集").click()
            driver.find_element_by_link_text("学生每日上报").click()
            time.sleep(2)
            # 这地方写博客记录,由于是动态的id和class,因此...
            driver.switch_to.frame(driver.find_element_by_xpath("//iframe[starts-with(@id, 'fs_tab_')]"))
            # 点击“与昨日情况一致”radio
            driver.find_elements_by_class_name('fr-group-span')[0].click()

            # 列ID形式为:列编号-0-0
            curColID = str(getDateDiff() + 20) + r'-0-0'
            inputCol = ['D', 'AB']
            inputVal = ['36', '一直在京']
            for i in range(len(inputCol)):
                inputBox = driver.find_element_by_id(inputCol[i] + curColID)
                driver.execute_script("arguments[0].scrollIntoView();", inputBox)
                time.sleep(1)
                # 开始模拟鼠标双击操作,不然无法锁定表格填数据
                action_chains = ActionChains(driver)
                action_chains.double_click(inputBox).perform()
                time.sleep(1)
                # 填写体温度数
                driver.find_element_by_xpath("//input[contains(@class,'fr-texteditor')]").send_keys(inputVal[i])
                time.sleep(1)

            # css选择器选择button .x-emb-submit 这是关键
            print(driver.find_element_by_css_selector('.x-emb-submit').click())
            time.sleep(1)
            # driver.close()

    except TimeoutException:
        # 报错后就强制停止加载
        # 这里是js控制
        driver.execute_script('window.stop()')
        print(driver.page_source)

# 方法主入口
if __name__ == '__main__':
    # 加启动配置
    driver = webdriver.Firefox()  # 利用火狐浏览器
    # driver.get("http://www.baidu.com")  # 打开get到的网址
    operationAuth(driver)

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值