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

  • Post author:
  • Post category:python



目录


前言


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. 登录

首先我们看到登录界面,需要提供三个内容{


用户名



密码



验证码

},

用户名



密码

都是我们已知的,那么我们需要先解决第一个问题,就是这个

验证码

1) 验证码

我们打开

F12开发者工具

,打开

network

,然后点击

验证码

刷新出一张新的验证码,然后就可以看到验证码的来源地址是

http://zfxk.zjtcm.net/CheckCode.aspx

2)登录请求构造

我们先登录一次,然后同样的在

network

中找到我们的请求界面中找到我们登录时提交的表单

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

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

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

后面是自己的学号,即登陆的

username



xm

则是登录后抓取的姓名在网页编码

gb2312

下的转义

当然这里有一点特殊的地方,如果我们直接在地址中输入我们构造出的查询界面的链接,会出现

302

跳转,在

python

下可能会报错

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

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

network

中的表头我们可以发现一项名为

Referer

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

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)

构造完

data



headers

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

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后写入我的博客中…这样可以方便所有本校同学在各个地方查询成绩以及课表….



版权声明:本文为jnxxhzz原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。