目录

         ​​前言​

​0. 依赖及代码头:​

​1. 登录​

​1) 验证码​

​2)登录请求构造​

​2. 跳转到成绩界面获取成绩​

​3. 输出成绩​

​4.整体代码​


前言

以下的所有代码都基于python3

最近学习网络爬虫...加上我们学校的教务系统经常因为各种奇怪的原因没有办法读取界面啥啥啥的....所以这里准备写一个脚本挂到服务器上,之后增加到我的博客界面....方便所有人查询自己的成绩以及课表,课表的话爬取比较简单,因为跳转次数较少,方式也大同小异,所以这里只给出爬取成绩的具体思路

0. 依赖及代码头:

  1. Beautiful库,解析网页
  2. PrettyTable库,格式化输出
  3. PIL,抓取验证码并显示
  4. urllib库,http请求库

以下是代码头,这里我们需要构造一个opener来模拟在线连续访问

import re
import urllib.request
import urllib.parse
import http.cookiejar
import bs4
import getpass
import pickle
import os
import platform
import subprocess
from bs4 import BeautifulSoup
from prettytable import PrettyTable
from PIL import Image
from PIL import ImageEnhance
import pytesseract

#准备Cookie和opener,因为cookie存于opener中,所以以下所有网页操作全部要基于同一个opener
cookie = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie))

1. 登录

python爬虫之 正方教务管理系统查询成绩_验证码

首先我们看到登录界面,需要提供三个内容{用户名,密码,验证码},用户名密码都是我们已知的,那么我们需要先解决第一个问题,就是这个验证码

1) 验证码

我们打开F12开发者工具,打开network,然后点击验证码刷新出一张新的验证码,然后就可以看到验证码的来源地址是​​http://zfxk.zjtcm.net/CheckCode.aspx​

python爬虫之 正方教务管理系统查询成绩_html_02

2)登录请求构造

我们先登录一次,然后同样的在network中找到我们的请求界面中找到我们登录时提交的表单

python爬虫之 正方教务管理系统查询成绩_验证码_03

这里需要四个内容,{__VIEWSTATE,txtUserName,TextBox2,txtSecretCode},当然这里的RadioButtonList1也是需要填写的,这里显示不出来是因为这是中文表单,内容是“学生”,也就是我们登录时需要勾选的登录方式

__VIEWSTATE是为了防止攻击增加的内容,这个东西我们可以在登录前的界面中找到

python爬虫之 正方教务管理系统查询成绩_.net_04

txtUserName就是用户名,TextBox2中的就是密码,txtSecretCode中就是验证码

那么现在我们已经找齐了登录所需要的所有内容,现在只需要一个个组合成请求即可

用户名密码我们当然是需要输入的

username=input("输入用户名: ")
password=input("输入密码 ")

验证码直接去验证码链接中保存下图片,然后手动输入,当然也可以增加验证码识别功能....正方的验证码都是比较简单的基础验证码...稍微处理一下就可以达到较高的识别率,当然这里就不作赘述

res = opener.open('http://zfxk.zjtcm.net/checkcode.aspx').read()
with open(r'D:\code.jpg','wb') as file:
file.write(res)
im = Image.open(r'D:\code.jpg')
im.show()
vcode = input('请输入验证码:')
im.close()

以及获取__VIEWSTATE

response = urllib.request.urlopen('http://zfxk.zjtcm.net/')
html = response.read().decode('gb2312')
viewstate = re.search('<input type="hidden" name="__VIEWSTATE" value="(.+?)"',html)

在获取所有内容后,我们组成登录所需要的请求头

params = {
'__VIEWSTATE':viewstate.group(1)
'txtUserName' : username,
'Textbox1' : '',
'Textbox2': password,
'txtSecretCode':vcode
'RadioButtonList1':'学生',
'Button1' : '',
'lbLanguage':'',
'hidPdrs':'',
'hidsc':'',
}

接下来我们使用这个表单请求登录

data = urllib.parse.urlencode(params).encode('gb2312')
response = opener.open(loginurl,data)

就可以完成登录了

以下是完整的登录代码

username=input("输入用户名: ")
password=input("输入密码 ")

while True:
params = {
'txtUserName' : username,
'Textbox1' : '',
'Textbox2': password,
'RadioButtonList1':'学生',
'Button1' : '',
'lbLanguage':'',
'hidPdrs':'',
'hidsc':'',
}
#获取验证码
res = opener.open('http://zfxk.zjtcm.net/checkcode.aspx').read()
with open(r'D:\code.jpg','wb') as file:
file.write(res)
im = Image.open(r'D:\code.jpg')
im.show()
vcode = input('请输入验证码:')
im.close()
params['txtSecretCode'] = vcode
#获取ViewState
response = urllib.request.urlopen('http://zfxk.zjtcm.net/')
html = response.read().decode('gb2312')
viewstate = re.search('<input type="hidden" name="__VIEWSTATE" value="(.+?)"',html)
params['__VIEWSTATE'] = viewstate.group(1)

#尝试登陆
loginurl = 'http://zfxk.zjtcm.net/default2.aspx'
data = urllib.parse.urlencode(params).encode('gb2312')
response = opener.open(loginurl,data)
if response.geturl() != 'http://zfxk.zjtcm.net/default2.aspx':
#获取学生姓名,之后需要使用
catch='<span id="xhxm">(.*?)</span>'
tmpname=re.search(catch,response.read().decode('gb2312'))
name=tmpname.group(1)
name=name[:-2]
break

print(name)

2. 跳转到成绩界面获取成绩

同样的在network上找到查询成绩的直接地址,可以看到是

​http://zfxk.zjtcm.net/xscj_gc.aspx?xh=(.?)&xm=(.?)&gnmkdm=N121605​

xh后面是自己的学号,即登陆的usernamexm则是登录后抓取的姓名在网页编码gb2312下的转义

当然这里有一点特殊的地方,如果我们直接在地址中输入我们构造出的查询界面的链接,会出现302跳转,在python下可能会报错

当然这也是一定程度上的网站防护措施

那么解决这一问题也很简单,我们可以猜想为什么我们正常点击可以跳转到页面,而直接输入链接却不行,最简单的猜想就是,在传递的表单中我们需要给出我们是从哪个界面跳转过来的,只有某些特定界面的跳转才被允许,并且查看一下network中的表头我们可以发现一项名为Referer

python爬虫之 正方教务管理系统查询成绩_.net_05

所以这一项也是非常重要的...需要同时构造在表头中,所以在查询成绩的这一步中,我们不止需要构造请求表单data,还需要构造表头headers

构造表单时,当然我们可以发现还是需要__VIEWSTATE这一项的,这一项当然可能会发生改变,所以我们去登录成功后的界面获取这一项的内容

req = urllib.request.Request(url)
print(url)
req.add_header('Referer','http://zfxk.zjtcm.net/xs_main.aspx?xh='+username )
req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36')
response = opener.open(req)
html = response.read().decode('gb2312')
# print(html)
viewstate = re.search('<input type="hidden" name="__VIEWSTATE" value="(.+?)"',html)

然后按上面的方式构造表头

params = {
'ddlXN':'',
'ddlXQ':'',
'Button2':'在校学习成绩',
}
params['__VIEWSTATE'] = viewstate.group(1)

构造完dataheaders

我们就可以访问查询成绩的界面了

req = urllib.request.Request(url,urllib.parse.urlencode(params).encode('gb2312'))
req.add_header('Referer','http://zfxk.zjtcm.net/default2.aspx')
req.add_header('Origin','http://zfxk.zjtcm.net/')
response = opener.open(req)
soup = BeautifulSoup(response.read().decode('gb2312'),'html.parser')
html = soup.find('table',class_='datelist')

这里的response就是我们需要的成绩页面,然后就是怎么从成绩页面获取我们需要的内容了,这里我们使用BeautifulSoup这个库,直接find所有成绩标签

3. 输出成绩

这里使用prettytable库来格式化输出表单,当然这里也可以保存到excel中或者各种各样个人喜欢的方式.....

print('你的所有成绩如下:')
#指定要输出的列,原网页的表格列下标从0开始
outColumn = [1,2,3,4,6,7,8]
#用于标记是否是遍历第一行
flag = True

for each in html:
columnCounter = 0
column = []
if(type(each) == bs4.element.NavigableString):
pass
else:
#遍历列
for item in each.contents:
if(item != '\n'):
if columnCounter in outColumn:
#要使用str转换,不然陷入copy与deepcopy的无限递归
column.append(str(item.contents[0]).strip())
columnCounter += 1
if flag:
table = PrettyTable(column)
flag = False
else:
table.add_row(column)
print(table)

 

以上就是简单的爬取正方教务系统在校成绩的代码

4.整体代码

import re
import urllib.request
import urllib.parse
import http.cookiejar
import bs4
import getpass
import pickle
import os
import platform
import subprocess
from bs4 import BeautifulSoup
from prettytable import PrettyTable
from PIL import Image
from PIL import ImageEnhance
import pytesseract

#准备Cookie和opener,因为cookie存于opener中,所以以下所有网页操作全部要基于同一个opener
cookie = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie))

username=input("输入用户名: ")
password=input("输入密码 ")

while True:
params = {
'txtUserName' : username,
'Textbox1' : '',
'Textbox2': password,
'RadioButtonList1':'学生',
'Button1' : '',
'lbLanguage':'',
'hidPdrs':'',
'hidsc':'',
}
#获取验证码
res = opener.open('http://zfxk.zjtcm.net/checkcode.aspx').read()
with open(r'D:\code.jpg','wb') as file:
file.write(res)
im = Image.open(r'D:\code.jpg')
im.show()
vcode = input('请输入验证码:')
im.close()
params['txtSecretCode'] = vcode
#获取ViewState
response = urllib.request.urlopen('http://zfxk.zjtcm.net/')
html = response.read().decode('gb2312')
viewstate = re.search('<input type="hidden" name="__VIEWSTATE" value="(.+?)"',html)
params['__VIEWSTATE'] = viewstate.group(1)

#尝试登陆
loginurl = 'http://zfxk.zjtcm.net/default2.aspx'
data = urllib.parse.urlencode(params).encode('gb2312')
response = opener.open(loginurl,data)
if response.geturl() != 'http://zfxk.zjtcm.net/default2.aspx':
#获取学生姓名,之后需要使用
catch='<span id="xhxm">(.*?)</span>'
tmpname=re.search(catch,response.read().decode('gb2312'))
name=tmpname.group(1)
name=name[:-2]
break

print(name)

#构造url
url = ''.join([
'http://zfxk.zjtcm.net/xscj_gc.aspx',
'?xh=',
username,
'&xm=',
urllib.parse.quote(name),
'&gnmkdm=N121605',
])
#构建查询全部成绩表单
params = {
'ddlXN':'',
'ddlXQ':'',
'Button2':'在校学习成绩',
}
#构造Request对象,填入Header,防止302跳转,获取新的View_State
req = urllib.request.Request(url)
print(url)
req.add_header('Referer','http://zfxk.zjtcm.net/xs_main.aspx?xh='+username )
req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36')

response = opener.open(req)
html = response.read().decode('gb2312')
# print(html)
viewstate = re.search('<input type="hidden" name="__VIEWSTATE" value="(.+?)"',html)
params['__VIEWSTATE'] = viewstate.group(1)
#查询所有成绩
req = urllib.request.Request(url,urllib.parse.urlencode(params).encode('gb2312'))
req.add_header('Referer','http://zfxk.zjtcm.net/default2.aspx')
req.add_header('Origin','http://zfxk.zjtcm.net/')
response = opener.open(req)
soup = BeautifulSoup(response.read().decode('gb2312'),'html.parser')
html = soup.find('table',class_='datelist')

print('你的所有成绩如下:')
#指定要输出的列,原网页的表格列下标从0开始
outColumn = [1,2,3,4,6,7,8]
#用于标记是否是遍历第一行
flag = True

for each in html:
columnCounter = 0
column = []
if(type(each) == bs4.element.NavigableString):
pass
else:
#遍历列
for item in each.contents:
if(item != '\n'):
if columnCounter in outColumn:
#要使用str转换,不然陷入copy与deepcopy的无限递归
column.append(str(item.contents[0]).strip())
columnCounter += 1
if flag:
table = PrettyTable(column)
flag = False
else:
table.add_row(column)
print(table)

 

当然这份代码没有增加验证码识别以及美化成绩界面,之后可能会为了方便考虑将这份代码连接html后写入我的博客中...这样可以方便所有本校同学在各个地方查询成绩以及课表....