正方教务系统爬虫
简介
这是利用python爬虫对正方教务系统成绩进行爬取,将爬取到的成绩放入excel中的程序。
一、设计思路以及工具
正方教务系统主要使用了ASP.NET技术,是一个比较好的爬虫练手程序。
PythonIDE:Anaconda
使用库:requests_html,bs4,requests,xlwt,os
二、实现步骤
1.登陆流程
1.1抓取登陆链接
首先爬虫的登陆不同于常规的网页登陆,需要找到登陆请求的post连接,所以需要使用检查-NetWork抓包,建议开启Preserve log,这样会显示所有登陆时产生的网络交互信息。
首先在登陆界面输入一次错误信息,可以捕获到请求发送的目标地址
可以看到在登陆请求中有四个重要字段包含在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是个隐藏状态的输入框。
通过以下代码可以将它提取出来
# 使用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文件下获取到的
由此我们可以知道需要通过get方法去得到验证码,在观察了请求头后,发现只有cookie比较特殊,我们可以知道,验证码匹配就是用的Cookie中的ASP.NET_SessionId作为依据。
所以我们需要从登陆请求页面获取到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
如果没有出现报错说明你已经成功登陆,在登陆后我们需要找到历年成绩的入口
访问历年成绩的页面的地址是这个
> 'http://jwxt.yxnu.edu.cn/xscjcx.aspx?xh={}&xm={}&gnmkdm=N121605'
其中的xh对应学号,xm对应姓名(需要从页面提取,或者从用户输入)
但是如果直接访问是会出现以下界面的
这说明我们无法直接使用原来的请求头获取页面
在观察这个请求的结构时,发现__VIEWSTATE已经不是原来那个了
于是为了获取这个最新的页面我们需要先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的分析我们可以知道一个<tr>标签下有若干个<td>,由此我们可以使用循环获取。
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文档