本文记录我用Python登录教务系统查询成绩的过程。手动输入验证码,简单获取成绩页面。后续将可能更新自动识别验证码登录查询
前期准备
本爬虫用到了Python的Requests库和BeautifulSoup库
参考文章:从零开始写Python爬虫 --- 爬虫实践:登录正方教务系统
页面分析
登录前
打开chrome的开发者工具,提交一次表单
我们可以分析出请求的Headers,以及Post的表单
请求的Headers整个复制就好,因此我们需要解决的问题就在Post的表单这里。
Post表单
可以看到上图中,需要注意的项有:__VIEWSTATE、txtUserName、TextBox2、txtSecretCode、RadioButtonList1
其中
- txtUserName:用户名
- TextBox2:密码
- txtSecretCode:验证码
- RadioButtonList1:三个单选项,我们选学生
- __VIEWSTATE
关于RadioButtonList1,我们只要点view source
就可以看到
__VIEWSTATE的解决
而__VIEWSTATE通过页面分析,我们知道,其实就是图中input元素的value值
因此我们只需获取到登录页面,通过BeautifulSoup分析出这个值即可
验证码的解决
对于教务的验证码,我们可以用机器识别,也可以手动输入,在这里我们采用手动输入验证码的方式。
在chrome开发者工具中可以看到,CheckCode.aspx就是验证码,它在default2.aspx之后访问,而每次访问CheckCode.aspx,验证码都会改变,也就是说以最后一次访问的为准。因此我们可以先访问default2.aspx,分析页面获取到viewstate,再访问CheckCode.aspx来获取验证码
而验证码的链接就是http://jw.******.com/(m4kwrs3n51rwlebcum0adrq5)/CheckCode.aspx
登录后
再次查看chrome开发者工具,可知登录后跳转到xs_main.aspx?xh=账号
页面
分析页面元素,可以知道成绩查询的链接:
xscjcx.aspx?xh=账号&xm=名字&gnmkdm=N121605
(不同学校的可能不同,需要你自己分析)我们再进入成绩查询的页面(在这里我们要再次记录请求的Headers)
我想通过历年成绩来查询,这样可以查询所有的成绩,也不用选择学年学期
于是再再再度分析页面
可以看出这次是提交表单,于是我们打开chrome开发者工具,然后点击
历年成绩
,看看它提交了什么东西又是__VIEWSTATE,这次学聪明了,一看就知道在页面元素里面,不过一定是在提交前的页面,所以我们要回到成绩查询的页面再分析,用chrome的元素搜索一下viewstate,这一长串的就是我们想要的东西
至此,分析结束,一切就绪
页面分析总结
我们要查询成绩,总共需要以下步骤:
- 先登录:
- 获取登录界面的headers
- 获取__VIEWSTATE
- 获取验证码
- Post 表单登录
- 然后获取查询成绩页面:
- 获取成绩查询Headers
- 获取成绩查询链接
- 获取__VIEWSTATE
- Post表单提交
- 获得成绩页面
代码的实现
import requests
import bs4
from bs4 import BeautifulSoup
#获取请求头部
#可以通过chrome浏览器查看
#此函数用于把复制下来的Headers分成字典
def getHeaders(raw_head):
headers={}
for raw in raw_head.split('\n'):
headerKey,headerValue = raw.split(':',1)
headers[headerKey] = headerValue
return headers
#登录Header
login_head='''Host:xsweb.scuteo.com
Connection:keep-alive
Content-Length:178
Cache-Control:max-age=0
Origin:http://xsweb.scuteo.com
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
Content-Type:application/x-www-form-urlencoded
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer:http://xsweb.scuteo.com/(5mn1wwnfzv1qepq1z5pnpgak)/default2.aspx
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6'''
#成绩查询请求头部
mark_head='''Host:xsweb.scuteo.com
Connection:keep-alive
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer:http://xsweb.scuteo.com/(5mn1wwnfzv1qepq1z5pnpgak)/xs_main.aspx?xh=201630021407
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6'''
#构造session
s=requests.session()
#请求url
url='http://xsweb.scuteo.com/(5mn1wwnfzv1qepq1z5pnpgak)/'
#登录页面
#获取登录headers
headers=getHeaders(login_head)
#构造登录Post表单
account='账号'
password='密码'
formtable={
'txtUserName':'',
'TextBox2':'',
'txtSecretCode':'',
'__VIEWSTATE':'',
'RadioButtonList1':'%D1%A7%C9%FA',
'Button1':'',
'lbLanguage':'',
'hidPdrs':'',
'hidsc':''
}
formtable['txtUserName']=account
formtable['TextBox2']=password
#获取viewstate
#获取登录界面
login_page=s.get(url+'default2.aspx')
soup=BeautifulSoup(login_page.text,'lxml')
__VIEWSTATE = soup.find('input',attrs={'name':'__VIEWSTATE'}).get('value')
#将Viewstate存入登录Post表单
formtable['__VIEWSTATE']=__VIEWSTATE
#获取验证码
pic = requests.get(url+'CheckCode.aspx').content
with open('ver_pic.png','wb') as f:
f.write(pic)
vercode=input('请输入验证码:')
formtable['txtSecretCode']=vercode
#Post表单,登录
res = s.post(url+'/default2.aspx',formtable,headers=headers)
print(res)
#主页面
#获取成绩查询链接
page=s.get(url+'/xs_main.aspx?xh='+account)
soup2=BeautifulSoup(page.text,'lxml')
markurl = soup2.find('a',attrs={'onclick':"GetMc('成绩查询');"}).get('href')
#成绩查询界面
#获取成绩查询Headers
headers=getHeaders(mark_head)
#Post表单
formtable={
'__EVENTTARGET':'',
'__EVENTARGUMENT':'',
'__VIEWSTATE':'',
'hidLanguage':'',
'ddlXN':'',
'ddlXQ':'',
'ddl_kcxz':'',
'btn_zcj':'%C0%FA%C4%EA%B3%C9%BC%A8'
}
#获取__VIEWSTATE
markpage = s.get(url+markurl,headers=headers)
soup=BeautifulSoup(markpage.text,'lxml')
__VIEWSTATE = soup.find('input',attrs={'name':'__VIEWSTATE'}).get('value')
formtable['__VIEWSTATE']=__VIEWSTATE
#提交表单,获取成绩查询界面
markpage=s.post(url+markurl,formtable,headers=headers)
print(markpage.text) #该页面包含了成绩
实际效果
在桌面运行
提示输入验证码,验证码会自动保存到桌面
输入后若顺利就能够看到成绩的页面了
我们还可以进一步通过BeautifulSoup筛选出成绩,美化我们的查询结果。
美化结果显示
这里我用BeautifulSoup进行节点的筛选,将成绩表格数据存入一个二维数组,再输出。
我单独写了一个table2List.py
文件,和源文件同一个目录
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 文件名:table2List.py
'<table>转换成二维数组'
import bs4
from bs4 import BeautifulSoup
def tableToList(document):
document=str(document)
soup = BeautifulSoup(document,'lxml')
#得到table的每行每项,存到items中
lines=soup.table.find_all('tr')
items=[]
for line in lines:
lineList=[]
for item in line.find_all('td'):
lineList.append(item.string)
items.append(lineList)
return items
最后只需要在原来的文件引入这段代码
from table2List import tableToList
然后在源代码加上这段代码
soup3=BeautifulSoup(markpage.text,'lxml')
marktable = str(soup3.find_all(id="Datagrid1")[0])
marklist = tableToList(marktable)
for line in marklist:
for column in [3,7,8]: #这里的数字表示你想显示的列,不同教务不同
print("%-20s" % line[column] ,end='')
print("")
这样能得到相对整洁的输出,当然我们还可以用prettytable库
实现比较好看的表格输出,为了不暴露成绩,还是只截一半的图好了哈哈