python学习之钉钉打卡

  • 背景
  • 原理
  • 实现
    • 一、准备
    • 二、代码

背景

曾经写过几个python小工具,刷快手、自动答题、刷火车票、爬电影天堂电影…,最近因为钉钉成了我们公司官方软件,所以,你懂得啦,呵呵。刚好手头有个退休的小米4安卓机,让python来钉钉打卡,这需要借助adb,因为只有adb才能让我们的电脑跟安卓手机交互。该文章内容仅仅只是为了学习,最好不要用于实际打卡(要打我也拦不住)。

原理

  1. python命令行库显示调用adb,利用adb命令做点击、截屏、滑动操作。
  2. adb获取当前屏幕布局xml,解析xml,找到需要点击或者滑动的元素,实现安卓手机的控制。
  3. adb打卡操作成功后,做一个python邮件或者短信通知提醒打卡结果。

实现

一、准备

  1. 首先要下载一个adb工具,这里我直接下载好了一个工具。
  2. VSCode最新版、python3.7

二、代码

  1. python需要调用adb工具,首先写一个通用的cmd命令行工具类。
import shlex
import datetime
import subprocess
import timedef executeCommand(cmd,cwd=None,timeout=None,shell=False):if shell:cmdStringList = cmdelse:cmdStringList = shlex.split(cmd)if timeout:endTime = datetime.datetime.now() + datetime.timedelta(seconds=timeout)sub = subprocess.Popen(cmdStringList,cwd=cwd,stdin=subprocess.PIPE,stdout=subprocess.PIPE,shell=shell,bufsize=4096)while sub.poll() is None:time.sleep(0.1)if timeout:if endTime <= datetime.datetime.now():raise Exception('Timeout: {}'.format(cmd))return sub.stdout.read()if __name__ == "__main__":print(executeCommand("ls"))
  1. 获取安卓设备编号
currentPath = os.getcwd()
print('当前路径:{}'.format(currentPath))
# 杀死存在的adb.exe进程
print('start------预杀死存在的adb.exe------start')
cuscmd.executeCommand('taskkill /im adb.exe /f',currentPath)
print('end------预杀死存在的adb.exe------end')
# 连接设备,获取设备编号
out=cuscmd.executeCommand('adb/adb devices',currentPath)
deviceListStr=out.decode(encoding="utf-8")
print('设备编号:{}'.format(deviceListStr))
  1. 执行点击
# 查找符合要求的字符串(第4行开始读)
deviceListStr = deviceListStr.split('\r\n',3)[3]
deviceList=re.findall(r'[A-Za-z0-9/.:]+',deviceListStr)
total = len(deviceList)
if len(deviceList) > 1:dtotal = Decimal(total)deviceNum = (Decimal(dtotal))/Decimal(2)print('发现'+str(deviceNum)+'台手机设备')# 设备id列表deviceId = []for i in range(0,int(deviceNum)):deviceId.append(deviceList[2*i])print(deviceId)# 获取每个设备的应用包名for id in deviceId:# 启动应用# 检查屏幕是否熄灭,熄灭是不能获取到正确的xml页面布局cmdStr = 'adb/adb -s '+id+' shell dumpsys window policy|grep "mScreenOnEarly"'out=cuscmd.executeCommand(cmdStr,currentPath)returnStr=out.decode(encoding="utf-8").strip()print('获取屏幕信息:{}'.format(returnStr))if 'mScreenOnEarly=false' in returnStr:# 点亮屏幕print('当前设备屏幕熄灭,点亮屏幕')cuscmd.executeCommand('adb/adb -s '+id+' shell input keyevent 26',currentPath)# 关闭应用out=cuscmd.executeCommand('adb/adb -s '+id+' shell am force-stop com.alibaba.android.rimet',currentPath)stopStr=out.decode(encoding="utf-8").strip()print('关闭信息:{}'.format(stopStr))out=cuscmd.executeCommand('adb/adb -s '+id+' shell am start -n com.alibaba.android.rimet/com.alibaba.android.rimet.biz.LaunchHomeActivity',currentPath)startStr=out.decode(encoding="utf-8").strip()print('启动信息:{}'.format(startStr))# 延时15秒给应用足够的时间完成钉钉启动time.sleep(15)# 创建临时目录listdir=os.listdir(currentPath)tempPath = os.path.join(currentPath,'temp')if 'temp' not in listdir:print('文件夹temp不存在,创建temp')os.mkdir(tempPath)# 路径\\转/tempPath = tempPath.replace('\\','/')# 获取设备名称out=cuscmd.executeCommand('adb/adb -s '+id+' shell getprop ro.product.model',currentPath)modelStr=out.decode(encoding="utf-8").strip().replace(r' ','_')print('获取设备名称:{}'.format(modelStr))# 获取钉钉应用版本号out=cuscmd.executeCommand('adb/adb -s '+id+' shell pm dump com.alibaba.android.rimet | grep "versionName"',currentPath)returnStr=out.decode(encoding="utf-8").strip()ddVersion = re.findall(r'[0-9/.:]+',returnStr)[0]print('获取钉钉版本号:{}'.format(ddVersion))listdir=os.listdir(tempPath)# 依次点击按钮打卡DDClick(id,modelStr,ddVersion,currentPath,tempPath,listdir).clickWork().clickKQDK().clickSXBDK().showResult()# 发送短信# tencentsms.sendmessage('18672332926',['钉钉打卡','已经成功打开啦',time.strftime("%Y年%m月%d日%H:%M:%S", time.localtime())])# 关闭应用out=cuscmd.executeCommand('adb/adb -s '+id+' shell am force-stop com.alibaba.android.rimet',currentPath)stopStr=out.decode(encoding="utf-8").strip()print('关闭信息:{}'.format(stopStr))
else :print('未发现任何手机设备')
print('结束')
  1. 具体点击操作的类 ddclick
import cuscmd
from decimal import Decimal
import time
import re
import os
import utils
import json
import datetime
import qqemail
class DDClick:def __init__(self,id,modelStr,ddVersion,currentPath,tempPath,listdir):self.mainXMLName = modelStr+'_'+ddVersion+'_mainui.xml'self.workXMLName = modelStr+'_'+ddVersion+'_workui.xml'self.sxbdkXMLName = modelStr+'_'+ddVersion+'_sxbdkui.xml'self.listdir = listdirself.tempPath = tempPathself.currentPath = currentPathself.id = id# 显示打卡结果def showResult(self):xmlName = self.sxbdkXMLNametempPath = self.tempPathcurrentPath = self.currentPathid = self.idself.__createXML(id,xmlName,self.listdir,currentPath,tempPath,force=True)# 获取打卡页面的打卡完成# 读文件with open(os.path.join(tempPath,xmlName), 'r',encoding='utf-8') as f:xmlContentStr = json.dumps(utils.xmlToJsonDict(f.read())).encode('utf-8').decode('unicode_escape')f.close()index = 0# 查询关键字index = xmlContentStr.find("打卡时间")totalIndex = len(xmlContentStr)-1# 截屏self.__screencapDK(id,currentPath,tempPath)# 图片转base64base64Str = utils.imgToBase64(os.path.join(tempPath,'dkscreen.png'))# 读html模板emailtemplate = ''with open('emailtemplate.txt', 'r',encoding='utf-8') as hf:emailtemplate = hf.read()hf.close()while index > -1:# 反向查找@content-descsIndex = xmlContentStr.rfind('@content-desc',0,index-17)# 正向查找@content-desceIndex = xmlContentStr.find('@content-desc',index,totalIndex)printValue = '打卡时间'printValue1 = '打开地址'if sIndex>-1 and eIndex>-1:sbTimeStr = xmlContentStr[sIndex+17:sIndex+26]dkTimeStr = xmlContentStr[eIndex+17:eIndex+22]# 查找打卡地址sNodeIndex = xmlContentStr.find('node"',eIndex,totalIndex)dkAddressStr = ''sDKAddressIndex = 0searchIndex = sNodeIndex+6while len(dkAddressStr) == 0 and sDKAddressIndex >= 0:sDKAddressIndex = xmlContentStr.find('@content-desc',searchIndex,totalIndex)eDKAddressIndex = xmlContentStr.find('",',sDKAddressIndex+13,totalIndex)dkAddressStr = xmlContentStr[sDKAddressIndex+17:eDKAddressIndex]searchIndex = eDKAddressIndex+6printValue=sbTimeStr+'  '+printValue+''+dkTimeStrprintValue1 = printValue1+'  '+dkAddressStrprintStr = '''-------------------------打卡结果-------------------------{}{}---------------------------------------------------------'''.format(printValue,printValue1)print(printStr)emailStr = emailtemplate.format(sbTimeStr,dkTimeStr,dkAddressStr,base64Str)# 发送邮件通知qqemail.sendhtml(emailStr)index = xmlContentStr.find("打卡时间",index+4)# 自动判定上下班打卡def clickSXBDK(self):currentDateTime = datetime.datetime.now()print('>>>当前系统时间:{}'.format(datetime.datetime.strftime(currentDateTime, "%Y-%m-%d %H:%M")))currentDateStr = datetime.datetime.strftime(currentDateTime,'%Y-%m-%d')xmlName = self.sxbdkXMLNametempPath = self.tempPathcurrentPath = self.currentPathid = self.idself.__createXML(id,xmlName,self.listdir,currentPath,tempPath,force=True)try:# 获取上下班时间sbTimeStr = currentDateStr+' '+self.__getSXBTime(xmlName,'上班时间')print('>>>今天上班时间:{}<<<'.format(sbTimeStr))xbTimeStr = currentDateStr+' '+self.__getSXBTime(xmlName,'下班时间')print('>>>今天下班时间:{}<<<'.format(xbTimeStr))sbTime = datetime.datetime.strptime(sbTimeStr, "%Y-%m-%d %H:%M")xbTime = datetime.datetime.strptime(xbTimeStr, "%Y-%m-%d %H:%M")if currentDateTime < sbTime:print('>>>上班打卡<<<')# 点击对应的图标self.__click(xmlName,0,0,'上班打卡',self.__clickByPoint)if currentDateTime > xbTime:print('>>>下班打卡<<<')# 点击对应的图标self.__click(xmlName,0,0,'下班打卡',self.__clickByPoint)else:print('>>>不在打卡范围,正确范围是:{}前,或{}后<<<'.format(sbTimeStr,xbTimeStr))except Exception as e:print(e)self.clickSXBDK()return self# 点击考勤打卡图标def clickKQDK(self):workXMLName = self.workXMLNametempPath = self.tempPathcurrentPath = self.currentPathid = self.idself.__createXML(id,workXMLName,self.listdir,currentPath,tempPath)# 点击对应的图标self.__click(workXMLName,0,-22,'考勤打卡',self.__clickByPoint)return self# 点击工作def clickWork(self):mainXMLName = self.mainXMLNametempPath = self.tempPathcurrentPath = self.currentPathid = self.idself.__createXML(id,mainXMLName,self.listdir,currentPath,tempPath)# 读文件with open(os.path.join(tempPath,mainXMLName), 'r',encoding='utf-8') as f:mainuiDict=utils.xmlToJsonDict(f.read())nodes = mainuiDict['hierarchy']['node']['node']['node']['node']['node']['node']['node']for node in nodes:if node['@resource-id'] == 'com.alibaba.android.rimet:id/bottom_tab':nodeItems = node['node']for nodeItem in nodeItems:if 'node' in nodeItem:nodeChildren = nodeItem['node']for nodeChild in nodeChildren:if nodeChild['@resource-id'] == 'com.alibaba.android.rimet:id/home_bottom_tab_button_work':zbStr = nodeChild['@bounds']self.__clickByPoint(zbStr)return self# 创建文件def __createXML(self,id,xmlName,listdir,currentPath,tempPath,force = False):# 检查页面是否正确cmd = 'adb/adb -s '+id+' shell "dumpsys window | grep mCurrentFocus"'out=cuscmd.executeCommand(cmd,currentPath)returnStr=out.decode(encoding="utf-8").strip()print('获取页面所在activity信息:{}'.format(returnStr))if 'com.alibaba.android.rimet' not in returnStr:# 点击电源键cuscmd.executeCommand('adb/adb -s '+id+' shell input keyevent 26',currentPath)# 检查屏幕是否熄灭,熄灭是不能获取到正确的xml页面布局cmd = 'adb/adb -s '+id+' shell dumpsys window policy|grep "mScreenOnEarly"'out=cuscmd.executeCommand(cmd,currentPath)returnStr=out.decode(encoding="utf-8").strip()print('获取屏幕信息:{}'.format(returnStr))if 'mScreenOnEarly=false' in returnStr:# 点亮屏幕print('当前设备屏幕熄灭,点亮屏幕')cuscmd.executeCommand('adb/adb -s '+id+' shell input keyevent 26',currentPath)if xmlName not in listdir or force is True:print(xmlName+'不存在或强制更新'+xmlName+',创建新的'+xmlName)# 获取xml页面布局cmd = 'adb/adb -s '+id+' shell uiautomator dump /data/local/tmp/'+xmlName# print('获取xml页面布局命令:{}'.format(cmd))out=cuscmd.executeCommand(cmd,currentPath)returnStr=out.decode(encoding="utf-8").strip()print('获取页面UIXML信息:{}'.format(returnStr))# 将xml放置本地cmd = 'adb/adb -s '+id+' pull /data/local/tmp/'+xmlName+' '+tempPath# print('将xml放置本地命令:{}'.format(cmd))out=cuscmd.executeCommand(cmd,currentPath)returnStr=out.decode(encoding="utf-8").strip()print('保存页面UIXML信息:{}'.format(returnStr))# 截钉钉打卡屏def __screencapDK(self,id,currentPath,tempPath,imgName='dkscreen.png'):cmd = 'adb/adb -s '+id+' shell  screencap -p /data/local/tmp/'+imgNameout=cuscmd.executeCommand(cmd,currentPath)returnStr=out.decode(encoding="utf-8").strip()print('获取钉钉打卡截屏:{}'.format(returnStr))cmd = 'adb/adb -s '+id+' pull /data/local/tmp/'+imgName+' '+tempPathout=cuscmd.executeCommand(cmd,currentPath)returnStr=out.decode(encoding="utf-8").strip()print('保存钉钉打卡截屏:{}'.format(returnStr))# 点击坐标def __clickByPoint(self,zbStr,xoffset=0,yoffset=0):print('当前点击坐标范围为:{}'.format(zbStr))# 转化为坐标zbList = re.findall(r'[0-9]+',zbStr)lx = Decimal(zbList[0])ly = Decimal(zbList[1])rx = Decimal(zbList[2])ry = Decimal(zbList[3])# 计算中心点坐标mx = lx+(rx-lx)/2+xoffsetmy = ly+(ry-ly)/2+yoffsetprint('当前点击坐标为:{}'.format('['+str(round(mx,0))+','+str(round(my,0))+']'))# 点击mPointStr = str(round(mx,0))+' '+str(round(my,0))out=cuscmd.executeCommand('adb/adb -s '+self.id+' shell input tap '+mPointStr,self.currentPath)clickStr=out.decode(encoding="utf-8").strip()print('点击信息:{}'.format(clickStr))# 延时6秒给应用足够的时间渲染页面time.sleep(6)# 点击操作def __click(self,xmlName,xoffset=0,yoffset=0,keywords='',callback=None):tempPath = self.tempPath# 读文件with open(os.path.join(tempPath,xmlName), 'r',encoding='utf-8') as f:workuiStr = json.dumps(utils.xmlToJsonDict(f.read())).encode('utf-8').decode('unicode_escape')# 查询关键字index = workuiStr.find(keywords)if index > -1:# 反向查找{sIndex = workuiStr.rfind('{',0,index)# 正向查找}eIndex = workuiStr.find('}',index,len(workuiStr)-1)if sIndex>-1 and eIndex>-1:kqdkStr = workuiStr[sIndex:eIndex+1]kqdkDict = json.loads(kqdkStr)zbStr = kqdkDict['@bounds']if callback != None:callback(zbStr,xoffset,yoffset)# 获取上下班时间def __getSXBTime(self,xmlName,keyWords):tempPath = self.tempPathtimeStr = None# 读文件with open(os.path.join(tempPath,xmlName), 'r',encoding='utf-8') as f:xmlContentStr = f.read()# 查询关键字index = xmlContentStr.find(keyWords)if index > -1:# 查找到时间并截取出来timeStr = xmlContentStr[index+4:index+9]print('得到的时间为:{}'.format(timeStr))return timeStr
  1. 邮件通知
import smtplib
from email.mime.text import MIMEText
from email.header import Header
# 发送服务器
host='smtp.qq.com'
# 发送邮箱
user='xxxxx@qq.com'
# 授权码
pwd=''
# 接收邮箱(改为自己需要接收的邮箱)
receive=['xxxxx@qq.com']def send(msg):try:smtp = smtplib.SMTP()smtp.connect(host, 25)smtp.login(user, pwd)smtp.sendmail(user, receive, msg.as_string())smtp.quit()print(">>>邮件发送成功!<<<")except smtplib.SMTPException as e:print(">>>邮件发送失败<<<", e)def sendhtml(content):msg = MIMEText(content, 'html', 'utf-8')#from表示发件人显示内容msg['From'] = Header("钉钉自动打卡助手", 'utf-8')#to表示收件人显示内容msg['To'] = Header('钉钉用户', 'utf-8')# subject,邮件标头subject = '钉钉自动打卡邮件通知'msg['subject'] = Header(subject, 'utf-8')send(msg)
  1. email模板
<meta charset="utf-8"><div class="content-wrap" style="margin: 0px auto; overflow: hidden; padding: 0px; border: 0px solid rgb(238, 238, 238); width: 600px;"><!----><div class="full" tindex="1" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;"><tbody><tr><td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;"><tr><td align="left" style="font-size: 0px; padding: 20px;"><div class="text" style="font-family: &quot;Microsoft YaHei&quot;; overflow-wrap: break-word; margin: 0px; text-align: center; line-height: 24px; color: rgb(250, 137, 123); font-size: 24px;"><div><p style="text-size-adjust: none; word-break: break-word; line-height: 24px; font-size: 24px; margin: 0px;"><strong>打卡时间</strong></p></div></div></td></tr></table></td></tr></tbody></table></div><div tindex="2" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(134, 227, 206); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;"><tbody><tr><td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;"><table width="100%" border="0" cellpadding="0" cellspacing="0" style="vertical-align: top;"><tbody><tr><td style="width: 33.3333%; max-width: 33.3333%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;"><div class="full" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 200px;"><tbody><tr><td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;"><tr><td align="center" vertical-align="middle" style="padding-top: 40px; width: 200px; background-image: url(&quot;&quot;); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td></tr></table></td></tr></tbody></table></div></td><td style="width: 33.3333%; max-width: 33.3333%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;"><div class="full" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 200px;"><tbody><tr><td style="direction: ltr; width: 200px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;"><tr><td align="left" style="font-size: 0px; padding: 26px 20px;"><div class="text" style="font-family: &quot;Microsoft YaHei&quot;; overflow-wrap: break-word; margin: 0px; text-align: center; line-height: 12px; color: rgb(32, 32, 32); font-size: 16px;"><div><p style="text-size-adjust: none; word-break: break-word; line-height: 12px; font-size: 16px; margin: 0px;"><strong>{0}</strong></p></div></div></td></tr></table></td></tr></tbody></table></div></td><td style="width: 33.3333%; max-width: 33.3333%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;"><div class="full" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 200px;"><tbody><tr><td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;"><tr><td align="center" vertical-align="middle" style="padding-top: 40px; width: 200px; background-image: url(&quot;&quot;); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td></tr></table></td></tr></tbody></table></div></td></tr></tbody></table></td></tr></tbody></table></div><div tindex="3" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" style="background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;"><tbody><tr><td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;"><table width="100%" border="0" cellpadding="0" cellspacing="0" style="vertical-align: top;"><tbody><tr><td style="width: 25%; max-width: 25%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;"><div class="full" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 150px;"><tbody><tr><td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;"><tr><td align="center" vertical-align="middle" style="padding-top: 120px; background-color: rgb(255, 221, 148); width: 150px; background-image: url(&quot;&quot;); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td></tr></table></td></tr></tbody></table></div></td><td style="width: 25%; max-width: 25%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;"><div columnnumber="3"><table align="center" border="0" cellpadding="0" cellspacing="0" style="width: 100%;"><tbody><tr><td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; border: 0px;"><a target="_blank" href="javascript:;" style="cursor: default;"><div class="mj-column-per-50" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;"><table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse; border-spacing: 0px; width: 100%; vertical-align: top;"><tr><td align="center" border="0" style="font-size: 0px; word-break: break-word;"><div class="full" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 150px;"><tbody><tr><td style="direction: ltr; width: 150px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-color: rgb(221, 230, 165); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;"><tr><td align="left" style="font-size: 0px; padding: 20px;"><div class="text" style="font-family: &quot;Microsoft YaHei&quot;; overflow-wrap: break-word; margin: 0px; text-align: right; line-height: 20px; color: rgb(32, 32, 32); font-size: 16px;"><div><p style="text-size-adjust: none; word-break: break-word; line-height: 20px; font-size: 16px; margin: 0px;"><strong>打卡时间</strong></p></div></div></td></tr></table></td></tr></tbody></table></div></td></tr></table></div><div class="mj-column-per-50" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;"><table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse; border-spacing: 0px; width: 100%; vertical-align: top;"><tr><td align="center" border="0" style="font-size: 0px; word-break: break-word;"><div class="full" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 150px;"><tbody><tr><td style="direction: ltr; width: 150px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-color: rgb(221, 230, 165); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;"><tr><td align="left" style="font-size: 0px; padding: 20px;"><div class="text" style="font-family: &quot;Microsoft YaHei&quot;; overflow-wrap: break-word; margin: 0px; text-align: right; line-height: 20px; color: rgb(32, 32, 32); font-size: 16px;"><div><p style="text-size-adjust: none; word-break: break-word; line-height: 20px; font-size: 16px; margin: 0px;"><strong>打卡地址</strong></p></div></div></td></tr></table></td></tr></tbody></table></div></td></tr></table></div></a></td></tr></tbody></table></div></td><td style="width: 50%; max-width: 50%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;"><div columnnumber="3"><table align="center" border="0" cellpadding="0" cellspacing="0" style="width: 100%;"><tbody><tr><td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; border: 0px;"><a target="_blank" href="javascript:;" style="cursor: default;"><div class="mj-column-per-50" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;"><table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse; border-spacing: 0px; width: 100%; vertical-align: top;"><tr><td align="center" border="0" style="font-size: 0px; word-break: break-word;"><div class="full" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 300px;"><tbody><tr><td style="direction: ltr; width: 300px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-color: rgb(255, 221, 148); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;"><tr><td align="left" style="font-size: 0px; padding: 20px;"><div class="text" style="font-family: &quot;Microsoft YaHei&quot;; overflow-wrap: break-word; margin: 0px; text-align: left; line-height: 20px; color: rgb(32, 32, 32); font-size: 16px;"><div><p style="text-size-adjust: none; word-break: break-word; line-height: 20px; font-size: 16px; margin: 0px;"><strong>{1}</strong></p></div></div></td></tr></table></td></tr></tbody></table></div></td></tr></table></div><div class="mj-column-per-50" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;"><table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse; border-spacing: 0px; width: 100%; vertical-align: top;"><tr><td align="center" border="0" style="font-size: 0px; word-break: break-word;"><div class="full" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 300px;"><tbody><tr><td style="direction: ltr; width: 300px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-color: rgb(255, 221, 148); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;"><tr><td align="left" style="font-size: 0px; padding: 20px;"><div class="text" style="font-family: &quot;Microsoft YaHei&quot;; overflow-wrap: break-word; margin: 0px; text-align: left; line-height: 20px; color: rgb(32, 32, 32); font-size: 16px;"><div><p style="text-size-adjust: none; word-break: break-word; line-height: 20px; font-size: 16px; margin: 0px;"><strong>{2}</strong></p></div></div></td></tr></table></td></tr></tbody></table></div></td></tr></table></div></a></td></tr></tbody></table></div></td></tr></tbody></table></td></tr></tbody></table></div><div tindex="4" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(255, 255, 255); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;"><tbody><tr><td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;"><table width="100%" border="0" cellpadding="0" cellspacing="0" style="vertical-align: top;"><tbody><tr><td style="width: 100%; max-width: 100%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;"><div class="full" style="margin: 0px auto; max-width: 600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;"><tbody><tr><td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top;"><div style="display: inline-block; vertical-align: top; width: 100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;"><tr><td style="font-size: 0px; word-break: break-word; background-color: rgb(204, 171, 216); width: 580px; text-align: center; padding: 10px;"><div><img height="auto" alt="钉钉打卡图片" width="580" src="{3}" style="box-sizing: border-box; border: 0px; display: inline-block; outline: none; text-decoration: none; height: auto; max-width: 100%; padding: 0px;"></div></td></tr></table></div></td></tr></tbody></table></div></td></tr></tbody></table></td></tr></tbody></table></div></div>
  1. 钉钉打卡成功后通知截图


8. 完整代码请访问我的码云链接。

python学习之钉钉打卡相关推荐

  1. Python学习笔记--exe文件打包与UI界面设计

    exe文件打包与UI界面设计 前言 一.基于tkinter实现的UI设计 1.1 库的选择及思路 1.2 定位方法的选用 1.3 Frame控件 1.4 变量设置 1.5 批量设置 1.6 Text文 ...

  2. python钉钉自动打卡怎么设置_几行python代码实现钉钉自动打卡,网友:终于告别缺勤了...

    前言 众所周知因为疫情的原因大家都没有办法上学和上班,"钉钉"这个app起到了重大的作用.学校为了学生成绩开启"钉钉"之路.老师也成一个"合格&quo ...

  3. 钉钉API考勤打卡记录获取并存入数据库(python)

    前言: 钉钉有个开发平台,通过API可以开发自己企业内部应用,钉钉开发文档顺序写的有点乱,花了挺长时间才看懂,我写了一个python脚本来获取考勤记录,当然能做的不止这些,可以删除公司成员,修改部门, ...

  4. Python学习:Python分析钉钉评论(一)爬取数据

    爬取数据 从App Store官网拿下评论数据做分析 App Store评论API: https://itunes.apple.com/rss/customerreviews/page=1/id=/s ...

  5. python 闲鱼消息_GitHub - ygq556/XianyuSdd: 爬取闲鱼某关键字实时信息,将最新的发送到钉钉群聊。更多我们共同开发的项目(咸鱼反爬更新,只能作为学习使用)...

    XianyuSdd 爬取闲鱼某关键字实时信息,将最新的发送到钉钉群聊 写了一个异步爬取闲鱼商品最新信息推送到钉钉 图中的需求有两个部分值得学习. 多线程爬取最新关键字的商品信息 python对钉钉的操 ...

  6. Python学习小组课程P6-Python办公(3)邮件与钉钉消息通知

    Python办公(3)邮件与钉钉消息通知 一.前言 二.知识点 1 发送邮件 2 钉钉机器人消息通知 一.前言 注意:此为内部小组学习资料,非售卖品,仅供学习参考. 本系列课程: Python学习小组 ...

  7. python钉钉自动打卡_用Python来实现钉钉自动打卡并发送邮件短信提醒

    用Python来实现钉钉自动打卡并发送邮件短信提醒 自从用钉钉打卡,老忘记打,只会python语言,所以用Python写了一个自动跑的脚本,每天定时跑.这样就避免忘记了. 之后把adb.exe所在的文 ...

  8. python脚本实现自动钉钉打卡的技术实现细节

    文章目录 pip使用国内镜像源 临时使用 永久使用国内镜像源进行 pip安装 安卓调试工具集合 adb工具Android Platform Tools uiautomator2 控件定位工具 安装控件 ...

  9. Python钉钉打卡程序

    Python自动打上下班卡程序 本程序所用到的库和工具 库: 工具及环境: 代码实现: 源码: 说明 使用方法 本程序所用到的库和工具 库: import uiautomator2 as u2 //主 ...

最新文章

  1. 【1024创造营】精彩课程回顾
  2. VMware——安装CentOS
  3. Zeppelin 可视化操作spark sql
  4. C++知识整理 内存模型和命名空间
  5. 心海软件学生测试系统,心海软件心理管理系统
  6. 关于PBOC3.0发卡行认证的问题
  7. 微软苏州招 Android/iOS 开发!
  8. 明日之后在同一个服务器找不到人,明日之后怎么跨区加好友 看这里
  9. 时间局部性和空间局部性
  10. 世界时间UT和协调世界时间UTC
  11. Word如何从中间页插入页码
  12. 用Python写一个最简单的计时秒表
  13. 酒越陈越醇,OPPO Reno越更越香
  14. 基于java+Mysql的志愿者管理系统代码分享
  15. 关于我的英文名字Byron
  16. Android 系统图标
  17. [BZOJ4537][Hnoi2016]最小公倍数(并查集+分块)
  18. html 白色用rgba,javascript – 将RGB转换为RGBA白色
  19. 专利挖掘及专利布局规划
  20. 安装torch0.4.1的神坑

热门文章

  1. 钉钉微应用内置浏览器js缓存清理
  2. 多彩绚丽渐变Mac动态壁纸
  3. (附源码)node.js中小学生中文写作网 毕业设计 250623
  4. 单位阶跃信号是周期信号吗_这些无厘头微信号,扎你心了吗?
  5. [转载]摩托车 各车型 坐姿 疲劳逻辑详解
  6. 国密算法TF32A09硬件加密芯片
  7. 为什么狗狗听到铃声会叫
  8. mysql中update子查询,update 子查询使用介绍
  9. 【数据库系统概论】-数据库设计
  10. Springboot实现手机短信服务