使用Python爬取考勤信息

最近公司的加班调休审批制度有一些调整,由于公司网站上没有清楚的标明各自有多少天可以调休,所以为了清楚的知道自己还有多少天可以调休,就想着使用爬虫爬一下考勤信息,把它放在一个Excel表中以方便查阅。最近项目不是很忙,也趁机学习学习Python爬虫。

一、环境准备

1.首先需要先安装Python,笔者使用的Python3.X。
2.然后使用pip安装工具安装爬虫所需要的非标准库
主要使用到以下一些非标准库:

selenium:为了模拟浏览器操作
win32:为了解密浏览器Cookies
openpyxl:为了操作Excel

使用pip工具安装:

pip install selenium
pip install pywin32
pip install openpyxl

3.由于笔者使用的是浏览器的模拟操作,所以还需要下载一个WebDriver,笔者使用的Chrome浏览器,所以下载与Chrome版本相对应的WebDriver放在Python的安装目录下。

二、编码

1.首先,我们把所有需要使用到的库加进来

from win32 import win32crypt
from os import getenv
import sqlite3
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import time
import datetime
import openpyxl
from openpyxl.comments import Comment
from openpyxl.styles import Font, colors, Alignment
import math
import re

2.使用浏览器模拟操作
使用浏览器模拟操作时,可以选择浏览器是否可见。

opt = webdriver.ChromeOptions()
    opt.headless = True  # 浏览器是否可见
    driver = webdriver.Chrome(
        options=opt
    )

创建好了WebDriver后,就可以登录网站了:

driver.get('https://mis.XXXXX.cn')

由于网站是需要登录的,所以要模拟登录过程。使用浏览器模拟登录有两种方式,使用账号密码直接登录和使用之前登录过的Cookies登录。

  • 直接登录
    直接登录需要找到账号与密码输入框,在账号密码框中输入相应的账号与密码,再点登录。
usr_name = driver.find_element_by_class_name("ant-input") # 找到账号输入框
psw = driver.find_element_by_id("inputPassword") # 找到密码输入框
submit_btn = driver.find_element_by_class_name("ant-btn") # 找到登录按钮
usr_name.send_keys("abc")  # 输入账号
psw.send_keys("abc") # 输入密码
submit_btn.click() # 点击登录按钮

BTW:由于各个网站使用的标签不一样,所以标签仅供参考。

  • 使用Cookies登录
    这里定义了一个函数专门获取Chrome浏览器下考勤网站的Cookie
def get_cookie_from_chrome():
    conn = sqlite3.connect(getenv("LOCALAPPDATA") + r"\Google\Chrome\User Data\Default\Cookies")
    cursor = conn.cursor()
    cursor.execute(
        'select host_key, name, encrypted_value, path, is_httponly, is_secure from cookies where host_key like "%mis.XXXXX.cn%"')
    cookies = []
    for result in cursor.fetchall():
        value = win32crypt.CryptUnprotectData(result[2], None, None, None, 0)[1]
        if value:
            is_http_only = False
            secure = False
            if result[4] != 0:
                is_http_only = True

            if result[5] != 0:
                secure = True

            cookie = {
                'domain': result[0],
                'httpOnly': is_http_only,
                'name': result[1],
                'path': result[3],
                'secure': secure,
                'value': value.decode('utf-8')
            }
            cookies.append(cookie)
    cursor.close()
    return cookies

获取到Cookies之后将之添加到WebDriver中:

cookies = get_cookie_from_chrome()
for cookie in cookies:
     driver.add_cookie(cookie)
driver.get('https://mis.XXXXX.cn') 

由于网站使用了大量的Ajax以及Frame技术,所以需要等待一段时间,让数据加载完成。可以直接使用

time.sleep

函数进行显示等待,也可以使用selenium的expected_conditions条件等待。
前者简单明了,但是如果在指定的时间内数据未加载完成,获取就会失败,出现异常;后者是设置一个最大等待时间,在此时间内根据设定的间隔时间不断检测是否出现指定的数据,如果出现则继续向后执行,否则等待直到最大等待时间超时。

这里网站使用了Frame技术,左边有一个树型结构,里面有一个“个人考勤”,点了“个人考勤”后,右边才会出现个人考勤的详细列表,如下图:
在这里插入图片描述
所以需要切换Frame,然后点击“个人考勤”

wait.until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'left')))
work_day = wait.until(EC.element_to_be_clickable((By.ID, "sundefined5")))
work_day.click()

这段话的意思是直到找到ID为‘left’的Frame,成功切换过去为止,然后直到找到ID为"sundefined5"的元素且可以被点击时,点击它。点击了“个人考勤”后,右边就会出现详细的考勤列表。
由于这个时候还在“left”Frame中,需要切换到新的个人考勤,就需要先切换到“left“的父Frame再切到”个人考勤“Fame

driver.switch_to.parent_frame()
wait.until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'dz-kaoqin_iframe')))

切换到考勤详细列表后,开始找数据然后分析数据:

wait.until(EC.presence_of_element_located((By.XPATH, "//tbody/tr")))
date_list = wait.until(EC.presence_of_element_located((By.XPATH, "//tbody")))
flag, lst = parse_work_time(driver, text)  # 这里使用一个专门的函数来分析数据

考勤列表的日期项可以点开,然后弹出一个对话框,详细列出了打卡记录,但如果有没上班,则为空
在这里插入图片描述

所以需要模拟点击日期,再获取打卡记录,获取完后,再把“打卡信息”对话框关闭,继续获取下一天的信息

item = wait.until(EC.element_to_be_clickable((By.LINK_TEXT, lst[0])))
item.click() # 点击日期
wait.until(EC.frame_to_be_available_and_switch_to_it((By.XPATH, '//iframe[@frameborder="0"]'))) # 切换到“打卡信息”对话框
time.sleep(0.2)  # 这里只能使用sleep,因为这里可能只有tbody而没有数据
record_text = driver.find_element_by_xpath("//tbody").text  # 获取所有打卡记录,保存在record_text中
driver.switch_to.parent_frame() # 返回父Frame
close_btn = wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "layui-layer-ico"))) # 找到“打卡信息”对话框的关闭按钮
close_btn.click() # 点击关闭按钮,关闭“打卡信息”对话框

如果有考勤异常或者请假单之类的,也会有一个链接,可以查看请假单记录:

bill_list = driver.find_elements_by_class_name("link-billId")
for bill in bill_list:
        bill.click()
        wait.until(EC.frame_to_be_available_and_switch_to_it((By.XPATH, '//iframe[@frameborder="0"]')))
        time.sleep(0.2)
        form = driver.find_element_by_xpath('//form')
        work_time.bill_text = form.text
        driver.switch_to.parent_frame()
        close_btn = wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "layui-layer-ico")))
        close_btn.click()
        return

把考勤信息爬取到后,就需要写入Excel表了。这里使用openpyxl库来写入,因为我试了xlwings以及xlwt两个库都不支持批注,只有openpyxl支持。

def write_to_excel(work_time_list, file_path):
    wb = openpyxl.Workbook()    # 打开一个工作薄
    wb.encoding = 'utf-8'  # 使用UTF8编码
    sh = wb.worksheets[0]  # 获取第一个sheet
    sh.title = "考勤"  #将标题改为考勤
    sheet = wb.create_sheet("原始文本记录")  # 创建一个“原始文本记录”的表

	# 设置“考勤”表的表头
    row = 1
    col = 1
    sh.cell(row, col, '日期')
    sh.cell(row, col + 1, '上班时间')
    sh.cell(row, col + 2, '下班时间')
    sh.cell(row, col + 3, '上班打卡时间')
    sh.cell(row, col + 4, '下班打卡时间')
    sh.cell(row, col + 5, '最晚到时间')
    sh.cell(row, col + 6, '迟到分钟')
    sh.cell(row, col + 7, '早退分钟')
    sh.cell(row, col + 8, '考勤状态')
    sh.cell(row, col + 9, '单据编号')
    sh.cell(row, col + 10, '单据类型')
    sh.cell(row, col + 11, '工作分钟')
    sh.cell(row, col + 12, '工作小时')
    sh.cell(row, col + 13, '是否加班')
    sh.cell(row, col + 14, '剩余可调休加班小时')

    # 设置“考勤”表的列宽
    sh.column_dimensions['A'].width = 18
    sh.column_dimensions['B'].width = 8
    sh.column_dimensions['C'].width = 8
    sh.column_dimensions['D'].width = 12
    sh.column_dimensions['E'].width = 12
    sh.column_dimensions['F'].width = 10
    sh.column_dimensions['G'].width = 8
    sh.column_dimensions['H'].width = 8
    sh.column_dimensions['I'].width = 8
    sh.column_dimensions['J'].width = 18
    sh.column_dimensions['K'].width = 20
    sh.column_dimensions['L'].width = 8
    sh.column_dimensions['M'].width = 8
    sh.column_dimensions['N'].width = 8
    sh.column_dimensions['O'].width = 18

    sheet.column_dimensions['A'].width = 100

    blue_font = Font(name='宋体', size=11, italic=False, color=colors.BLUE, bold=False)
    red_font = Font(name='宋体', size=11, italic=False, color=colors.RED, bold=False)

    over_work_time_acc = 0
    for item in work_time_list:
        calc_work_time(item)
        time_hour = parse_bill_info(item)
        sheet.cell(row, 1).value = item.origin_text

        row = row + 1
        col = 1
        dt = datetime.datetime.strptime(item.date, "%Y-%m-%d")
        weekday = dt.weekday() + 1

        cell = sh.cell(row, col, item.date + '(星期' + str(weekday) + ')')
        has_comment = False
        if item.record_text.__len__() > 0:
            cell.comment = Comment(item.record_text, None, width=350)  # 设置批注
            has_comment = True

        sh.cell(row, col + 1, item.start_time)
        sh.cell(row, col + 2, item.end_time)
        sh.cell(row, col + 3, item.real_start_time)
        sh.cell(row, col + 4, item.real_end_time)
        sh.cell(row, col + 5, item.late_start_time)
        sh.cell(row, col + 6, item.late_time)
        sh.cell(row, col + 7, item.before_time)
        sh.cell(row, col + 8, item.status)
        sh.cell(row, col + 9, item.handle_sn)
        if item.bill_text.__len__() > 0:
            sh.cell(row, col + 9).comment = Comment(item.bill_text, None, width=550)

        sh.cell(row, col + 10, item.handle_type)
        sh.cell(row, col + 11, item.valid_work_time)
        valid_work_time = math.floor(item.valid_work_time / 60)
        if valid_work_time > 8:
            valid_work_time = 8
        sh.cell(row, col + 12, valid_work_time)
        if weekday == 6 or weekday == 7:
            if has_comment and item.status == '休息':
                sh.cell(row, col + 13, '加班')
                over_work_time_acc += valid_work_time

        over_work_time_acc -= time_hour
        sh.cell(row, col + 14, over_work_time_acc)

        if weekday == 6 or weekday == 7:
            for col in range(1, sh.max_column + 1):
                if has_comment:
                    sh.cell(row, col).font = red_font
                else:
                    sh.cell(row, col).font = blue_font

    wb.save(file_path)  # 保存Excel表
    wb.close() # 关闭
    return

保存下来的内容如图所示:
在这里插入图片描述

祝好

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值