我的新博客地址:
http://jujuba.me/
0x00. 准备工作
工具:
chrome
python
一些python库,主要是requests
要想爬进教务系统,首先要知道教务系统的网址对吧。
这边可以看到网址是http://jwgl.bistu.edu.cn
。但是,机智的人都会发现,当你将这个url输入地址栏并按下回车时,网址变了,变成了形如http://jwgl.bistu.edu.cn/(d5njjm552sqn0j45ijyef3jn)/default2.aspx
这样的地址。多试几次后发现括号内的值一直在变,是一个随机生成的值,但是呢,url里直接带上这个随机生成的值也可以访问同一个页面。这样就比较好办了,直接第一次访问后将括号里的值用正则匹配并保存下来即可。目测这个东西是用来防爬虫的
0x01. 查看表单
首先,需要准备一个chrome,firefox也可以,这边用chrome讲解。
进到http://jwgl.bistu.edu.cn
,并输入账号密码,此时先不要点登录,按下F12打开开发者工具。 点击Network,并勾选perserve log,如下图所示
然后点击登录,可以看到开发者工具里有一大堆东西。最重要的是Form Data,也就是表单数据。我们通过查看aspx文件来获取。如下图所示:
下面我们来看表单各个项:
__VIEWSTATE:dDwyODE2NTM0OTg7Oz55Zwyt%2FiahjOv%2BFwC3Gwi8bASMRg%3D%3D
藏在登录页的网页源代码中,原本为dDwyODE2NTM0OTg7Oz55Zwyt%2FiahjOv%2BFwC3Gwi8bASMRg== 经过url编码后得到
txtUserName:username
TextBox2:password
txtSecretCode:cfp0
RadioButtonList1:%D1%A7%C9%FA
Button1:
lbLanguage:
hidPdrs:
hidsc:
注意:正方教务系统均为gb2312编码,如果程序是utf-8编码,必须进行相应的编码转换。
0x02. 填充表单
上面说到的的表单数据中,有四项是空的,有两项代表了帐号密码,这些都好解决。 但是还有两项,__VIEWSTATE和txtSecretCode,不能轻易得到。
1. __VIEWSTATE的值的获取
在登录页的源代码中Ctrl+F看一下就可以看到VIEWSTATE的值,我们可以用正则表达式来匹配返回的源代码,从而得到 VIEWSTATE的值。
value="dDwyODE2NTM0OTg7Oz55Zwyt/iahjOv+FwC3Gwi8bASMRg==" />
参考代码
if_match = re.search(r'((.*)VIEWSTATE" value=")(.*)(" />)', r.text)
if if_match:
view_state = if_match.group(3)
2. 验证码的获取
同样还是在登录页的源代码中,可以看到
οnclick="reloadcode();"
alt="看不清,换一张"
title="看不清,换一张" a
lt="" border="0"
style="POSITION:absolute;TOP:5px;LEFT:130px" />
点击src可以得到请求验证码的网页,但是这里有个问题,这个页面返回的是一个aspx文件,而aspx文件不是一个图片。仔细观察返回的源代码,我们可以发现,其实这个页面的源代码是由以下几部分组成:
gif的二进制字节流
注意GIF的开头几个字节是GIF,而GIF格式的结尾是0x0b
一个\r\n的换行符
一段html文本
此时我们可以再用正则表达式去匹配这个gif的二进制字节流,我的做法是连\r\n的换行符也匹配进来了,匹配完之后用strip()函数处理一下即可得到一张gif图片完整的字节流。
对于图像的处理,这里我用到了著名的PIL库以及pytesseract库,前者处理验证码图片,后者直接识别验证码。但是经过实践后发现,pytesseract处理正方教务管理系统那种有点歪歪扭扭的验证码,正确率为0。不过听说pytesseract是一个会学习的库,所以弄很多验证码的图片给它训练,应该可以提高一些准确度。
所以,我这边还是采取将图片扔给用户去识别的办法,处理代码如下:
from PIL import Image
import pytesseract
if_match = re.search(r'(((.*)\n)*)(
if if_match:
f = open("data.gif", "wb+")
f.write(if_match.group(1).strip())
f.close()
这里提一下为什么我们请求一次验证码图片再把这个验证码内容发往对应地址有效,想一想登录页面上的【看不清,换一张】按钮,我们请求的实质只是重新申请一个验证码而已。
0x03. 登录教务系统
r = requests.post(self.login_url, data=payload, headers=headers)
以上就是登录代码,注意我们的登录方式是post。
1. 设置表单
表单信息可以通过观察开发者工具中Form Data的view source功能来看传输时的表单信息应该是长什么样的,然后通过字符串来拼接一个完整的表单。当然我们也可以采用更优雅的方式,字典(dict):
payload = {"__VIEWSTATE": self.view_state,
"txtUserName": id,
"TextBox2": password,
"txtSecretCode": check_code,
"RadioButtonList1": u'学生'.encode('gb2312'),
"Button1": "",
"lbLanguage": "",
"hidPdrs": "",
"hidsc": "",}
回顾我们的请求代码
r = requests.post(self.login_url, data=payload, headers=headers)
这里必须说明的是,假如data = string,那么requests就不会再帮你进行url编码了,你得自己编码好并拼凑成字符串。但是如果data = dict,requests就会帮助你把dict转码并变成目标string。 还有这里可以看到,我们必须使用和要爬取的目标网站一致的编码。
2. 设置headers
headers可以通过观察开发者工具中的header来酌情添加,因为并不是每个都是必须的。一般开始爬一个不熟悉的网站时,最好上网查一查那些是必须提交的headers,否则一个个尝试非常磨人。
下面是我登录时的headers:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Content-Type": "application/x-www-form-urlencoded",
"Host":"jwgl.bistu.edu.cn",
"Origin":"http://jwgl.bistu.edu.cn ",
"Upgrade-Insecure-Requests":"1"}
3. 登录
使用r = requests.post(self.login_url, data=payload, headers=headers)
一键登录。之后我们可以通过r.status_code
来查看StatusCode。
对了,别忘了正方返回的是gb2312的编码,源代码中有中文的话很容易出现编码问题,使用r.content.decode('gb2312')
来解码后能得到正确信息。
如果登录失败了,请尝试用工具抓包,如wireshark,burpsuite等,查看自己发出的包是否和F12里看到的相同