利用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{
if(firstKey != null && firstKey !="" && secondKey != null && secondKey != ""){
var tempBt;
var x,y;
tempBt = bt;
for(x = 0;x < firstLength ;x ++){
tempBt = enc(tempBt,firstKeyBt[x]);
}
for(y = 0;y < secondLength ;y ++){
tempBt = enc(tempBt,secondKeyBt[y]);
}
encByte = tempBt;
}else{
if(firstKey != null && firstKey !=""){
var tempBt;
var x = 0;
tempBt = bt;
for(x = 0;x < firstLength ;x ++){
tempBt = enc(tempBt,firstKeyBt[x]);
}
encByte = tempBt;
}
}
}
encData = bt64ToHex(encByte);
}else{
var iterator = parseInt(leng/4);
var remainder = leng%4;
var i=0;
for(i = 0;i < iterator;i++){
var tempData = data.substring(i*4+0,i*4+4);
var tempByte = strToBt(tempData);
var encByte ;
if(firstKey != null && firstKey !="" && secondKey != null && secondKey != "" && thirdKey != null && thirdKey != ""){
var tempBt;
var x,y,z;
tempBt = tempByte;
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{
if(firstKey != null && firstKey !="" && secondKey != null && secondKey != ""){
var tempBt;
var x,y;
tempBt = tempByte;
for(x = 0;x < firstLength ;x ++){
tempBt = enc(tempBt,firstKeyBt[x]);
}
for(y = 0;y < secondLength ;y ++){
tempBt = enc(tempBt,secondKeyBt[y]);
}
encByte = tempBt;
}else{
if(firstKey != null && firstKey !=""){
var tempBt;
var x;
tempBt = tempByte;
for(x = 0;x < firstLength ;x ++){
tempBt = enc(tempBt,firstKeyBt[x]);
}
encByte = tempBt;
}
}
}
encData += bt64ToHex(encByte);
}
if(remainder > 0){
var remainderData = data.substring(iterator*4+0,leng);
var tempByte = strToBt(remainderData);
var encByte ;
if(firstKey != null && firstKey !="" && secondKey != null && secondKey != "" && thirdKey != null && thirdKey != ""){
var tempBt;
var x,y,z;
tempBt = tempByte;
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{
if(firstKey != null && firstKey !="" && secondKey != null && secondKey != ""){
var tempBt;
var x,y;
tempBt = tempByte;
for(x = 0;x < firstLength ;x ++){
tempBt = enc(tempBt,firstKeyBt[x]);
}
for(y = 0;y < secondLength ;y ++){
tempBt = enc(tempBt,secondKeyBt[y]);
}
encByte = tempBt;
}else{
if(firstKey != null && firstKey !=""){
var tempBt;
var x;
tempBt = tempByte;
for(x = 0;x < firstLength ;x ++){
tempBt = enc(tempBt,firstKeyBt[x]);
}
encByte = tempBt;
}
}
}
encData += bt64ToHex(encByte);
}
}
}
return encData;
}
// 剩余的部分请上网查阅,链接在下方给出。
有关这个DES加密函数的源文件,请参考这个网址: https://www.cnblogs.com/qiongmiaoer/p/3573474.html ,这上面不仅给出了 des.js
的代码、甚至连它的 java
版本都有,极大地减少了工作量!
login.js
的源码形式比较复杂,它是被 eval(function(p, a, c, k, e, r){})
函数加密 (也有称为压缩的)后的结果,我尝试把这堆原始的代码贴上来一下(读者应跳过这段代码)。
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('$(4(){$("#2l").8(4(){6 a=$(m).l("2u-2p");v.2V(a)});5(z()==19){n.2i({G:2v,w:2c,2W:\'17\',2G:2Q});n.1k(\'2g\',4(){$("#1j").o();$("#P").7();$(".24").9("w","2D");5($("#12").21(":20")){$(".1Y").7()}});n.1k(\'2e\',4(){$("#10").o()});n.1k(\'2t\',4(a){$("#1j").8()});1d()}t{$("#P").7();5(z()==\'1m\'){$("A[B=\'Z\']").9({"X-1R":"-1Q"})}}6 f=K.1P;5(f.M("1O.N.O.F")!=-1||f.M("2j.2k")!=-1||f.M("2o.N.O.F")!=-1||f.M("2q.N.O.F")!=-1||f.M("2r.N.O.F")!=-1){$("#12").7();$("1N").9("1M-W","1L(1b/W/2Z.17)")}t{$("#12").o();$("1N").9("1M-W","1L(1b/W/26.17)")}6 g=1J.1I("2f").1H;$("#1G").D("8").I("8",4(){5(!$(m).1F("C")){$(m).x("C").1D().Y("C");$.2C("1b/1C/2E.1C");$(".1B").9("X-1y","");$("#1v").1u(g);5(z()==19){1d()}t{$("#P").7();5(z()==\'1m\'&&z()!=\'1f\'){$("A[B=\'Z\']").9({"X-1R":"-1Q"})}}$("#2a").D("8").I("8",4(){L()});$("#1t").1i(4(e){5(e.15==13){L()}}).1p(4(e){$(m).q().Y("14");$("#J").q().7()});$("#1n").1i(4(e){5(e.15==13){L()}}).1p(4(e){$(m).q().Y("14");$("#J").q().7()});$("#Z").D().1i(4(e){5(e.15==13){L()}})}});$("#1G").2s("8");6 h=1o 2y("1l",1q,1q);6 i=1J.1I("3c").1H;$("#2K").D("8").I("8",4(){5(!$(m).1F("C")){$(m).x("C").1D().Y("C");$(".1B").9("X-1y","2O");$("#J").q().7();$("#1v").1u(i);5(z()==\'1f\'){$("#1l").x("2P");$(".1r").x("2R")}t{$("#1l").x("2S");$(".1r").x("2T")}h.2U(4(a){6 b=1s("1O");5(a){v.K.1h="30://31.N.O.F/32/33?34=35/39"}t{5(b!=1e){v.K.1h=b}t{v.K.1h=27}}})}});5($("#J").28()){$("#J").q().o()}6 j={1w:2b,1x:2d};6 k=4(){6 a=$(v).w();6 b=$(v).G();$(".1z").9("w",a);$(".1z").9("G",b);$("#1A").9("w",a);$("#1A").9("G",b);$("#2h").9("w",a);6 c=j.1w;6 d=j.1x;6 e=d/c;c=b;d=1a.1E(b*e);5(d<a){d=a;c=1a.1E(d/e)}$(".2m").G(c).w(d)};k();$(v).2n(4(){k()})});6 H="";4 L(){6 a=$("#1t"),$p=$("#1n");6 u=a.E().1g();5(u==""){a.25();a.q().x("14");y}6 p=$p.E().1g();5(p==""){$p.25();$p.q().x("14");y}a.l("T","T");$p.l("T","T");6 b=$("#2w").E();$("#2x").E(u.1K);$("#2z").E(p.1K);$("#2A").E(2B(u+p+b,\'1\',\'2\',\'3\'));$("A[B=\'S\']").l("R",H);$("#2F")[0].2H()};4 2I(){$("#2J").l("U","Z?"+1a.2L())};2M.2N.1g=4(){y m.V(/^\\s\\s*/,\'\').V(/\\s\\s*$/,\'\')};6 z=4(){5(Q.1S=="1T 1U 1V"&&Q.1W.1X(";")[1].V(/[ ]/g,"")=="2X.0"){y\'1m\'}t 5(Q.1S=="1T 1U 1V"&&Q.1W.1X(";")[1].V(/[ ]/g,"")=="2Y.0"){y\'1f\'}t{y 19}};4 1d(){$("#P").D().I(\'8\',4(){n.18();n.1Z(\'#11\')});$("#1j").D("8").I("8",4(){n.18();$(m).7();$("#P").o();$("#16").7();$("#10").7();$("#11").22("23");$("#1c").l("U","").7();H="";$("A[B=\'S\']").l("R","");$(".24").9("w","36");5($("#12").21(":20")){$(".1Y").o()}});$("#10").8(4(){n.37(4(a){$("#1c").l("U",a).o();H=a;$("A[B=\'S\']").l("R",a)});n.18();$(m).7();$("#16").o();$("#11").22("23")});$("#16").8(4(){$(m).7();$("#10").o();$("#1c").l("U","").7();n.1Z(\'#11\');H="";$("A[B=\'S\']").l("R","")})};4 1s(a){6 b=1o 38("(^|&)"+a+"=([^&]*)(&|$)");6 r=v.K.1P.3a(1).3b(b);5(r!=1e)y 29(r[2]);y 1e};',62,199,'||||function|if|var|hide|click|css||||||||||||attr|this|Webcam|show||parent|||else||window|height|addClass|return|is_ie|input|name|active|unbind|val|cn|width|facedata|bind|errormsg|location|login|indexOf|ecnu|edu|activeAuthentication|navigator|value|loginFace|disabled|src|replace|image|margin|removeClass|code|confirm|my_camera|apps_bar||login_error_border|which|again|jpg|reset|false|Math|comm|my_face|initCamera|null|ie8|trim|href|keyup|concel|on|qrcode|ie7|pd|new|keydown|153|login_code_notice|GetQueryString|un|html|logincontainer|imageWidth|imageHeight|bottom|login_conatiner|container_bg|login_box_title|js|siblings|round|hasClass|accountlogin|innerHTML|getElementById|document|length|url|background|body|service|search|12px|left|appName|Microsoft|Internet|Explorer|appVersion|split|link_box_right|attach|hidden|is|removeAttr|style|content_login_box|focus|container_01|default_redirect_url|text|unescape|index_login_btn|1680|240|1050|live|account_template|load|login_right_box|set|oaindex|jsp|guidebook|login_img_01|resize|treasury|link|applicationidc|applicationnewjw|trigger|error|data|336|lt|ul|loginQRCode|pl|rsa|strEnc|getScript|567px|placeholderfriend|loginForm|jpeg_quality|submit|refreshCodeImg|codeImage|weixinlogin|random|String|prototype|50px|qr_code_ie8|90|qr_notice_ie8|qr_code_normal|qr_notice_normal|generateLoginQRCode|open|image_format|MSIE7|MSIE8|container_02|https|portal1|tp_tpass|h5|act|tpass|373px|snap|RegExp|guide|substr|match|weixin_template'.split('|'),0,{}))
上网查阅了一下资料之后 (https://blog.csdn.net/cainiaoxiaozhou/article/details/8960561),发现这种加密的格式是可以解密的,而且解密函数已经写在了上述 eval()
中,只需稍加修改就可以获得原来的 javascript
代码。下面给出具体的操作及注释。
在eval(function(){})函数的开始,有这样一行代码:
while(c--)
if(k[c])
p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);
return p;
这其实就是通过正则表达式匹配到被压缩/加密的部分并且进行解密的操作,为了直观地得到原来的javascript代码,我们不要让他将p返回,将p输出到html文档上,就可以获得解密后的 javascript 代码了。因此只要将
return p;
改成
document.getElementById("_text").innerText=p;
并且在登陆页面的副本上加上
<div id="_text"></div>
就可以看到加密前的 javascript
代码了,它并没有换行符或其他空白,并没有可读性。
就像这样。通过这个网站:http://tool.chinaz.com/Tools/JsFormat.aspx ,可以提高可读性。
出于篇幅考虑,这里贴一下原来的 login4.js
中我需要关心的部分,另加了一些注释。其余的部分请感兴趣的读者照着上述的方法自己实现并获得。
function login() {
var a = $("#un"),
$p = $("#pd"); // id="un" 登陆界面的用户名,id="pd" 登陆界面的密码
var u = a.val().trim();
if (u == "") {
a.focus();
a.parent().addClass("login_error_border");
return
}
var p = $p.val().trim();
if (p == "") {
$p.focus();
$p.parent().addClass("login_error_border");
return
}
a.attr("disabled", "disabled");
$p.attr("disabled", "disabled");
var b = $("#lt").val();
$("#ul").val(u.length);
$("#pl").val(p.length);
$("#rsa").val(strEnc(u + p + b, '1', '2', '3'));
$("input[name='loginFace']").attr("value", facedata);
$("#loginForm")[0].submit()
};
首先解释了为什么POST请求数据中没有用户名密码字段,因为它们
- 被disabled了
- 和lt一起作为rsa的输入,以'1'、'2'、'3'为密钥进行了加密,在后端进行解密与验证。
另外,我们也知道了 `ul` 是用户名的长度, `pl` 是密码的长度, `loginFace` 在不启用人脸识别登陆的情况下是空字符串。
在之前的有关 des.js
的源文件中,也提供了 des.java
,在写爬虫的时候进行 rsa
的计算也方便了很多。
转载自原文链接, 如需删除请联系管理员。
原文链接:利用HTTPPost请求登陆ECNU公共数据库的预备工作,转载请注明来源!