用Python查成绩(二) 获取成绩

前言碎语

上一篇 用Python的requests库伪装成浏览器,模拟登录了学校的教务系统。登录进去之后,就可以开始做我们想做的事啦!

这一篇主要写写登录进去之后进入查询成绩页面,以及如何提取成绩信息。

访问查询成绩页面

构造URL

用Chrome F12工具可以看到,当我们进入教务系统,点击成绩查询按钮后,获取的URL是

jwgl

http://jwgl.zsc.edu.cn:90/(bmdsd0ur5p4fbu4512erwdzt)/xscj_gc.aspx?xh=2015XXXXXX9&xm=%D9%XXXXXF3&gnmkdm=N121605

其中2015XXXXXX9是学号,%D9%XXXXXF3是姓名的urlencode编码后的字符串

学号在前面已经输入过了,姓名字符串可以用urllib库

1
2
3
4
5
6
# student变量在上文中已经得到,即学生姓名
# number即上一篇输入过的学号
# student_name是姓名urlencode编码后的字符串
import urllib.parse
student_name = urllib.parse.quote(student.encode("gb2312"))
grade_url = "http://jwgl.zsc.edu.cn:90/(enfj1b45crtyfibn2cj2u045)/xscj_gc.aspx?xh={}&xm={}&gnmkdm=N121605".format(number, student_name)

这样就得到我们需要的URL

访问页面,获取网页源码

访问之前先更新一下cookie,然后get成绩页面获取网页源码

1
2
3
cookies = requests.utils.dict_from_cookiejar(s.cookies)
grade_headers.update(cookies)
response = session.get(grade_url, headers=grade_headers)

构造post所需的数据

可以看到,要查询成绩,需要先选择学年和学期,然后点击下面不同的按钮

jwgl

先随便点一个学年和学期,然后点击按学年查询,用F12工具追踪post数据

jwgl

发现提交的数据包括一个新的__VIEWSTATE值,以及ddlXN、ddlXQ、Button5。不难猜测ddlXN是学年,ddlXQ是学期。至于Button5,其实是下面的“按学年查询”、“按学期查询”这些按钮。

我们刚刚已经用get方法访问了一次成绩查询页面,得到了其源码,因此在网页源码中可以直接找到新的__VIEWSTATE值,用etree和xpath提取出来。

然后构造post数据,再用post方法访问网页。

1
2
3
4
5
6
7
8
9
10
11
selector = etree.HTML(response.content)
__VIEWSTATE = selector.xpath('//*[@id="Form1"]/input/@value')[0]
# post 数据
data = {
"__VIEWSTATE": __VIEWSTATE,
"ddlXN": "2017-2018",
"ddlXQ": 1,
"Button5": u"按学年查询".encode('gb2312', 'replace'),
}

response = s.post(grade_url, headers=grade_headers, data=data)

response是服务器返回给我们的网页,至此我们已经得到一个含有成绩信息的网页了。离成功不远了!


提取成绩

得到一个含有成绩信息的网页之后,分析其源码,看看网页中成绩是怎么显示的

1
2
3
4
5
6
7
8
9
10
11
12
<tr class="datelisthead">
<td>学年</td><td>学期</td><td>课程代码</td><td>课程名称</td><td>课程性质</td><td>课程归属</td><td>学分</td><td>绩点</td><td>平时成绩</td><td>期中成绩</td><td>期末成绩</td><td>实验成绩</td><td>成绩</td><td>辅修标记</td><td>补考成绩</td><td>重修成绩</td><td>学院名称</td><td>备注</td><td>重修标记</td><td>课程英文名称</td>
</tr><tr>
<td>2017-2018</td><td>1</td><td>10327540</td><td>linux 软件开发基础</td><td>必修课</td><td>&nbsp;</td><td>4.0</td><td> 3.91</td><td>90</td><td>&nbsp;</td><td>94</td><td>&nbsp;</td><td>93</td><td>0</td><td>&nbsp;</td><td>&nbsp;</td><td>计算机学院</td><td>&nbsp;</td><td>0</td><td></td>
</tr><tr class="alt">
<td>2017-2018</td><td>1</td><td>10302530</td><td>多媒体技术基础</td><td>限选课</td><td>&nbsp;</td><td>3.0</td><td> 3.39</td><td>59</td><td>&nbsp;</td><td>92</td><td>&nbsp;</td><td>82</td><td>0</td><td>&nbsp;</td><td>&nbsp;</td><td>计算机学院</td><td>&nbsp;</td><td>0</td><td></td>
</tr><tr>
<td>2017-2018</td><td>1</td><td>10337040</td><td>嵌入式网络协议及应用开发</td><td>限选课</td><td>&nbsp;</td><td>4.0</td><td> 3.88</td><td>93</td><td>&nbsp;</td><td>91</td><td>&nbsp;</td><td>92</td><td>0</td><td>&nbsp;</td><td>&nbsp;</td><td>计算机学院</td><td>&nbsp;</td><td>0</td><td></td>
</tr><tr class="alt">
<td>2017-2018</td><td>1</td><td>10329020</td><td>嵌入式最小系统设计</td><td>必修课</td><td>&nbsp;</td><td>2.0</td><td> 4.00</td><td>&nbsp;</td><td>&nbsp;</td><td>100</td><td>&nbsp;</td><td>100</td><td>0</td><td>&nbsp;</td><td>&nbsp;</td><td>计算机学院</td><td>&nbsp;</td><td>0</td><td></td>
</tr>
</table>

这种情况,可以先用Beautifulsoup库的find方法,找到所有的tr标签的内容,存到trs变量中。然后在每一行tr中,依次找到课程名字、学分、绩点、平时成绩、期末成绩、总评(每一行的第3个td是课程名字,第6个td是学分,以此类推…)。最后构建一个字典,把数据存进去。

具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def getGrade(response):
html = response.content.decode("gb2312")
soup = BeautifulSoup(html, "html5lib")
trs = soup.find(id="Datagrid1").findAll("tr")[1:]
Grades = []
for tr in trs:
tds = tr.findAll("td")
tds = tds[3:4] + tds[6:9] + tds[10:13:2]
oneGradeKeys = ["课程名字", "学分", "绩点", "平时成绩", "期末成绩", "总评"]
oneGradeValues = []
for td in tds:
s = td.string.replace(" ", "") # 去掉空格
s = "".join(s.split()) # 去掉 \xa0 ,\xa0 是不间断空白符 &nbsp;
oneGradeValues.append(s)
oneGrade = dict((key, value) for key, value in zip(oneGradeKeys, oneGradeValues))
Grades.append(oneGrade)
return Grades


result = getGrade(response)

for go in result:
print(go)

至此,成绩已经提取出来了。看看输出结果:

jwgl


总结

这是我第一次做了一个像样的实用Python爬虫小项目,部分代码和成绩提取的内容参考了网上的代码。写得也不怎么样,不过最终还是实现了一开始的构想,已经很满足了。

参考链接:link

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# -*-coding:utf-8-*-
import os
import requests
from lxml import etree
from PIL import Image
from bs4 import BeautifulSoup
import urllib.parse


# 浏览器头
headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.8",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Host": "jwgl.zsc.edu.cn:90",
"Referer": "http://jwgl.zsc.edu.cn:90/(enfj1b45crtyfibn2cj2u045)/default2.aspx",
"Upgrade-Insecure-Requests": "1"
}

# 验证码页面浏览器头
headers_code = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Referer": "http://jwgl.zsc.edu.cn:90/(enfj1b45crtyfibn2cj2u045)/default2.aspx",
"Host": "jwgl.zsc.edu.cn:90",
"Cache-Control": "max-age=0"
}

# 成绩页面浏览器头
grade_headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7",
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
"Host": "jwgl.zsc.edu.cn:90",
"Referer": "http://jwgl.zsc.edu.cn:90/(enfj1b45crtyfibn2cj2u045)/default2.aspx",
"Upgrade-Insecure-Requests": "1"
}


def login_jcgl(session):
global number
number = input('请输入学号:')
password = input("请输入密码:")
url = "http://jwgl.zsc.edu.cn:90/(enfj1b45crtyfibn2cj2u045)/default2.aspx"
response = session.get(url)

# 把cookie转变成字典类型,并加入到header中
cookies = requests.utils.dict_from_cookiejar(s.cookies)
headers.update(cookies)
headers_code.update(cookies)

# Download checkcode
check_code_url = "http://jwgl.zsc.edu.cn:90/(enfj1b45crtyfibn2cj2u045)/CheckCode.aspx"
pic_response = session.get(check_code_url, headers=headers_code).content

with open('ver_pic.jpg', 'wb') as f:
f.write(pic_response)

# open checkcode
image = Image.open('{}\\ver_pic.jpg'.format(os.getcwd()))
image.show()


# VIEWSTATE 教务系统 post 需要用到的一个随机值
selector = etree.HTML(response.content)
__VIEWSTATE = selector.xpath('//*[@id="form1"]/input/@value')[0]

# post 数据
data = {
'txtSecretCode': str(input('请输入图片中的验证码:')),
'txtUserName': str(number),
'Textbox1': '',
'TextBox2': str(password),
'__VIEWSTATE': __VIEWSTATE,
'RadioButtonList1': u"学生".encode('gb2312', 'replace'),
'Button1': '',
'lbLanguage': '',
'hidPdrs': '',
'hidsc': ''
}


# 登录教务系统
response = s.post(url, data=data, headers=headers)
print("正在登录...")
print(response.status_code)

if "验证码不正确" in response.text:
print("验证码不正确")
return login_jcgl(s)
if ("密码错误" or "密码不能为空") in response.text:
print("密码错误")
return login_jcgl(s)
if ("用户名不能为空" or "用户名不存在或未按照要求参加教学活动") in response.text:
print("学号错误")
return login_jcgl(s)
else:
print("登录成功!")
global student
student = getInfor(response, '//*[@id="xhxm"]/text()').replace("同学", "")
# student = student.replace("同学","")
print("你好," + student + " " + number)
return response


# 获取基本信息(用于验证是否登录成功)
def getInfor(response, xpath):
content = response.content.decode('gb2312') # 网页源码是gb2312要先解码
selector = etree.HTML(content)
infor = selector.xpath(xpath)[0]
return infor


def login_grade(session):
student_name = urllib.parse.quote(student.encode("gb2312"))
grade_url = "http://jwgl.zsc.edu.cn:90/(enfj1b45crtyfibn2cj2u045)/xscj_gc.aspx?xh={}&xm={}&gnmkdm=N121605".format(number, student_name)
cookies = requests.utils.dict_from_cookiejar(s.cookies)
grade_headers.update(cookies)
response = session.get(grade_url, headers=grade_headers)
selector = etree.HTML(response.content)
__VIEWSTATE = selector.xpath('//*[@id="Form1"]/input/@value')[0]

# post 数据
data = {
"__VIEWSTATE": __VIEWSTATE,
"ddlXN": "2017-2018",
"ddlXQ": 1,
"Button5": u"按学年查询".encode('gb2312', 'replace'),
}

response = s.post(grade_url, headers=grade_headers, data=data)
return response


def getGrade(response):
html = response.content.decode("gb2312")
soup = BeautifulSoup(html, "html5lib")
trs = soup.find(id="Datagrid1").findAll("tr")[1:]
Grades = []
for tr in trs:
tds = tr.findAll("td")
tds = tds[3:4] + tds[6:9] + tds[10:13:2] # 0 1 3 4 6 7 8 10 12
oneGradeKeys = ["课程名字", "学分", "绩点", "平时成绩", "期末成绩", "总评"]
oneGradeValues = []
for td in tds:
s = td.string.replace(" ", "") # 去掉空格
s = "".join(s.split()) # 去掉 \xa0 ,\xa0 是不间断空白符 &nbsp;
oneGradeValues.append(s)
oneGrade = dict((key, value) for key, value in zip(oneGradeKeys, oneGradeValues))
Grades.append(oneGrade)
return Grades


s = requests.Session()
response = login_jcgl(s)
response = login_grade(s)

result = getGrade(response)

for go in result:
print(go)

os.system("pause")

代码写得比较烂,请多批评指教。