最近公司的加班调休审批制度有一些调整,由于公司网站上没有清楚的标明各自有多少天可以调休,所以为了清楚的知道自己还有多少天可以调休,就想着使用爬虫爬一下考勤信息,把它放在一个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:由于各个网站使用的标签不一样,所以标签仅供参考。
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
祝好