迫于学校对健康打卡的要求愈发严格,花半天简单写了个脚本,用于自动健康打卡,写完往服务器一扔,加个crontab任务,告别枯燥打卡。
本文记述下此过程中遇到的一些坑以及解决方法,以备查阅。
以下代码均使用Chrome作为webdriver。
关于抓包获取真实URL
日常打卡是通过企业微信中的链接跳转至打卡页面,并自动完成登录和身份信息填充。复制该页面链接并使用浏览器打开后,会提示“请在微信客户端打开链接”。 这里就要用到Fiddler来获取可在浏览器上打开的真实URL了。本次仅使用Fiddler中最基本的功能,故不多做介绍,有需要可以自行搜索。
关于页面跳转
很多时候我们最终需要页面的URL,不好通过结构分析来找到规律,比较简单的思路是从主页模拟点击,一路跳转到最终需要的页面。此法虽然费时(相对而言),但个人使用足矣。
关于获取图片验证码
本校登录系统中使用了随机图片验证码,利用验证码URL保存的图片,与当前显示并不一致,比较明显的思路是截图获取。截图获取又可分为两类,一类是先给整个页面截图(driver.get_screenshot_as_file(savePath)),再通过图片元素的坐标、尺寸来进行截取(Pillow库);另一种是直接截取元素(element.screenshot(savePath))。在条件允许的情况下,显然后者更为便利。
关于验证码识别
获取到验证码图片后,还需要OCR识别。百度、腾讯等云服务提供商,基本都有自己的OCR服务,每月的免费额度供个人使用足矣。而Python本身也有一个第三方库——Ddddocr(带带弟弟OCR)。本校系统的验证码并不复杂,使用Ddddocr测试多次,几乎没有识别错误的情况,所以不再花时间去调用别厂的OCR服务,直接安装使用Ddddocr了。使用Ddddocr识别图片的参考代码如下:
import ddddocr
def verCodeReco(picPath):
# verification code recognition
ocr = ddddocr.DdddOcr()
with open(picPath, 'rb') as f:
imgBytes = f.read()
return ocr.classification(imgBytes)
picPath为需要识别的图片路径,函数以字符串返回识别结果
关于地理位置
健康打卡中有一项是获取当前地理位置,可以使用以下代码来给浏览器设置地理位置:
driver.execute_cdp_cmd("Emulation.setGeolocationOverride", {
"latitude": latitude,
"longitude": longitude,
"accuracy": 100
})
其中latitude为纬度,longtitude为经度。
仅在无头模式下报错
大部分情况应该可以参考这个回答。
但我遇到的情况可能更特殊一些。当仅在无头模式下报错后,我在代码中插入了截图语句,通过查看截图来确定问题所在。结果发现问题在获取定位权限上。
无头模式下默认是不给定位权限的,所以即使我们如上节所述设置了经纬度也没用。我在中文网络搜不到相关问题和解决方法,最后在国外论坛找到如下应对措施:
driver.execute_cdp_cmd('Browser.grantPermissions', {
'origin': mainPage,
'permissions': ['geolocation']
})
其中mainPage是访问网站的域名。
关于无法点击
有些元素用element.click()方法点击不到,可以换一种方法:
from selenium.webdriver.common.keys import Keys
element.send_keys(Keys.SPACE)
关于动态加载的下拉列表
可以先点击框内其他元素,比如一般都有个正三角,点击后会变倒三角,且显示下拉选项,然后再使用Select,通过value或index等来选择。
关于嵌套iframe
页面中有些元素是在嵌套iframe中的,无法直接定位,需要先定位到iframe元素,然后driver.switch_to_frame(iframeElement),之后正常定位元素即可。
弹出密码保存提示
禁用这一功能:
prefs = {"":""}
prefs["credentials_enable_service"] = False
prefs["profile.password_manager_enabled"] = False
options.add_experimental_option("prefs", prefs)
设定隐性等待
手动sleep()过于麻烦,大部分情况下设置全局的隐性等待即可,同时追求稳定和性能,需要单独设置元素等待。
全局隐形等待的设置:
driver.implicitly_wait(timeout)
其中timeout是超时时间,整个driver的持续时间内,所有操作都会等待页面全部加载完毕,直到超过timeout值。
处理复合class元素
有些元素仅有复合的class属性,而xpath也不好用的时候,可以使用driver.find_element_by_css_selector("[class='class值']")来定位。因为driver.find_element_by_class_name的参数,是不支持复合class属性的。
代码
最后贴上俩自动打卡脚本,具体信息隐去。
健康打卡
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.keys import Keys
from diyLib.verCodeReco import verCodeReco
from time import sleep
import time
# define options
options = Options()
options.add_argument('--headless')
options.add_argument("--window-size=1920,1080")
options.add_experimental_option('excludeSwitches', ['enable-logging'])
# options.add_experimental_option("detach", True) # 不自动关闭浏览器
mainPage = 'https://***'
verCodePath = '***'
timeout = 10
maxTry = 3
# 判断登录是否成功
def logSuc(driver):
errMsg = driver.find_elements_by_class_name('auth_error')
if len(errMsg) == 0:
print('登录成功')
return True
else:
print('登录失败')
return False
def healthCheck(stuNum, stuPasswd, latitude, longitude, phone, temperature, resiType):
# health check
driver = webdriver.Chrome(options=options)
driver.implicitly_wait(timeout)
# set the location
driver.execute_cdp_cmd("Emulation.setGeolocationOverride", {
"latitude": latitude,
"longitude": longitude,
"accuracy": 100
})
# driver.execute_cdp_cmd("Page.setGeolocationOverride", {
# "latitude": latitude,
# "longitude": longitude,
# "accuracy": 100
# })
driver.execute_cdp_cmd('Browser.grantPermissions', {
'origin': mainPage,
'permissions': ['geolocation']
})
login = False
tryNum = maxTry
print('开始登陆')
while (login == False and tryNum > 0):
#go to the main page and login
driver.get(mainPage)
# name and password input
driver.find_element_by_xpath('//*[(@id = "username")]').send_keys(stuNum)
driver.find_element_by_xpath('//*[(@id = "password")]').send_keys(stuPasswd)
# get the verification code img
driver.find_element_by_xpath('//*[(@id = "captchaImg")]').screenshot(verCodePath)
# recognize verification code and input
verCode = verCodeReco(verCodePath)
driver.find_element_by_xpath('//*[(@id = "captchaResponse")]').send_keys(verCode)
driver.find_element_by_xpath('//*[contains(concat( " ", @class, " " ), concat( " ", "full_width", " " ))]').click()
sleep(3)
login = logSuc(driver)
tryNum-=1
if login == False:
print('密码错误')
return False
sleep(1)
uiNav = driver.find_element_by_id('ui_nav')
navList = uiNav.find_elements_by_tag_name('a')
navList[1].click()
# now we are in the homepage of all work service
servFav = driver.find_element_by_class_name('fuwutab')
servList = servFav.find_elements_by_tag_name('li')
servList[3].click()
# now we are in the favorait service
healthServ = driver.find_element_by_class_name('kuai').find_element_by_tag_name('a')
healthUrl = healthServ.get_attribute('href')
driver.get(healthUrl)
# now we are in the check page
formIframe = driver.find_element_by_id('pageFrame')
driver.switch_to_frame(formIframe)
driver.find_element_by_id('STUDENT_PHONE').send_keys(phone)
driver.find_element_by_id('STZK_0').send_keys(Keys.SPACE)
driver.find_element_by_id('TW').send_keys(temperature)
# to select residence type, 1 for Shanghai, 3 for other
driver.find_element_by_class_name('select2-selection__arrow').click()
driver.find_element_by_class_name('select2-selection__arrow').click()
selectResi = Select(driver.find_element_by_id('DQJZD'))
selectResi.select_by_index(resiType)
# select 未途径中高风险地区
driver.find_element_by_id('SFTJGFXDQ_1').send_keys(Keys.SPACE)
driver.find_element_by_id('BTN_SAVE').click()
# screenshot for headless test
# driver.get_screenshot_as_file('CrawlResult/screen.png')
driver.quit()
print(time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()), 'task' , stuNum , 'done')
sleep(1)
return True
# ***打卡
healthCheck('123', '123', 11, 11, 111, 36, 3)
某站日常签到
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from time import sleep
import time
from retrying import retry
# define options
options = Options()
options.add_argument('--headless')
options.add_argument("--window-size=1920,1080")
options.add_experimental_option('excludeSwitches', ['enable-logging'])
# 取消自控提示
options.add_experimental_option('useAutomationExtension',False)
options.add_experimental_option("excludeSwitches",['enable-automation'])
# 防止弹出密码保存框
prefs = {"":""}
prefs["credentials_enable_service"] = False
prefs["profile.password_manager_enabled"] = False
options.add_experimental_option("prefs", prefs)
# options.add_experimental_option("detach", True) # 不自动关闭浏览器
mainPage = 'https://***'
timeout = 30
@retry(stop_max_attempt_number=5)
def isCheck(driver):
# 检查是否已经签到
checkBox = driver.find_elements_by_css_selector("[class='click-qiandao btn btn-qiandao']")
if len(checkBox) == 0: return True
else: return False
@retry(stop_max_attempt_number=5)
def bbsCheck(userName, passwd):
# 签到程序
driver = webdriver.Chrome(options=options)
driver.implicitly_wait(timeout)
driver.get(mainPage)
driver.find_element_by_xpath('//*[contains(concat( " ", @class, " " ), concat( " ", "swal2-close", " " ))]').click()
# 使用CSS选择器处理复合class的div元素
driver.find_element_by_css_selector("[class='login-btn navbar-button']").click()
driver.find_element_by_name('username').send_keys(userName)
driver.find_element_by_name('password').send_keys(passwd)
driver.find_element_by_css_selector("[class='go-login btn btn--primary btn--block']").click()
sleep(10)
burger = driver.find_element_by_class_name('burger')
driver.execute_script("arguments[0].click();", burger)
alCheck = isCheck(driver)
# driver.get(mainPage)
authorField = driver.find_element_by_class_name('author-fields')
numList = authorField.find_elements_by_class_name('num')
# 获取积分余额
integral = float(numList[0].get_attribute('textContent'))
if alCheck:
print(time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()), 'account:', userName, 'already check, remain:', integral)
driver.quit()
return True
else:
driver.find_element_by_css_selector("[class='click-qiandao btn btn-qiandao']").click()
sleep(1)
print(time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()), 'account:', userName, 'check success, remain:', integral + 5)
driver.quit()
return True
bbsCheck(***, ***)
print(time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()), 'task done')

Comments NOTHING