Python Requests实现图书馆座位自动预约与定时提醒
本文介绍使用Python Requests库模拟登录图书馆预约系统,通过获取Cookie和隐藏表单字段构造POST请求实现自动预约。结合Crontab定时任务与邮件模块,实现每日自动抢座并发送结果提醒,适用于高校图书馆座位管理系统。
本文分享使用Python实现图书馆座位自动预约的完整代码示例,供读者参考学习。
环境配置
通过公网服务器定时执行脚本,并将结果发送至个人QQ邮箱,从而在微信端接收预约成功与否的消息提醒。
编辑 /etc/crontab 文件以配置定时任务。
设置每天早晨7:01自动运行该脚本。
程序运行流程
以 yuyue.juneberry.cn 网站为例进行说明。
- 使用GET请求访问登录页面,获取Cookie及表单中的隐藏POST字段。
- 构造登录所需的POST数据,包含从表单中提取的隐藏POST字段。
- 发送构造好的POST数据以模拟登录,激活Cookie(使其具备登录权限)。
- 使用GET请求访问座位预约界面,激活Cookie(使其具备预约座位权限)。
- 发送POST预约请求,完成座位预约操作。
- 解析返回结果,判断预约是否成功,并通过邮件发送提醒。
关键技术点
- Requests库中的 requests.session() 方法可用于创建支持Cookie传递的会话对象。
- 提取页面中 <input type=hidden> 的数据,并将其包含在POST请求数据中。
- 通过抓包分析网站逻辑,筛选出各请求的关键参数,并在程序中实现对应逻辑。
函数详细说明
- FUCK() 为主类定义。
- _get_date_str(self):获取当前日期并加上一天,用于构造URL特征字段(图书馆系统通常支持提前一天预约)。
- _get_order_url(self):构造“预约座位”POST请求的目标URL。
- _get_static_post_attr:解析GET请求返回的页面,提取 <input type=hidden> 字段,用于后续构造POST数据。
- login(self):实现用户登录功能。
- run(self):执行座位预约功能。
- _is_success(self, text):判断预约结果是否成功。
- error_log_once(self, text='default error (once)'):
- error_log(self, text='default error'):这两个函数用于设置程序状态为“已出错”或“未出错”,在自动化运行中避免将重复错误信息写入日志。
- error_log(self, text='default error'):单次将错误信息写入本地日志文件。
- sendmail.send_mail():邮件发送模块,用于通知预约结果。
完整代码及详细注释如下。
# /bin/python
# -*- coding:utf-8 -*-
import time
import sys
import requests
from bs4 import BeautifulSoup
from mail import sendmail
__author__ = 'xy'
# 主类
class FUCK():
def __init__(self, username, password, seatNO, mailto):
"""
以四个参数初始化,用户名,密码,要预约的座位号,接受预约结果提醒邮件的邮箱
"""
self.username = username
self.password = password
self.seatNO = seatNO
self.mailto = mailto
self.base_url = 'http://yuyue.juneberry.cn'
self.login_url = 'http://yuyue.juneberry.cn'
self.order_url = self._get_order_url()
self.login_content = ''
self.middle_content = ''
self.final_content = ''
self.s = requests.session() # 创建可传递cookies的会话
# post data for login
self.data1 = {
'subCmd': 'Login',
'txt_LoginID': self.username, # S+学号
'txt_Password': self.password, # 密码
'selSchool': 60, # 60表示北京交通大学
}
# post data for order a seat
self.data2 = {
'subCmd': 'query',
}
# 自定义http头,然而我在程序里并没有使用
self.headers = {
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
}
self.login()
self.run()
self._is_success(self.final_content)
# 怀疑程序出错时,取消下行注释,可打印一些参数
# self._debug()
def _get_date_str(self):
s = time.localtime(time.time())
########333
date_str = str(s.tm_year) + '%2f' + str(s.tm_mon) + '%2f' + str(s.tm_mday + 1)
date_str = date_str.replace('%2f1%2f32', '%2f2%2f1') \
.replace('%2f2%2f29', '%2f3%2f1') \
.replace('%2f3%2f32', '%2f4%2f1') \
.replace('%2f4%2f31', '%2f5%2f1') \
.replace('%2f5%2f32', '%2f6%2f1') \
.replace('%2f6%2f31', '%2f7%2f1') \
.replace('%2f7%2f32', '%2f8%2f1') \
.replace('%2f8%2f32', '%2f9%2f1') \
.replace('%2f9%2f31', '%2f10%2f1') \
.replace('%2f10%2f32', '%2f11%2f1') \
.replace('%2f11%2f31', '%2f12%2f1') \
.replace('%2f12%2f32', '%2f1%2f1')
return date_str
def _get_order_url(self):
return "http://yuyue.juneberry.cn/BookSeat/BookSeatMessage.aspx?seatNo=101001" + self.seatNO + "&seatShortNo=01" + self.seatNO + "&roomNo=101001&date=" + self._get_date_str()
def _get_static_post_attr(self, page_content, data_dict):
"""
拿到<input type='hidden'>的post参数,并添加到post_data中
"""
soup = BeautifulSoup(page_content, "html.parser")
for each in soup.find_all('input'):
if 'value' in each.attrs and 'name' in each.attrs:
data_dict[each['name']] = each['value'] # 添加到login的post_data中
# self.data2[each['name']] = each['value'] # 添加到order的post_data中
return data_dict
def _debug(self):
print self.order_url
print self.data1
print self.data2
print self.headers
print self.s.cookies
# print self.login_content
# print self.middle_content
print self.final_content
def login(self):
homepage_content = self.s.get(self.base_url).content
self.data1 = self._get_static_post_attr(homepage_content, self.data1)
r = self.s.post(self.login_url, self.data1)
self.login_content = r.content
def run(self):
# 这个get的意思是:原先的cookie没有预约权限,
# 访问这个get之后,会使cookie拥有预约权限,从而执行下一个post
self.middle_content = self.s.get('http://yuyue.juneberry.cn/BookSeat/BookSeatListForm.aspx').content
# 经测试,这个post只需要一个subCmd的参数就可以正常返回,因此不必根据get内容更新post参数
# self.data2 = self._get_static_post_attr(middle_content, self.data2)
# 这个post请求完成了预约功能!
r = self.s.post(self.order_url, self.data2)
self.final_content = r.content
def _is_success(self, text):
"""
接受最终的html内容,判断是否成功,并触发日志记录和邮件提醒
"""
if '<h5 id="MessageTip">已经存在有效的预约记录。</h5>' in text:
self.clear_error_once('[done!] You already ordered a seat!')
elif '<h5 id="MessageTip">选择的日期不允许预约。</h5>' in text:
self.clear_error_once('[done!] Date is wrong!')
elif '<h5 id="MessageTip">所选座位已经被预约。</h5>' in text:
self.clear_error_once('[done!] This seat is not available, maybe taken by others!')
elif '<h5 id="MessageTip">座位预约成功' in text:
self.clear_error_once('[done!] Success! An email is sending to you!')
sendmail.send_mail('BJTU Library Seat_NO:' + self.seatNO + 'ordered!',
'Sending by robot. Do not reply this mail!', self.mailto)
else:
self.error_log_once('Error! 302 to login page')
def error_log_once(self, text='default error (once)'):
try:
is_error_file = open('./isopen_xy.txt', 'r')
except:
is_error_file = open('./isopen_xy.txt', 'w')
if '1' not in is_error_file.read():
print 'writting error to log...'
self.error_log(text)
else:
print 'already written to log'
is_error_file.close()
sendmail.send_mail('BJTU_Library system error once !', 'error text!')
def error_log(self, text='default error'):
is_error_file = open('./isopen_xy.txt', 'w')
is_error_file.write('1\n')
is_error_file.close()
f = open("./log_xy.txt", 'a')
f.write(time.strftime("%Y-%m-%d %X", time.localtime()) + text + '\n')
f.close()
def clear_error_once(self, text='success'):
print text
is_error_file = open('./isopen_xy.txt', 'w')
is_error_file.write('0\n')
is_error_file.close()
if __name__ == '__main__':
if len(sys.argv) < 5:
print 'Usage: python library.py [username] [password] [seat_NO] [email]'
print 'eg. python library.py S13280001 123456 003 XXXX@qq.com\n'
print 'Any problems, mail to: i[at]cdxy.me'
print '#-*- Edit by cdxy 16.03.24 -*-'
sys.exit(0)
else:
FUCK(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
以上就是本文的全部内容,希望对大家的学习有所帮助,也欢迎大家继续支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









