利用Post请求登陆ECNU公共数据库的预备工作
最近需要用java写个爬虫,爬取学校公共数据库的课表信息 (在用户提供公共数据库学号和密码的情况下) 。
· 需要的文件/库/工具
- Eclipse
- org.apache.http 包
- jsoup 包
- Google Chrome (本文档中分析网站使用Chrome浏览器中的Network功能进行抓包,以获得发送给后端的POST数据,其他的现代浏览器,也提供这种功能)
· 预备工作—获得并自己生成POST表单数据
有关Eclipse的安装、org.apache.http包的安装、jsoup的安装不赘述。
利用http包写爬虫主要方法就是通过GET/POST方法获取给定URI的资源,或者发送、请求数据。对于公共数据库这样的大型工程,登录界面的表单数据提交无疑是POST方法,那么爬虫在爬学生课表之前的第一步就是要通过已知的学生学号密码进行登陆。
因此要知道登陆公共数据库所POST的表单数据的形式
华东师范大学公共数据库登陆界面的网址是https://portal1.ecnu.edu.cn/cas/login?service=http%3A%2F%2Fportal.ecnu.edu.cn%2Fneusoftcas.jsp。我们使用Chrome浏览器的开发者工具(f12
键)来分析一下登录界面的网页,特别是他的 <form>
元素,以及可能使用的 <script>
元素。
这是登陆界面的网页,需要留意的是表单部分的学号、密码、验证码、和登陆按钮,这是用爬虫模拟登陆需要参考的一些元素。
通过开发者工具,查看网页文档,这里我们特别留意表单元素和脚本链接。
如上图所示,表单提交方法为POST方法。需要提交的表单数据,除了可见的用户名 (学号) 、密码和验证码以外,还有一些隐藏的表单数据也要提交,包括 loginFace
字段 (后续研究发现只要不是人脸识别这个字段提交的时候就是空字符串)、rsa
字段、ul
字段、pl
字段、lt
字段、execution
字段、_eventId
字段。其中 lt
、execution
、_eventId
字段的值 (value
) 在文档加载的时候就已经由后端给出,他们可能分别指定了时间戳、执行状态、事件类型
在网页的最后还执行了两个 javascript
文件,一个是 des.js
,它定义了几个用于DES加密解密的函数;还有一个是 login4.js
,它对用户的表单输入数据进行最后的处理,以正确的形式输出发给网站后端的POST请求数据。
为了让爬虫程序创建一个httpClient并且能够模拟登陆,我们必须按照正确的格式提供正确的POST请求数据给后端,或者说我们需要知道POST请求中提交了哪些Name-Value Pair
。为此需要知道在浏览器上当点击登陆按钮之后,从本机发出的数据包中包含了什么POST请求数据,这样爬虫可以生成类似的POST请求数据像后端提交登陆信息。
还是在Chrome的开发者工具界面中,点击 Network
工具,之后勾选 Preserve Log
选项,使用Chrome自带的抓包程序,并且在提交表单页面刷新之后抓包的结果不会丢失。
就像这样。
在原来的网页上,输入用户名、密码、验证码,点击登陆按钮,在开发者工具的Network工具中查看抓包的结果。
如上图所示,蓝框标识的这个数据包就是我们要研究的发送POST请求的数据包,而右边数据包内容中的 Form Data
部分也应证了这一点,这些都是表单需要发送的POST请求数据。
可以回顾一下前几幅图,网页上面的验证码 (6841) 说明 code
字段是存储验证码的。LoginFace
对应的值一直是空字符串。rsa
中存放的密文 (很明显这是DES的输出结果,但是就是放在 rsa
字段中),应该至少包括了用户的账号密码信息,因为在后续的POST表单数据中并没有账号密码的字段,那么账号密码一定是加密到这个 rsa
字段中在后端进行解密、验证了。ul
和 pl
字段很明显存储的是用户名和密码的长度 (这也是为什么我打码了 pl
)。lt
、execution
、_eventId
三个字段在文档加载的时候就已经由后端给出,他们的值可以用爬虫程序爬取静态网页直接获得这些信息放在POST请求数据中,我们不需要考虑这个是怎么生成的。
问题在于这个 rsa
字段,它存放了至少与用户名密码有关的密文,发送到后端之后进行解密和验证。所以为了正确的POST请求数据,我们的爬虫必须能根据用户名密码信息自己生成一个一样的 rsa
字段的值作为POST请求数据,并发送。
这里就需要看一下网页上引用的外部 javascript
脚本 des.js
和 login4.js
了。在浏览器中键入它们的URL,可以下载到这两个文件。
首先是 des.js
,它仅仅定义了加密解密函数、以及中间过程必要的函数,(读者可以跳过这段代码)。
/**
* DES加密解密
* @Copyright Copyright (c) 2006
* @author Guapo
* @see DESCore
*/
/*
* encrypt the string to string made up of hex
* return the encrypted string
*/
function strEnc(data,firstKey,secondKey,thirdKey){
var leng = data.length;
var encData = "";
var firstKeyBt,secondKeyBt,thirdKeyBt,firstLength,secondLength,thirdLength;
if(firstKey != null && firstKey != ""){
firstKeyBt = getKeyBytes(firstKey);
firstLength = firstKeyBt.length;
}
if(secondKey != null && secondKey != ""){
secondKeyBt = getKeyBytes(secondKey);
secondLength = secondKeyBt.length;
}
if(thirdKey != null && thirdKey != ""){
thirdKeyBt = getKeyBytes(thirdKey);
thirdLength = thirdKeyBt.length;
}
if(leng > 0){
if(leng < 4){
var bt = strToBt(data);
var encByte ;
if(firstKey != null && firstKey !="" && secondKey != null && secondKey != "" && thirdKey != null && thirdKey != ""){
var tempBt;
var x,y,z;
tempBt = bt;
for(x = 0;x < firstLength ;x ++){
tempBt = enc(tempBt,firstKeyBt[x]);
}
for(y = 0;y < secondLength ;y ++){
tempBt = enc(tempBt,secondKeyBt[y]);
}
for(z = 0;z < thirdLength ;z ++){
tempBt = enc(tempBt,thirdKeyBt[z]);
}
encByte = tempBt;
}else{