女朋友是药学院的,做实验时需要在特定的网站上进行设备预约,由于预约人数过多,从而导致从浏览器登录不进去或者登录进去预约失败等情况,所以我用python帮她写了一个抢位助手,让程序自动去进行位置预定,实测表明,程序的设备预定运行结果十分理想,可以预约到自己想要预约的时间段以及设备。下面分享我是如何编写该软件助手的。
首先,在浏览器开发者工具上查看浏览器和设备预定服务器的交互信息,找出关键的信息,如登录页的URL,登录信息提交页的URL,post提交的数据,Cookies信息等。这个过程不涉及编程操作。
在浏览器上,经过了登录,一系列的跳转和点击后,就到了设备预约提交页面,然后点击提交按钮之后,设备预约就成功了,在网络调试窗口中,发现点击提交按钮之后,浏览器向后台页面http://222.200.178.***/Appointment/Appointment 发起了POST 请求,查看POST请求参数后,发现参数信息含有预约时间,预约设备ID等信息,POST参数如下:
POST参数
SubjectId=2e5f5627-3bbf-4aae-ac2b-b5cc586f4d70
SubjectProjectId
SampleNo
SampleCount
SampleStuff
SampleSize
Target
UseNature=0
ExperimentationContent
isSelectTimeScope=false
beginTime=2019-05-26 8:0
endTime=2019-05-26 8:0
userId
AppointmentStep=15
AppointmentTimes=2019-05-27 12:30:00,2019-05-27 12:45:00
EquipmentId=17ee43c4-bb30-4e43-8e6b-b82de698e20b
EquipmentPartIds
ChangeAppointmentId
VirtualEquipmentBindId
AppointmentFeeTips=false
现在,基本可以明确进行设备预约需要向后台提交的数据以及提交的地址了。为了测试该POST请求正确,我把POST请求的内容复制为curl 字段了,然后在Linux shell下运行该curl字段,
curl "http://222.200.178.***/Appointment/Appointment" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:67.0) Gecko/20100101 Firefox/67.0" -H "Accept: */*" -H "Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" --compressed -H "Referer: http://222.200.178.58/Admin?CenterBoxUrl=/Equipment/AppointmentBoxIndex"%"3Fid"%"3D17ee43c4-bb30-4e43-8e6b-b82de698e20b"%"26time"%"3D424" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "X-Requested-With: XMLHttpRequest" -H "Connection: keep-alive" -H "Cookie: ASP.NET_SessionId=yn2ot2upq03e2fssruvambif; .ASPXAUTH=43CAFF53FC7CC0B81B29369AAA24B3DB9BAB78E5E52FC467BE9CB3728B82AD0E0C55F5C92F077AE302C15F9C730018E4690E8705C18EF602A34BE70988E617710376CDC6C44C0472F8028F54B4EF5BF7E7B8DD72748C249578473E3E56CC1035FA86E765179B392276AC9DB0D098227F7BBFDE954136B1BB0EB39C4B0B1F3D25" --data "SubjectId=2e5f5627-3bbf-4aae-ac2b-b5cc586f4d70&SubjectProjectId=&SampleNo=&SampleCount=&SampleStuff=&SampleSize=&Target=&UseNature=0&ExperimentationContent=&isSelectTimeScope=false&beginTime=2019-05-26 8:0&endTime=2019-05-26 8:0&userId=&AppointmentStep=15&AppointmentTimes=2019-05-27 12:30:00,2019-05-27 12:45:00&EquipmentId=17ee43c4-bb30-4e43-8e6b-b82de698e20b&EquipmentPartIds=&ChangeAppointmentId=&VirtualEquipmentBindId=&AppointmentFeeTips=false"
返回的结果是提示我设备预约成功。到了这一步,可以理出一个基本的编程实现思路:构造POST请求参数,向指定的URL提交POST请求,用python实现就是import requests, requests.post(url,postdata),很简单,对不对。嗯嗯~,是不是我们的设备预约助手到这里就可以结束呢,回头再看一下curl 字段的内容,发现了下面的cookies 信息
Cookie:
ASP.NET_SessionId=yn2ot2upq03e2fssruvambif;
.ASPXAUTH=43CAFF53FC7CC0B81B29369AAA24B3DB9BAB78E5E52FC467BE9CB3728B82AD0E0C55F5C92F077AE302C15F9C730018E4690E8705C18EF602A34BE70988E617710376CDC6C44C0472F8028F54B4EF5BF7E7B8DD72748C249578473E3E56CC1035FA86E765179B392276AC9DB0D098227F7BBFDE954136B1BB0EB39C4B0B1F3D25
既然存在cookie信息,我们每次和服务器进行交互时,就得把cookie值带上,cookie有两个,一个是ASP.NET_SessionId,另一个是.ASPXAUTH。那么服务器是什么时候把这两个cookie值提交过来的呢?我还是在浏览器上做尝试。重新返回登录界面,打开调试窗口,找到cookie参数项,发现了ASP.NET_SessionId 值,但却没看到.ASPXAUTH 值,输入账号密码之后,验证通过之后,可以看cookie参数项存在.ASPXAUTH ,至此,两个cookie值可以拿到了。
现在,可以重新理一下编程的思路了:先访问登录界面url,获取ASP.NET_SessionId 值,然后向后台登录链接发送POST请求,验证成功之后就可以获取到.ASPXAUTH cookie值,接着就是构造POST请求参数,向指定的预约URL提交POST请求,至此,整个预约助手的工作流程就明朗了。
下面是源代码:
#!/bin/bash/python3
#coding:utf-8
#实验设备抢位助手,开发by linjunji in SYSU 2019-05-27
import requests
from datetime import datetime,timedelta
import hashlib
import sys
import time
import configparser
import re
import base64
import logging
import webbrowser
logging.basicConfig(level=logging.INFO,format='%(asctime)s: %(message)s')
#定义登录提交URL以及预约请求URL
login_url="http://222.200.178.***/Account/Login" #登录界面url
vaild_url="http://222.200.178.***/Account/LoginSubmit" #账号密码提交验证URL
appointment_url="http://222.200.178.***/Appointment/Appointment" #预约请求提交页面
#定义头部信息
user_agent="Mozilla/5.0 (Windows NT 10.0; WOW64; rv:67.0) Gecko/20100101 Firefox/67.0"
referer="http://222.200.178.***/Admin?CenterBoxUrl=/Equipment/AppointmentBoxIndex%3Fid%3D17ee43c4-bb30-4e43-8e6b-b82de698e20b%26time%3D424"
Content_Type="application/x-www-form-urlencoded"
header={'User-Agent':user_agent,'Referer':referer,'Content-Type':Content_Type}
#密码MD5计算
def get_password_md5(password):
password_bin=password.encode("utf-8")
m=hashlib.md5()
m.update(password_bin)
return m.hexdigest()
#用户名base64编码
def get_base64_loginname(name):
name_b = base64.b64encode(name.encode("utf-8"))
return name_b.decode("utf-8")
postdata={
'SubjectId':'2e5f5627-3bbf-4aae-ac2b-b5cc586f4d70',
'SubjectProjectId':'',
'SampleNo':'',
'SampleCount':'',
'SampleStuff':'',
'SampleSize':'',
'Target':'',
'UseNature':'0',
'ExperimentationContent':'',
'isSelectTimeScope':'false',
'beginTime':'2019-05-27 8:0', #该字段是否会改变
'endTime':'2019-05-27 8:0',
'userId':'',
'AppointmentStep':'15',
'AppointmentTimes':'2019-05-28 21:30:00,2019-05-28 21:45:00', #请求时间
'EquipmentId':'17ee43c4-bb30-4e43-8e6b-b82de698e20b', #设备ID号
'EquipmentPartIds':'',
'ChangeAppointmentId':'',
'VirtualEquipmentBindId':'',
'AppointmentFeeTips':'false'
}
#设备ID和名称对应表
device_msg={
"819a7b77-dc03-40f8-b1ef-1824ea8e4683":"400M核磁共振谱仪1",
"17ee43c4-bb30-4e43-8e6b-b82de698e20b":"400M核磁共振谱仪2 (AvanceIII)",
"6a7f349d-6295-4a53-817d-413f35be07bb":"500M超导核磁共振波谱仪-3"
}
pattern_title=re.compile('<div class="a_center f_bold">(.+)</div>') #匹配标题 ['您的预约已提交,请在预约时间前24小时登录确认!您当前的预约时间如下:']
pattern_head=re.compile('<th>(.+?)</th>') #匹配行数 ['开始时间', '结束时间', '时长']
pattern_time=re.compile('<td>([^//td<>]+?)</td>') #['2019年05月28 21时30分', '2019年05月28 22时00分', '0.5', '</td><td>', '共:0.5']
if __name__=="__main__":
#读取配置文件,从配置文件读取登录名,密码,预订时间,设备信息
cf=configparser.ConfigParser() #读取配置文件
try:
cf.read('config.ini')
user_name = cf.get('user', 'name')
user_password = cf.get('user','password')
time1 = cf.get('time1','time')
time2 = cf.get('time2', 'time')
time3 = cf.get('time3', 'time')
time4 = cf.get('time4', 'time')
device_id=cf.get('device','device_id')
except Exception as e:
logging.info('【respone】配置文件读取异常,请检查!,程序两秒钟后退出')
logging.info(e)
time.sleep(2)
sys.exit()
logging.info("【respone】成功加载配置文件")
logging.info("【respone】{},欢迎您进入自动预订系统".format(user_name))
nextday=(datetime.now()+timedelta(days=1)).strftime('%Y-%m-%d')
booking_time=[time1,time2,time3,time4]
time_str=[' '.join([nextday,t]) for t in booking_time if len(t)>0]
booking_time_str=','.join(time_str)
begin_time="{} 8:0".format(nextday)
end_time=begin_time
#定义post提交数据
postdata["beginTime"] = begin_time
postdata['endTime'] = end_time
postdata['AppointmentTimes'] = booking_time_str
postdata['EquipmentId'] = device_id
logging.info("【respone】你将要预约的时间是:{}".format(booking_time_str))
logging.info("【respone】你将要预订的设备是:{}".format(device_msg[device_id]))
s = requests.Session() # session会话会自动把cookie带进去
# post提交登录信息
data = {"LoginName": get_base64_loginname(user_name), 'LoginPassword': get_password_md5(user_password),'date': ''}
r = s.post(vaild_url, headers=header, data=data, allow_redirects=True)
# 打印出cookies信息
for cookies in r.cookies.keys():
logging.debug(cookies+':'+r.cookies.get(cookies))
r.encoding = "utf-8"
logging.debug(r.text)
if (r.text.find("true") > 0):
logging.info("【respone】你已经成功登录设备预约系统...正在等待系统开放预约操作 ")
else:
logging.info("【respone】登录失败,请重新运行程序尝试,按任意字符退出")
sys.exit()
logging.info("【respone】提醒:程序将于21点59分55秒进行设备预订,请勿关闭本程序")
while(True):
now = datetime.now()
hour = now.hour # 小时
minutes = now.minute # 分
second = now.second # 秒
if hour == 21 and minutes == 59 and second > 45:
logging.info("【respone】10s后开始进行设备预定")
for i in range(10):
time.sleep(1)
logging.info("【respone】{}s...".format(10 - i))
break
logging.info("【respone】开始进行设备预约")
try:
r=s.post(appointment_url,data=postdata,headers=header,timeout=10)
r.encoding='utf-8'
rst=r.text
filename="booking_result.html"
f=open(filename,'w',encoding="utf-8")
f.write("<head>")
f.write("<meta charset='UTF-8'>")
f.write("<title>预订结果如下</title>")
f.write("</head>")
f.write(r.text)
f.close()
logging.info("【respone】预订结果在浏览器在浏览器查看")
webbrowser.open(filename)
except Exception as e:
logging.info("【respone】本次预约可能失败,请用网页登进系统查看")
logging.info(e)
程序运行结果如下,到时间之后就会开始预约请求,请求结果将会在浏览器中显示。