脚本编写过程记录
本来我是不必造轮子来写这个自动化打卡脚本的,但是无奈之前依赖的脚本在一次删库事件中丢失了,然后在某人的push之下完成了该脚本,感谢伟大的python以及python各类库的开发者们,你们真滴很不错~
登录过程
在进行写脚本之前首先跑通整个登录过程并抓包分析,本文是根据同院同学的打卡脚本找到的登录入口,现在学校已经进行了微服务的升级,升级之后的登录入口更改,并且需要验证码校验,等有空了再研究。
点击进入该链接后使用统一账户登录,此时将会发送一POST
请求,内容如下:
登录完成后获取到cookie
,同时跳转至https://wfw.scu.edu.cn/ncov/wap/default/index,可以获取到上次填写的信息
最后在https://wfw.scu.edu.cn/ncov/wap/default/save提交填报信息
基础知识
HTTP头字段(英语:HTTP header fields)是指在超文本传输协议(HTTP)的请求和响应消息中的消息头部分。它们定义了一个超文本传输协议事务中的操作参数。HTTP头部字段可以自己根据需要定义,因此可能在 Web 服务器和浏览器上发现非标准的头字段
头部字段代表含义,可在https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers中查询
登录请求
使用python
的requests
库发送请求
HTTP协议本身是无状态的,为了让请求之间保持状态,有了session
和cookie
机制。requests
也提供了相应的方法去操纵它们。
requests
中的session
对象能够让我们跨HTTP
请求保持某些参数,即在同一个 Session 实例发出的所有请求之间保持 cookie。
在请求之前,首先声明一个新的session用于会话保持
import requests
session=requests.Session()
r=session.get(url,header)#后续请求发送示意
接下来进行账户登录,设置好post
传输的data
以及headers
,虽然使用的是dict
格式,但Reqeusts
支持自动将传递给requests.post()
的data
参数以form
表单形式发送post
请求,因此使用的数据提交格式仍然为application/x-www-form-urlencoded
loginURL='https://wfw.scu.edu.cn/a_scu/api/sso/check'
loginData={
'username': username,
'password': password
}
loginHeader={
'Host':'wfw.scu.edu.cn',
'User-Agent':'',
'Origin':'https://wfw.scu.edu.cn',
'Referer':''
}
发送登录请求,session
对象会帮助我们保存cookie
,说白了我们登录的原因也就是为了获取cookie
loginR=session.post(loginURL,data=loginData,headers=loginHeader)
获取信息
现在已获取了cookie
,可以使用session
对象拉取上次填报信息,同样好设置请求头,伪装成一次正常的访问,提前在burp
中抓包可以预想到接收到的内容
接收到的内容比较复杂,但是我们记得在平时打卡的时候,大部分时候我们都不需要进行一项一项地填写,系统将会为我们自动填充上次填写的内容,因此我们可以在返回的response
中找到我们上次填写的信息oldInfo
。
我们直接将response
切换为string
格式,然后使用正则匹配式找到我们上次填写的oldInfo
信息,再把字符串转为dict
格式,方便post
请求上传data
import requests
import re
import json
getR=session.get(getURL,headers=getHeader).text
oldInfo=re.findall(r'.*?oldInfo: (.*),.*?',getR)#正则匹配
oldData=eval(oldInfo[0])#将字符转化为json
re.findall
(返回string中所有与pattern相匹配的全部字串,返回形式为数组),使用方法如下
re.findall(pattern, string, flags=0)
提交信息
将刚才获取的oldInfo
使用POST
请求发送到目标URL
postURL='https://wfw.scu.edu.cn/ncov/wap/default/save'
postHeader={
'Host':'wfw.scu.edu.cn',
'User-Agent':'',
'Content-Type': 'application/x-www-form-urlencoded;',
'X-Requested-With': 'XMLHttpRequest',
'Content-Length': '2082',
'Origin':'',
'Connection': 'keep-alive',
'Referer':''
}
postR=session.post(postURL,data=oldData,headers=postHeader).json()
配置邮件客户端
SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。
Python对SMTP支持有
smtplib
和smtplib
负责发送邮件。
其实现在已经填报成功了,不过还是设个邮件每天播报一下,说不定哪一天就gg了呢
使用到的库如下
import smtplib
from email.mime.text import MIMEText
构造MIMEText
对象时,第一个参数就是邮件正文,第二个参数是MIME的subtype,传入plain
表示纯文本,最终的MIME就是'text/plain'
,最后一定要用utf-8
编码保证多语言兼容性。
msg = MIMEText(content, 'plain', 'utf-8')
msg['Subject'] = '打卡播报'
msg['From'] = From
msg['To'] = To
发送邮件的过程如下,
client = smtplib.SMTP_SSL('smtp.qq.com', smtplib.SMTP_SSL_PORT)#设计邮件客户端
client.login(From, pwd)#登录邮件服务器
client.sendmail(From, To, msg.as_string())
client.quit()
login()
方法用来登录SMTP服务器,sendmail()
方法就是发邮件,邮件正文是一个str
,as_string()
把MIMEText
对象变成str
这里的mail_password填写的为授权码而非自己的QQ邮箱密码,获取授权码的过程如下
定时任务
设定为每天早上执行任务,在文章[1]中总结了几种定时执行任务的方法,本文使用的是任务框架APScheduler
使用的库以及使用方法如下:
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.schedulers.background import BackgroundScheduler
#创建调度器:BlockingScheduler
scheduler = BlockingScheduler(timezone='Asia/Shanghai')
#添加任务
scheduler.add_job(funcName, 'cron',hour=7,minute=40)
scheduler.start()
时区设置不是必须的,如果你能忍受长段warning
的话
另外APScheduler支持多种调度方法,包括了date(一次性日期),interval(间隔)和cron(个性化)
# 2016-12-12 12:00:00运行一次job_function
sched.add_job(job_function, 'date', run_date=datetime(2016, 12, 12, 12, 0, 0), args=['text'])
# 每两个小时调一下job_function
sched.add_job(job_function, 'interval', hours=2)
# 截止到2016-12-30 00:00:00,每周一到周五早上五点半运行job_function
sched.add_job(job_function, 'cron', day_of_week='mon-fri', hour=5, minute=30, end_date='2016-12-31')
总结
感谢同院同学的脚本参考,让我在实施过程中少走了很多弯路,同时再一次认识到自己的不足之处,对于HTTP请求头以及正则表达式的理解不够充分,下次另外写文章专门整理一下。