正方教务系统成绩爬虫的实现

正方教务系统爬虫

  • 简介
  • 一、设计思路以及工具
  • 二、实现步骤
    • 1.登陆流程
      • 1.1抓取登陆链接
      • 1.2 验证码获取
      • 1.3 发送登陆请求
    • 2.读入数据
      • 2.1 获取历年成绩对应的__VIEWSTATE
    • 3.数据处理
      • 3.1 存放数据
  • 总结

简介

这是利用python爬虫对正方教务系统成绩进行爬取,将爬取到的成绩放入excel中的程序。

一、设计思路以及工具

正方教务系统主要使用了ASP.NET技术,是一个比较好的爬虫练手程序。

PythonIDE:Anaconda
使用库:requests_html,bs4,requests,xlwt,os

二、实现步骤

1.登陆流程

1.1抓取登陆链接

首先爬虫的登陆不同于常规的网页登陆,需要找到登陆请求的post连接,所以需要使用检查-NetWork抓包,建议开启Preserve log,这样会显示所有登陆时产生的网络交互信息。
正方教务系统成绩爬虫的实现_第1张图片
首先在登陆界面输入一次错误信息,可以捕获到请求发送的目标地址
正方教务系统成绩爬虫的实现_第2张图片
可以看到在登陆请求中有四个重要字段包含在data中
txtUserName:登陆用户名
TextBox2:密码
txtSecretCode:验证码
__VIEWSTATE:

ViewState是
http://ASP.NET中用来保存WEB控件回传时状态值一种机制。在WEB窗体(FORM)的设置为runat=”server”,这个窗体(FORM)会被附加一个隐藏的属性_VIEWSTATE。_VIEWSTATE中存放了所有控件在ViewState中的状态值。

ViewState是类Control中的一个域,其他所有控件通过继承Control来获得了ViewState功能。它的类型是system.Web.UI.StateBag,一个名称/值的对象集合。

当请求某个页面时, http://ASP.NET把所有控件的状态序列化成一个字符串,然后做为窗体的隐藏属性送到客户端。当客户端把页面回传时,
http://ASP.NET分析回传的窗体属性,并赋给控件对应的值。

这个东西是不可或缺的,而我们又怎么找到它呢?
实际上,在页面的结构中我们就能找到它,它存在与input的value中,这个input是个隐藏状态的输入框。
正方教务系统成绩爬虫的实现_第3张图片
通过以下代码可以将它提取出来

    #   使用BeautifulSoup进行页面处理,提取出__VIEWSTATE
    soup = BeautifulSoup(res.text,'lxml')     
    viewState = soup.find('input', attrs={'name': '__VIEWSTATE'})['value'] 

由此我们可知我们将要将用户名,密码,验证码,VIEWSTATE以数据的形式发送到"http://XXXXXXX/default2.aspx"

1.2 验证码获取

通过观察页面的Element,可知验证码图片是从CheckCode.aspx文件下获取到的
正方教务系统成绩爬虫的实现_第4张图片
由此我们可以知道需要通过get方法去得到验证码,在观察了请求头后,发现只有cookie比较特殊,我们可以知道,验证码匹配就是用的Cookie中的ASP.NET_SessionId作为依据。
正方教务系统成绩爬虫的实现_第5张图片
所以我们需要从登陆请求页面获取到Cookie

    #   cookie进行处理
    Cookie=str(res.cookies)[27:69]

并且将它放到请求头里

    headeri = {
    "Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Cookie": Cookie,
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
    "Referer": "http://替换成你的jwxt网址/default2.aspx",
    "Host": "替换成你的jwxt网址",
    "Cache-Control": "max-age=0"
    }

发送请求保存图片,并要求用户进行验证(此处可以尝试用图像分析解决)

    #发送请求
    resi = session.get("http://XXXXX/CheckCode.aspx",headers=headeri,stream=True)

    #如果验证码文件已经存在则删除它,如果不存在就直接创建一个将验证码放入其中
    if os.path.exists(r'F://FzscoreGet//yanzheng.jpg'):
        os.remove(r'F://FzscoreGet//yanzheng.jpg')
    with open(r'F://FzscoreGet//yanzheng.jpg','wb')as f:
        f.write(resi.content)
        
    #打开验证码文件要求用户进行验证(待完善,目标自动识别)
    os.startfile(r'F://FzscoreGet//yanzheng.jpg')
    checkCode = input("请输入弹出的验证码:")

1.3 发送登陆请求

要求用户输入账号密码
初始化登陆数据

    login_info = {
            "__VIEWSTATE": viewState,
            "txtUserName": user, 
            "TextBox2": pwd,
            "txtSecretCode": checkCode,
            "RadioButtonList1": "%D1%A7%C9%FA",#学生选项
            "Button1": "",
            "lbLanguage": ""
        }

初始化请求头,此处cookie一定要和获取验证码的相同

    #处理登陆请求的请求头
    header = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate",
    "Cookie": Cookie,
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
    "Referer": "http://替换成你的jwxt网址/default2.aspx",
    "Host": "替换成你的jwxt网址",
    "Origin":"替换成你的jwxt网址",
    "Cache-Control": "max-age=0"
}

发送登陆请求

requests.session().post(url='http://xxxx/default2.aspx', data=login_info, headers=header)

2.读入数据

2.1 获取历年成绩对应的__VIEWSTATE

如果没有出现报错说明你已经成功登陆,在登陆后我们需要找到历年成绩的入口

正方教务系统成绩爬虫的实现_第6张图片
访问历年成绩的页面的地址是这个

> 'http://jwxt.yxnu.edu.cn/xscjcx.aspx?xh={}&xm={}&gnmkdm=N121605'

其中的xh对应学号,xm对应姓名(需要从页面提取,或者从用户输入)
但是如果直接访问是会出现以下界面的
正方教务系统成绩爬虫的实现_第7张图片
这说明我们无法直接使用原来的请求头获取页面
在观察这个请求的结构时,发现__VIEWSTATE已经不是原来那个了
正方教务系统成绩爬虫的实现_第8张图片
于是为了获取这个最新的页面我们需要先GET方法获取页面源代码,从中取得__VIEWSTATE的值,然后再次POST过去。

	    #使用get请求先get到__VIEWSTATE
	    rln = session.get('http://xxxxxxx/xscjcx.aspx?xh={}&xm={}&gnmkdm=N121605'.format(xh,name),headers=header)
	    #错误处理,如果报错则说明验证码或账户密码错误,不细化分析错误,学有余力可以自己搞下  
        soup=BeautifulSoup(rln.text,'lxml')
        value3=soup.find('input', attrs={'name': '__VIEWSTATE'})['value']

把这个最新的__VIEWSTATE放入data中

    data={
        'btn_zcj':'%C0%FA%C4%EA%B3%C9%BC%A8',#学年成绩:btn_xn 历年成绩:btn_zcj
        'ddlXN':'',
        'ddlXQ':'',
        '__EVENTVALIDATION': '',
        '__EVENTTARGET':'',   
        '__EVENTARGUMENT' :'',
        '__VIEWSTATE':'',
        'hidLanguage':'',
        'ddl_kcxz':'',
    }

	data['__VIEWSTATE']=value3

并将其放入链接post出去

        #post请求获取到显示界面
        lncj = session.post('http://xxxx/xscjcx.aspx?xh={}&xm={}&gnmkdm=N121605'.format(xh,name),data=data,headers=header)
    
        soup=BeautifulSoup(lncj.text,'lxml')

此时soup中就已经是历年成绩了

3.数据处理

通过对soup的观察我们可以知道成绩存放在form表格中,通过form的分析我们可以知道一个标签下有若干个,由此我们可以使用循环获取。

3.1 存放数据

    #初始化工作环境使用Xlwt
    workbook = xlwt.Workbook(encoding = 'utf-8')
    #表名
    worksheet = workbook.add_sheet('Score')
    #行数,列数初始化
    row = 0
    column = 0
    for tr in soup.find_all('tr'):
        row = row+ 1
        column = 0
        for td in tr.find_all('td'):
             worksheet.write(row,column, label = str(td.get_text()))
             column=column+1
    
     #保存excel    
    workbook.save('Score.xls')

总结

以上,就是一次爬取正方教务系统历年成绩的实现过程,总体来说是不难的,只是在验证码获取方面我卡了很久。爬取过程还可以继续完善,这只是实现的思路而不是完整的程序。实现过程中其实发现有一个地方可以挖坑,就是验证码,验证码功能可以尝试直接识别而不用用户输入。

最后感谢学校和正方教务系统提供的练手机会。

参考文章:http://ddrv.cn/a/288264ython 爬虫案例——正方教务学生成绩获取
参考文档:BeautifulSoup文档

你可能感兴趣的:(python,爬虫,网络)