Skip to main content

用requests等库封装的东莞理工学院相关系统的爬虫脚本库,亦是东莞理工学院第一个pypi的爬虫库

Project description

dugt-requests Build Status README GitHub

0. 前言

dgut-requests是一款适用于东莞理工学院系统的Python库(要求Python3.7及以上版本),主要基于requests库进行再抽象并实现所需功能,采用面向切面编程(AOP),目前基于该库已做出勤工俭学自动考勤、疫情防控自动打卡、出入校快速申请等小应用。

这是一篇面向编程新手的帮助文档,我会通过例子向你展示这个库的功能。如果你不熟悉dgut-requests库,请从头开始完整阅读本文档,并在自己的电脑上尝试运行一下(PS:演示所使用的操作系统是windows 10)。如果你有编程基础,并且对Python语言比较熟悉,那可以直接阅读说明文档

1. 安装

请先确保自己已经安装了pip

pip install dgut-requests

如果安装得很慢,甚至报错的话,很可能是网速问题,这时候可以使用镜像源进行下载。我个人比较推荐的镜像源是清华镜像源,我们可以将上面的cmd命令改成这样:

pip install -i "https://pypi.tuna.tsinghua.edu.cn/simple/" dgut-requests

在Python交互界面环境下,输入import dgut_requests没有报错即成功安装了dgut-requests库。

Python 3.8.1 (tags/v3.8.1:1b293b6, Dec 18 2019, 23:11:46) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import dgut_requests
>>>

2. 基本用法

2.1. 导入

  • 旧版
>>> from dgut_requests.dgut import dgutUser, dgutXgxt, dgutIllness
>>> dgutUser
<class 'dgut_requests.dgut.dgutUser'>
>>> dgutXgxt
<class 'dgut_requests.dgut.dgutXgxt'>
>>> dgutIllness
<class 'dgut_requests.dgut.dgutIllness'>
>>>
  • 但是更建议使用新版接口
>>> from dgut_requests import DgutUser, DgutXgxt, DgutIllness
>>> DgutUser
<class 'dgut_requests.dgut.DgutUser'>
>>> DgutXgxt
<class 'dgut_requests.dgut.DgutXgxt'>
>>> DgutIllness
<class 'dgut_requests.dgut.DgutIllness'>
>>>

如果想要更加方便的话,可以这样

>>> from dgut_requests.dgut *
>>> DgutUser
<class 'dgut_requests.dgut.DgutUser'>
>>> DgutXgxt
<class 'dgut_requests.dgut.DgutXgxt'>
>>>

2.2. 构建账号,实现登录

from dgut_requests import DgutUser

u = DgutUser("201841312111", "123456") # DgutUser(username, password)

response = u.signin("https://cas.dgut.edu.cn/home/Oauth/getToken/appid/xgxtt.html") # 登录学工系统, signin(login_url: str)
print(response)
# <Response [200]>
print(response.text)
"""
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
<html>
<head>
<title>学生信息管理系统</title>
<meta http-equiv="refresh" content="0;URL=homepage.jsp">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head><body>
正在进入首页
</body>
</html>
"""



response = u.session.get("http://stu.dgut.edu.cn/student/basicinfo/basicInfo.jsp")
print(response)
# <Response [200]>
print(response.text)
"""
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>学生基本信息</title>
<link href="/css/main.css" rel="stylesheet" type="text/css">
<script type="text/javascript">
function submitTo(formId, url) {
        var form = document.getElementById("" + formId);
        form.action = url;
        form.submit();
}
function SubmitToBlank(formId, url) {
        var form = document.getElementById("" + formId);
        form.action = url;
        form.target = "_blank";
        form.submit();
        form.action = '';
        form.target = "_self";
}
</script>
</head>
<body>
……
"""

2.3. 获取考勤职位信息 & 学工系统考勤

from dgut_requests import DgutXgxt

u = DgutXgxt("201841312111", "123456") # DgutUser(username, password)

# get_workAssignment()方法获取考勤职位信息,返回一个包含二元组的列表,每一个二元组包含考勤职位ID和考勤职位名。当你没有任何职位时,返回空列表[]
print(u.get_workAssignment())
# [('9000', 'XXX学生助理'), ('9001', 'xxx办公室助理')]


# attendance(flag: int, workAssignmentId: int = None)
# flag=1表示签到,flag=2表示签退
# workAssignmentId为None或者缺省时,表示调用get_workAssignment()方法并获取第一个职位信息的ID作为该参数
print(u.attendance(1))
# {'message': '签到成功', 'code': 1, 'info': {'data': {'action_name': 'beginWork', 'modifying': 'true', 'salaryInfoId': '', 'session_token': 'token_key_1619157009355', 'backUrl': '', 'workAssignmentId': '9000'}, 'time': datetime.datetime(2021, 4, 23, 13, 50, 9, 542232)}}
print(u.attendance(2))
# {'message': '签退成功', 'code': 1, 'info': {'data': {'action_name': 'endWork', 'modifying': 'true', 'salaryInfoId': '', 'session_token': 'token_key_1619157045726', 'backUrl': '', 'workAssignmentId': '9000'}, 'time': datetime.datetime(2021, 4, 23, 13, 50, 45, 960229)}}


print(u.attendance(1, 9001))
# {'message': '签到成功', 'code': 1, 'info': {'data': {'action_name': 'beginWork', 'modifying': 'true', 'salaryInfoId': '', 'session_token': 'token_key_1619157089460', 'backUrl': '', 'workAssignmentId': '9001'}, 'time': datetime.datetime(2021, 4, 23, 13, 51, 29, 618113)}}
print(u.attendance(2, 9001))
# {'message': '签退成功', 'code': 1, 'info': {'data': {'action_name': 'endWork', 'modifying': 'true', 'salaryInfoId': '', 'session_token': 'token_key_1619157094360', 'backUrl': '', 'workAssignmentId': '9001'}, 'time': datetime.datetime(2021, 4, 23, 13, 51, 34, 492294)}}

细心的读者可能已经发现了,上面的代码中,构建账号后并没有调用signin(login_url: str)方法,而是直接调用两个功能方法。
这是因为DgutXgxt类中使用python的装饰函数实现AOP面向切面编程,在调用get_workAssignment()attendance(flag: int, workAssignmentId: int = None)方法时会自动判断是否已登录、未登录则会先调用signin(login_url: str)方法进行登录认证。

2.4. 实现每日疫情打卡

from dgut_requests import DgutIllness

u = dgutIllness("201841312111", "123456")
print(u.report())
# {'message': '您今天已打卡成功!已连续打卡688天!'}

与前面一样,先构造一个DgutIllness对象,然后调用report()方法即可完成打卡。该功能仅用于爬虫学习及防止忘记打卡,疫情期间切勿轻视打卡。

因为有些字段(如最后一次核酸时间)学校系统会过几天清空一次,所以会出现打卡成功之后这些字段是空的情况。为了解决这个情况,我们可以自定义字段

from dgut_requests import DgutIllness

data = {
    "latest_acid_test": "2022-05-26"
}

u = DgutIllness("201841312111", "123456")
print(u.report(custom_data=data))
# {'message': '您今天已打卡成功!已连续打卡688天!'}

2.5. 获取个人成绩

from dgut_requests import DgutJwxt

u = DgutJwxt("201841312111", "123456")

# 获取本学期的原始成绩
# 现在是2022年5月,即2021-2022学年第二学期
for item in u.get_score():
    print(*item)
"""
[0710010]形势与政策 2.0 12 公共课/必修课 初修 考试 初修取得 xx
[4100088]毕业实习 2.0 0 实习 初修 初修取得 xx
...
"""


# 获取2020-2021学年第一学期的原始成绩
# xn=2020代表2020-2021学年
# xq=1代表第一学期,xq=2代表第二学期
for item in u.get_score(xn=2020, xq=1):
    print(*item)
"""
[0710007]形势与政策5 0.0 公共课/必修课 初修 考试 初修取得 xx
[1310006]体育5 0.5 公共课/必修课 初修 考试 初修取得 xx
[0410084]PHP程序设计 3.0 专业课/任选课 初修 考查 初修取得 xx
...
"""


# 获取2020-2021学年第一学期的有效成绩
for item in u.get_score(score_type=2, xn=2020, xq=1):
    print(*item)
"""
[0710007]形势与政策5 0.0 公共课/必修课 初修 考试 xx 0.0 xx xx
[1310006]体育5 0.5 公共课/必修课 初修 考试 xx 0.5 xx xx
[0410084]PHP程序设计 3.0 专业课/任选课 初修 考查 xx 3.0 xx xx
...
"""

    

# 获取2019-2020学年的原始成绩
# time_range=2代表按学年查询(1:入学以来 | 2:按学年 | 3:按学期)
# xn=2019代表2019-2020学年
for item in u.get_score(time_range=2, xn=2019):
    print(*item)
"""
[14510300]计算机组成原理 4.0 专业基础课/必修课 初修 考试 初修取得 xx
[14540011]JAVA语言程序设计 3.0 专业基础课/必修课 初修 考试 初修取得 xx
[048774]离散数学 4.0 专业基础课/必修课 初修 考试 初修取得 xx
[0710002]马克思主义基本原理 3.0 公共课/必修课 初修 考试 初修取得 xx
[0710005]形势与政策3 0.0 公共课/必修课 初修 考试 初修取得 xx
[1010005]应用英语A 2.0 公共课/必修课 初修 考试 初修取得 xx
[1310004]体育3 0.5 公共课/必修课 初修 考查 初修取得 xx
[0410032]概率论与数理统计 3.5 专业基础课/必修课 初修 考试 初修取得 xx
[379925]算法与数据结构 5.0 专业基础课/必修课 初修 考试 初修取得 xx
...
"""
    
    

# 获取入学以来的有效成绩
for item in u.get_score(time_range=1, score_type=2):
    print(*item)
"""
[19510070]中国近现代史纲要 2.0 公共课/必修课 初修 考试 xx 2.0 xx xx
[0710003]形势与政策1 0.0 公共课/必修课 初修 考试 xx 0.0 xx xx
[1010001]大学英语1 3.0 公共课/必修课 初修 考试 xx 3.0 xx xx
[1010003]英语口语1 1.0 公共课/必修课 初修 考试 xx 1.0 xx xx
[0810001]管理学概论 2.0 公共课/必修课 初修 考查 xx 2.0 xx xx
[1310001]大学生心理健康教育 1.0 公共课/必修课 初修 考查 xx 1.0 xx xx
[1310002]体育1 1.0 公共课/必修课 初修 考查 xx 1.0 xx xx
[1710001]军事训练与教育 3.0 军训 初修 xx 3.0 xx xx
[0410029]高等数学C(I) 5.0 专业基础课/必修课 初修 考试 xx 5.0 xx xx
[0410069]网络空间安全专业导论与职业生涯规划 1.0 专业基础课/必修课 初修 考查 xx 1.0 xx xx
[0410073]程序设计基础 5.0 专业基础课/必修课 初修 考试 xx 5.0 xx xx
[0710001]思想道德修养与法律基础 3.0 公共课/必修课 初修 考试 xx 3.0 xx xx
[0710004]形势与政策2 0.0 公共课/必修课 初修 考试 xx 0.0 xx xx
[078645]“思政课”社会实践1 1.0 社会实践 初修 xx 1.0 xx xx
...
[0410084]PHP程序设计 3.0 专业课/任选课 初修 考查 xx 3.0 xx xx
...
[4100088]毕业实习 2.0 0 实习 初修 初修取得 xx
"""
    
    

# get_score(score_type: int = 1, course_type: int = 3, time_range: int = 3, **kwargs)
# score_type: 成绩类型: 1=>原始成绩(默认) | 2=>有效成绩
# course_type: 课程类型: 1=>主修 | 2=>辅修 | 3=>主修和辅修(默认)
# time_range: 时间范围选择: 1=>入学以来 | 2=>学年 | 3=>学期(默认)
# **kwargs: 补充参数,有xn和xq两个参数,都是int类型
    # xn: 学年: [1970, 9999]
    # xq: 学期: 1 | 2

首先创建一个DgutJwxt对象,然后调用get_score方法返回一个成绩结果的生成器,该生成器的每个元素对应着一条记录。
有兴趣的朋友可以研究一下openpyxl库,制作一个生成学年综测excel表格的脚本。

3. 高级用法

3.1. session会话管理

dgutUser类及其子类(dgutXgxtdgutIllness)的构造函数中定义了一个requests.Session()对象session进行会话管理,它会自动保存cookies信息,并且每次发起http请求都会使用该对象的headers和cookies属性作为http包的头部。因此,我们使用其来发起请求,可以更加轻松方便。

from dgut_requests import DgutUser


u = DgutUser("201841312111", "123456")
u.signin("https://cas.dgut.edu.cn/home/Oauth/getToken/appid/xgxtt.html")

# 使用session进行会话管理
print("对象u的cookies有:")
for key, value in u.session.cookies.items():
    print(f"{key}={value}")
"""
PHPSESSID=f6e8e2ue5qqn8qu7kjq1abmhf1
last_oauth_appid=xgxtt
JSESSIONID=1811A6B611868FA3529A18214A263E1B.sms9011
"""


# 我们再登录一下教务服务平台试试,并查看登录之后cookies有什么变化
print("对象u当前的cookies有:")
u.session.get("https://cas.dgut.edu.cn/home/Oauth/getToken/appid/jwyd.html")
for key, value in u.session.cookies.items():
    print(f"{key}={value}")
"""
PHPSESSID=f6e8e2ue5qqn8qu7kjq1abmhf1
last_oauth_appid=jwyd
JSESSIONID=6FBE069884CCAFB6D26E69DC237686E7
JSESSIONID=1811A6B611868FA3529A18214A263E1B.sms9011
"""

3.2. 使用cookies进行管理

当账号很多并且操作很多的时候,如果每一次都需要进行登录,那么效率就会很低。此外,如果短时间内需要重复登录的话,可能需要重复输入账号密码。
有一个比较好的方案就是可以使用cookies。cookies一般会有一定时长的有效期,在有效期内我们可以把账号密码置空,直接将cookies信息添加到空账号中。这样,当我们模拟访问网页的时候也可以畅行无阻。

from dgut_requests import *
import re
import os
import json
import requests

path = 'cookies.json'

def cookies_is_expires(path: str, url: str, pattern: re.compile):
    '''
    判断cookies是否过期(过期或不存在该文件都视为过期)
    '''
    if not os.path.exists(path):
        return True
    with open(path, 'r') as f:
        cookie = json.loads(f.read())
    headers = {
        "cookie": ";".join([f"{k}={v}" for k,v in cookie.items()])
    }
    response = requests.get(url, headers=headers)
    if pattern.search(response.text, re.S):
        return True
    return False


if not os.path.exists(path) or cookies_is_expires(path, "http://stu.dgut.edu.cn/homepage.jsp", re.compile(r'token = ".*?"')):
    u = DgutXgxt("201841312111", "123456")
    print(u.get_workAssignment())
    cookie = requests.utils.dict_from_cookiejar(u.session.cookies)
    print(cookie)
    with open(path, 'w') as f:
        f.write(json.dumps(cookie))
else:
    print("has cookies")
    with open(path, 'r') as f:
        cookies = json.loads(f.read())
    u = DgutXgxt('', '')
    requests.utils.add_dict_to_cookiejar(u.session.cookies, cookies)
    print(u.get_workAssignment())
    print(u.session.cookies)

第一次输出结果

[('9000', 'xxx学生助理')]
{'JSESSIONID': '88847A3F393EB86DEC60ECDE60007533.sms9011', 'PHPSESSID': '8qahsd5f8gocuqdpddkq5vghg0', 'last_oauth_appid': 'xgxtt'}

第二次输出结果

has cookies
[('9000', 'xxx学生助理')]
<RequestsCookieJar[<Cookie JSESSIONID=88847A3F392EB86DEC60ECDE66007533.sms9011 for />, <Cookie PHPSESSID=8qahsd5f8gocuqdpddkq5vghg0 for />, <Cookie last_oauth_appid=xgxtt for />]>

3.3. 基于DgutUser开发新系统,基于现有系统开发新功能

开发者可基于DgutUser及其子类进行新功能的开发,继承DugtUser及其子类,然后用装饰函数DgutUser.decorator_signin装饰新功能函数。这样,开发者只需要关心功能的实现而不需要关心登录功能。

4. 说明文档

4.1. class AuthError

认证错误类,在认证失败时会抛出该异常。

from dgut_requests import DgutUser, AuthError

try:
    u = DgutUser('123', '456')
    u.signin("https://cas.dgut.edu.cn/home/Oauth/getToken/appid/xgxtt.html")
except AuthError as e:
    print(e)

4.2. class DgutUser

属性/方法 说明
username: str DGUT中央认证账号
__password: str DGUT中央认证密码
timeout: int 请求超时时间
is_authenticated: bool 是否已认证
session: requests.sessions.Session 会话
__init__(self, username: str, password: str, timeout: int=30) -> None 构造函数
username: DGUT中央认证账号
password: DGUT中央认证密码
timeout: 请求超时时间
signin(self, login_url: str) -> requests.Response 登录函数,返回结果响应
login_url: 登录url,以http://或https://开头
decorator_signin(url: str)(func: function) -> function 登录装饰器

4.3. class DgutXgxt

继承自[4.2](#42-class DgutUser)

属性/方法 说明
xgxt_login: str & 学工系统登录: str 学工系统的登录url
get_workAssignment(self) -> list 获取考勤职位信息,返回一个列表
attendance(self, flag: int, workAssignmentId: int=None) -> dict 考勤函数,返回一个字典结果
flag: 1表示签到,2表示签退
workAssignmentId: 考勤职位ID,缺省或None时自动调用get_workAssignment(self)获取第一个职位信息作为参数,若没有任何职位信息则抛出ValueError异常

4.4. class DgutIllness

继承自[4.2](#42-class DgutUser)

属性/方法 说明
illness_login: str & 疫情防控系统登录: str 疫情防控系统的登录url
report(self, custom_data: dict=None, priority: bool=False) -> dict 进行疫情防控每日打卡,返回字典结果
custom_data: 用户自定义数据,用于自定义字段(如最后一次核酸时间latest_acid_test)
priority: 用户自定义数据是否具有更高优先级。为True时用户自定义数据将覆盖云端数据,为False时相反。默认为False

其他方法同4.2.2

4.5. class DgutJwxt

继承自[4.2](#42-class DgutUser)

属性/方法 说明
jwxt_login: str & 教务系统登录: str 教务系统的登录url
get_score(self, score_type: int = 1, course_type: int = 3, time_range: int = 3, **kwargs) -> Generator 查询成绩,返回一个生成器对象
score_type: 成绩类型: 1=>原始成绩(默认) / 2=>有效成绩
course_type: 课程类型: 1=>主修 / 2=>辅修 / 3=>主修和辅修(默认)
time_range: 时间范围选择: 1=>入学以来 / 2=>学年 / 3=>学期(默认)
**kwargs: 补充参数,有xn和xq两个参数,都是int类型。xn: 学年,取值[1970, 9999]; xq: 学期,取值1或2

5. 目前已有的项目示例

6. 更新日志

v2.0.2 - 2022-9-17

  • 修正了DgutIllness.report中用户自定义数据添加的问题,现在只需要以{ key: value }方式传入即可

v2.0.1 - 2022-5-28

  • 修改依赖库lxml和requests的版本限制

v2.0.0 - 2022-5-27

  • 启用x.x.x的版本号,第一个数表示主版本号,往往意味着接口可能会有不兼容的修改。第二个数表示次版本号,一般是一些改变内部实现的更新、优化等。最后一个数则是对内容进行小幅度的修改或对bug的修复
  • 对README文档进行更新,将多余的废话去除,只留下核心文档
  • 对模块文件的结构进行修改,将其中类的名字也进行了修改,更加符合Python的命名规范(虽然是主版本更新,但诸多原因下还是对接口进行了兼容)

v1.2 - 2022-1-4

  • dgutIllnessreport方法有点小问题,在提交数据前不小心删除了最后一次核酸检测时间选项,本次是修复该小bug

v1.1 - 2021-12-22

  • dgutIllnessreport方法代码微修改

v1.0 - 2021-12-21

  • 疫情防控打卡系统更新。注意,因接口改变,dgutIllnessreport方法有所变化,不再接收任何参数
  • 随着版本更新,将版本号正式定为1.0,此后每次小的迭代增加0.1,大的更新向上取整

v0.1.5 - 2021-12-18

  • 疫情防控打卡时会检测是否已打卡,已打卡则返回打卡成功的信息{'code': 400, 'message': message, 'info': []},减少无故提交次数,避免被检测

v0.1.4 - 2021-9-5

  • 将返回成绩结果改为生成器
  • 修正了部分注释
  • 稍微修改了一下代码中不够简洁的部分

v0.1.3 - 2021-7-1

修复了显示{'code': 400, 'message': '选定的 核酸检测结果 是无效的', 'info': []}的问题

v0.1.2 - 2021-5-19

修改了上次更新时代码错误产生的无法正常打卡的问题,并更新了README.md。

v0.1.1 - 2021-5-16

主要解决了调用dgutIllness中report()方法出现提交异常的情况。此外,对READEME.md文档也进行了部分更新。

v0.1.0 - 2021-4-24

重大更新,重构代码。具体请查看5. 相比0.0.x版本的改动

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dgut_requests-2.0.2.tar.gz (22.8 kB view hashes)

Uploaded Source

Built Distribution

dgut_requests-2.0.2-py3-none-any.whl (15.4 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page