目录
使用软件以及运行环境
win10系统,Vs Code软件,Python 3.11.0环境下。
对于环境的搭建
知乎有一个帖子叫做:爬虫入门到精通-环境的搭建
这个帖子很详细,可以完整地搭建好python环境
Vs Code中,我下载的扩展如下,大家可以悉数下载:
对于每个扩展有什么用处,大家可以去搜一下。
ctrl+shift+p打开搜索栏,搜索lauch.json来配置python的调试
我的lauch.json内容如下
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: currenfile",
"type": "python",
"request": "launch",
"program":"${file}",
"console":"integratedTerminal"
}
]
}
接下来的配置不多赘述,F5调试,暂停可单步调试。
代码思路
如题所述,首先要实现爬数据,然后将数据整理,最后作处理,并用一些方法供用户使用。
需要导入的包
import os
import bs4
import urllib
import requests
模拟登录
要爬取数据,就得模拟登录,西安理工大学旧教务系统使用的是正方教务系统,模拟登录则首先要看一看西安理工大学旧教务系统的主页
可以看到登录界面就是要输入用户名、密码、验证码以及选择用户类型,这里我们默认是学生类型不需要修改,我们先用谷歌浏览器的f12打开开发者模式,然后尝试一下登录
可以看到network这里出现了一堆东西,我们点开第一个jsp看看
可以看到这里的response headers,将它保存下来,用作登录时所需要的headers,代码中如下
headers = { # 设定登录时的headers
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"Content-Length": "209",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "xfz.xaut.edu.cn",
"Origin": "http://xfz.xaut.edu.cn",
"Referer": "http://xfz.xaut.edu.cn/(ldvbtr55xj1noy45ran0ug55)/default2.aspx",
"Upgrade-Insecure-Requests": "1"
}
点击payload我们可以看到传递过去的数据,点击红色箭头位置,将文字转为解码后的,可以看到信息如下,我们需要的用户名、密码、验证码都有,还有一个隐藏的输入_VIEWSTATE,我们暂且不管他是干什么的,底下的buttonlist是一个固定值,可以验证一下
用户名和密码我们可以自行输入,而验证码需要获取到,这里我们查看一下验证码的url
右键验证码,检查可以发现它的url在这里,我们将它和主页url都保存下来
# 定义登录页面链接和验证码链接
url = 'http://xfz.xaut.edu.cn/(ldvbtr55xj1noy45ran0ug55)/default2.aspx'
imgUrl = 'http://xfz.xaut.edu.cn/(ldvbtr55xj1noy45ran0ug55)/CheckCode.aspx'
验证码的headers和主页的相同,可以共用,同学们可以自行测试,接下来就是爬取验证码
def get_post_data(url, username, password): # 定义获取验证码、并返回登录所需的data的函数
re = s.get(url) # 从登录页面获取response给变量re
# re.text是登录页面的html,以lxml格式用bs4的BeautifulShop方法传递给变量soup
soup = bs4.BeautifulSoup(re.text, 'lxml')
__VIEWSTATE = soup.find('input', attrs={'name': '__VIEWSTATE'})[
'value'] # 获取隐藏input中的值:_VIEWSTATE 用于登录时的data
imgresponse = s.get(imgUrl, stream=True) # 从验证码页面得到response
image = imgresponse.content # 用response.content调用验证码 即验证码页面内容
DstDir = os.getcwd()+"\\" # 保存到当前目录下
print("保存验证码到:"+DstDir+"code.jpg"+"\n")
try: # 尝试是否能读写
with open(DstDir+"code.jpg", "wb") as jpg:
jpg.write(image)
except IOError:
print("IO Error\n")
finally:
jpg.close
ycode = input("请输入验证码:")
data = {
'txtUserName': username,
'TextBox1': '',
'TextBox2': password,
'txtSecretCode': ycode,
'__VIEWSTATE': __VIEWSTATE,
'RadioButtonList1': '%D1%A7%C9%FA',
"Button1": "",
"lbLanguage": "",
"hidPdrs": "",
"hidsc": ""
}
return data
此处的s在全局变量的定义如下
s = requests.session() # 获取一个session给变量s
此处首先获取到登录界面的隐藏_VIEWSTATE的值,然后保存验证码到本地目录下查看即可,data数据是用来登录的,保存并作为返回值
有了headers和data就可以模拟登录了,登录函数如下,接下来后面的函数都是登录函数中包括到的
def login(url, data): # 定义登录函数
global username
r = s.post(url, headers=headers, data=data)
if judge(r.text) == 1:
soup = bs4.BeautifulSoup(r.text, 'lxml')
name_code = soup.find(name='span', attrs={'id': 'xhxm'})
name = urllib.parse.quote_plus( # name为登录成功后的网页源码解析后获得的姓名的gb2312解码
str(name_code.string[11:14]).encode('gb2312'))
b = str(name_code).find('>')
a = str(name_code).find('</span>')
print('❀'+(str(name_code)[b+1:a] + '你好').center(88, '-')+'❀')
kburl = get_headers(username, name) # 获取成绩页面链接
data = get_cj_data(kburl) # 获取访问成绩页面所需data
cjLoading(kburl, headers_code, data) # 获取成绩
else:
print(judge(r.text))
print("请重新登录")
main()
这里的judge函数是用来判断是否登录成功的,具体内容为
def judge(html): # 判断登录的接口
soup_judge = bs4.BeautifulSoup(html, 'html.parser')
script = soup_judge.find_all('script')[0].text
if script != "":
return "登录错误"
else:
return 1
爬取数据
用session的post方法提交数据,获得的response对象保存在r变量中,登录成功后得到姓名的gb2312编码,传递给get_headers函数,这个函数用来返回查询成绩界面所需要的url和referer,get_headers的具体内容如下
def get_headers(username, name): # 定义函数,用以获取成绩的headers中的refer,并返回查询成绩所在的链接地址,此步在登录之后
headers_code['Referer'] = "http://xfz.xaut.edu.cn/(ldvbtr55xj1noy45ran0ug55)/xscj_gc" + \
".aspx?xh=" + username + "&xm="+name+"&gnmkdm=N121603"
kburl = "http://xfz.xaut.edu.cn/(ldvbtr55xj1noy45ran0ug55)/xscj_gc" + \
".aspx?xh=" + username + "&xm="+name+"&gnmkdm=N121603"
return kburl
这个格式如何得到的呢?我们点击登陆成功后的学分制主页的查询所有成绩,选择相应的jsp可以看到
url链接和referer格式如上图,这个referer很重要,没有的话会被弹出
headers_code在全局被定义为
headers_code = { # 设定查询绩点时的headers
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
"Referer": "",
"Host": "xfz.xaut.edu.cn",
"Origin": "http://xfz.xaut.edu.cn",
"Upgrade-Insecure-Requests": "1"
}
有了url和headers,还需要data,get_cj_data函数被定义为
def get_cj_data(kburl): # 获得查询成绩的data,其中__VIEWSTATE,需要先用get请求获得源码后在通过解析获得,btn_xn的值为学年成绩的gb2312解码
response = s.get(kburl, headers=headers_code)
html = response.content.decode('gb2312') # 获取源码并解析
soup = bs4.BeautifulSoup(html, 'lxml')
__VIEWSTATE = soup.find('input', attrs={'name': '__VIEWSTATE'})['value']
data = {
'__VIEWSTATE': __VIEWSTATE,
'ddlXN': '',
'ddlXQ': '',
'Button5': '%B0%B4%D1%A7%C4%EA%B2%E9%D1%AF'
}
return data
需要的data依然可以在payload中看到
接下来成功访问到成绩页面后,就可以获取成绩了,获取成绩的函数如下
def cjLoading(kburl, headers_code, data): # 学生成绩的获取
global s, totalGpa, totalCredit
response = s.post(kburl, headers=headers_code, data=data) # 加载成绩查询页面
html = response.content.decode('gb2312', errors='ignore') # 获取页面的html,忽略错误
soup1 = bs4.BeautifulSoup(html, 'lxml')
xfjdzh = soup1.find('span', attrs={'id': 'xfjdzh'}) # 找到id为xfjdzh的span标签
a = str(xfjdzh).find('</b>')
b = str(xfjdzh).find('<b>')
totalGpa = float(str(xfjdzh)[b+10:a]) # 提取出学分绩点总和数据,用float形式保存
# 找到第一个属性符合的td标签,其中保存着学分总和
xftj = soup1.find('td', attrs={'valign': 'top', 'colspan': '2'})
b = str(xftj).find('<td>', str(xftj).find('<b>')+18)
a = str(xftj).find(
'</td>', str(xftj).find('</td>', str(xftj).find('<b>')+18)+1)
totalCredit = float(str(xftj)[b+4:a]) # 提取出学分总和,用float形式保存
为什么是这种格式呢?我们检查成绩页面的标签可以看到
大家可以去搜一下获取成绩时用到的方法例如str.find()和soup.find()都是干什么用的,帮助你们快速理解
这下我们就爬到了总学分绩点和总学分数据,并分别保存到了全局变量totalGpa和totalCredit中,至此,登录并爬取数据任务完成,总共的代码如下
import os
import bs4
import urllib
import requests
totalGpa = 0 # 从网上爬下的总学分绩点和总学分 初值为0
totalCredit = 0
username = '' # 预设学号密码,方便演示调试
password =
s = requests.session() # 获取一个session给变量s
# 定义登录页面链接和验证码链接
url = 'http://xfz.xaut.edu.cn/(ldvbtr55xj1noy45ran0ug55)/default2.aspx'
imgUrl = 'http://xfz.xaut.edu.cn/(ldvbtr55xj1noy45ran0ug55)/CheckCode.aspx'
headers = { # 设定登录时的headers
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"Content-Length": "209",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "xfz.xaut.edu.cn",
"Origin": "http://xfz.xaut.edu.cn",
"Referer": "http://xfz.xaut.edu.cn/(ldvbtr55xj1noy45ran0ug55)/default2.aspx",
"Upgrade-Insecure-Requests": "1"
}
headers_code = { # 设定查询绩点时的headers
"Accept-Encoding": "gzip, deflate",
"Accept-Language