CSUFT教务处爬虫项目 爬虫实现

CSUFT教务处爬虫项目 爬虫实现


说明

​ 最近考虑接手之前用Python写的教务系统爬虫项目,之前的python是纯控制台的无UI项目,而最近学习了SpringBoot
故此次再写这个项目 目的是复习SpringBoot 以及整合前端React框架,将项目系统化,优雅化,高效化。

首先声明,此项目仅适用于CSUFT教务系统,其它教务系统仅可借鉴思路,切勿照搬套用。

​ 我的开发目的是 希望能够 解决用户登录教务系统不方便不适配移动端、以及数据复杂显示不清晰直观、还有服务依赖于校园网和VPN等等问题。目标就是在教务系统获取同学们关注的关键数据(如成绩、课表、考试信息)然后利用清晰的可视化工具再次渲染。

开始🎨

​ 现在进入正题。想要拿到数据,在不知道服务器接口暴露出来的情况下,那么能做的只有一个那就是爬虫模拟登录,我们都知道Python写爬虫是一个好手,尤其是Request库中的session可以帮助我们方便的保持登录态,也正是因为如此,所以我的初态教务系统爬虫就是由Python完成,但是作为一名Java程序员,Python能做的Java也能写得很优雅。

首先让我们打开控制台正常登录分析一下其网络请求情况,看见一次登录有四次网络请求,前三次是重定向,最后定位到了一个远古的jsp页面,所以这里就可以大胆猜测,是服务器做了多次重定向最后定位到jsp,可能是用jcookie保持登录态。

image-20221005225024810

现在依次分析首先是初始请求,先看负载

image-20221005225306750

​ 这里就是登陆表单提交的数据username故名思意就是学号,然后还有一长串password,这里我们可以推断出这个password应该是密码加密后的某种数据,然后还有lt,dllt,execution,_eventId , rmShown几个参数。暂时不清楚那些参数是什么意思,那就再请求多次,看看不同的请求负载长什么样。

image-20221005230145718

再次请求发现lt参数也在每次不同请求变化,而dllt,_eventId , rmShown一直保持不变,execution也一直是e4s1或者e3s1、e2s1变化有理由相信也应该是常量,那么我们需要模拟构建的数据是动态的只有password和lt。下面让我们去源码文件找找看有没有这些东西。

image-20221005230836947

原来在登录表单里,隐藏了这些表单数据,而lt的值竟然出现在了value中,那么我们就可以通过html解析工具类来拿到这个值,再看看其它参数,估计都可以写一个定值。那么现在最后剩下了加密后的password这个值怎么拿到,还是老套路,没有想法就去翻源码,这远古的教务系统源码几乎都没有被打包压缩过,按理来说这种加密就应该在前端能找到。

image-20221006161316360

查看这个密码表单,会找到一个passwordEncrypt,但这里value值没有显示出来,其实可以想到一定是通过js将给出的密码加密产生的,继续找找

image-20221006161618879

这里可以找到一个js,里面包含一个加密盐,且每次这个值也是变化的,能找到这个估计我猜整个加密算法都可以找到。再仔细找一找

image-20221006161840108

好家伙这加密算法都给你了

image-20221006162014367

这就是加密算法,猜测一下,data就是你的输入框输入的真实密码,而aeskey估计就是之前那个加密盐,我们只需要用之前得到的盐和自己的密码放进去就可以得到加密密码了。那么要想完成这个方法必须要要在java端完成加密,但是我显然不会去看一遍这个加密算法,然后用java重写一遍,所以我选择另一个方法,在java逆向解密js,就是在java运行js代码。

image-20221006162507845

我们先把这两个文件代码复制下来,(最好用在线压缩代码的网站压缩一下不然太长了)然后直接利用Java的ScriptEngineManager执行这个js函数里的encryptAES函数得到加密密码,这里给出我们学校的加密方法的压缩后Js代码(用字符串js存储),具体怎么使用ScriptEngineManager可以去我的源码查找。


//第一个文件
     String   js = "var CryptoJS=CryptoJS||function(u,p){var d={},l=d.lib={},s=function(){},t=l.Base={extend:function(a){s.prototype=this;var c=new s;a&&c.mixIn(a);c.hasOwnProperty(\"init\")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty(\"toString\")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},\n" +
                "r=l.WordArray=t.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=p?c:4*a.length},toString:function(a){return(a||v).stringify(this)},concat:function(a){var c=this.words,e=a.words,j=this.sigBytes;a=a.sigBytes;this.clamp();if(j%4)for(var k=0;k<a;k++)c[j+k>>>2]|=(e[k>>>2]>>>24-8*(k%4)&255)<<24-8*((j+k)%4);else if(65535<e.length)for(k=0;k<a;k+=4)c[j+k>>>2]=e[k>>>2];else c.push.apply(c,e);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<\n" +
                "32-8*(c%4);a.length=u.ceil(c/4)},clone:function(){var a=t.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],e=0;e<a;e+=4)c.push(4294967296*u.random()|0);return new r.init(c,a)}}),w=d.enc={},v=w.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j<a;j++){var k=c[j>>>2]>>>24-8*(j%4)&255;e.push((k>>>4).toString(16));e.push((k&15).toString(16))}return e.join(\"\")},parse:function(a){for(var c=a.length,e=[],j=0;j<c;j+=2)e[j>>>3]|=parseInt(a.substr(j,\n" +
                "2),16)<<24-4*(j%8);return new r.init(e,c/2)}},b=w.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j<a;j++)e.push(String.fromCharCode(c[j>>>2]>>>24-8*(j%4)&255));return e.join(\"\")},parse:function(a){for(var c=a.length,e=[],j=0;j<c;j++)e[j>>>2]|=(a.charCodeAt(j)&255)<<24-8*(j%4);return new r.init(e,c)}},x=w.Utf8={stringify:function(a){try{return decodeURIComponent(escape(b.stringify(a)))}catch(c){throw Error(\"Malformed UTF-8 data\");}},parse:function(a){return b.parse(unescape(encodeURIComponent(a)))}},\n" +
                "q=l.BufferedBlockAlgorithm=t.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){\"string\"==typeof a&&(a=x.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,e=c.words,j=c.sigBytes,k=this.blockSize,b=j/(4*k),b=a?u.ceil(b):u.max((b|0)-this._minBufferSize,0);a=b*k;j=u.min(4*a,j);if(a){for(var q=0;q<a;q+=k)this._doProcessBlock(e,q);q=e.splice(0,a);c.sigBytes-=j}return new r.init(q,j)},clone:function(){var a=t.clone.call(this);\n" +
                "a._data=this._data.clone();return a},_minBufferSize:0});l.Hasher=q.extend({cfg:t.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){q.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(b,e){return(new a.init(e)).finalize(b)}},_createHmacHelper:function(a){return function(b,e){return(new n.HMAC.init(a,\n" +
                "e)).finalize(b)}}});var n=d.algo={};return d}(Math);\n" +
                "(function(){var u=CryptoJS,p=u.lib.WordArray;u.enc.Base64={stringify:function(d){var l=d.words,p=d.sigBytes,t=this._map;d.clamp();d=[];for(var r=0;r<p;r+=3)for(var w=(l[r>>>2]>>>24-8*(r%4)&255)<<16|(l[r+1>>>2]>>>24-8*((r+1)%4)&255)<<8|l[r+2>>>2]>>>24-8*((r+2)%4)&255,v=0;4>v&&r+0.75*v<p;v++)d.push(t.charAt(w>>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join(\"\")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w<\n" +
                "l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\"}})();\n" +
                "(function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<<j|b>>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<<j|b>>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<<j|b>>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<<j|b>>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])},\n" +
                "_doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]),\n" +
                "f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f,\n" +
                "m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m,\n" +
                "E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/\n" +
                "4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math);\n" +
                "(function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length<q;){n&&s.update(n);var n=s.update(d).finalize(r);s.reset();for(var a=1;a<p;a++)n=s.finalize(n),s.reset();b.concat(n)}b.sigBytes=4*q;return b}});u.EvpKDF=function(d,l,p){return s.create(p).compute(d,\n" +
                "l)}})();\n" +
                "CryptoJS.lib.Cipher||function(u){var p=CryptoJS,d=p.lib,l=d.Base,s=d.WordArray,t=d.BufferedBlockAlgorithm,r=p.enc.Base64,w=p.algo.EvpKDF,v=d.Cipher=t.extend({cfg:l.extend(),createEncryptor:function(e,a){return this.create(this._ENC_XFORM_MODE,e,a)},createDecryptor:function(e,a){return this.create(this._DEC_XFORM_MODE,e,a)},init:function(e,a,b){this.cfg=this.cfg.extend(b);this._xformMode=e;this._key=a;this.reset()},reset:function(){t.reset.call(this);this._doReset()},process:function(e){this._append(e);return this._process()},\n" +
                "finalize:function(e){e&&this._append(e);return this._doFinalize()},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(e){return{encrypt:function(b,k,d){return(\"string\"==typeof k?c:a).encrypt(e,b,k,d)},decrypt:function(b,k,d){return(\"string\"==typeof k?c:a).decrypt(e,b,k,d)}}}});d.StreamCipher=v.extend({_doFinalize:function(){return this._process(!0)},blockSize:1});var b=p.mode={},x=function(e,a,b){var c=this._iv;c?this._iv=u:c=this._prevBlock;for(var d=0;d<b;d++)e[a+d]^=\n" +
                "c[d]},q=(d.BlockCipherMode=l.extend({createEncryptor:function(e,a){return this.Encryptor.create(e,a)},createDecryptor:function(e,a){return this.Decryptor.create(e,a)},init:function(e,a){this._cipher=e;this._iv=a}})).extend();q.Encryptor=q.extend({processBlock:function(e,a){var b=this._cipher,c=b.blockSize;x.call(this,e,a,c);b.encryptBlock(e,a);this._prevBlock=e.slice(a,a+c)}});q.Decryptor=q.extend({processBlock:function(e,a){var b=this._cipher,c=b.blockSize,d=e.slice(a,a+c);b.decryptBlock(e,a);x.call(this,\n" +
                "e,a,c);this._prevBlock=d}});b=b.CBC=q;q=(p.pad={}).Pkcs7={pad:function(a,b){for(var c=4*b,c=c-a.sigBytes%c,d=c<<24|c<<16|c<<8|c,l=[],n=0;n<c;n+=4)l.push(d);c=s.create(l,c);a.concat(c)},unpad:function(a){a.sigBytes-=a.words[a.sigBytes-1>>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a,\n" +
                "this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684,\n" +
                "1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})},\n" +
                "decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return\"string\"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d,\n" +
                "b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}();\n" +
                "(function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8,\n" +
                "16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j<a;j++)if(j<d)e[j]=c[j];else{var k=e[j-1];j%d?6<d&&4==j%d&&(k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;d<a;d++)j=a-d,k=d%4?e[j]:e[j-4],c[d]=4>d||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>>\n" +
                "8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r<m;r++)var q=d[g>>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t=\n" +
                "d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})();";
                
//第二个文件
                js += "function getAesString(data,key0,iv0){key0=key0.replace(/(^\\s+)|(\\s+$)/g,\"\");var key=CryptoJS.enc.Utf8.parse(key0);var iv=CryptoJS.enc.Utf8.parse(iv0);var encrypted=CryptoJS.AES.encrypt(data,key,{iv:iv,mode:CryptoJS.mode.CBC,padding:CryptoJS.pad.Pkcs7});return encrypted.toString();}function encryptAES(data,aesKey){if(!aesKey){return data;}var encrypted=getAesString(randomString(64)+data,aesKey,randomString(16));return encrypted;}var $aes_chars='ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';var aes_chars_len=$aes_chars.length;function randomString(len){var retStr='';for(i=0;i<len;i++){retStr+=$aes_chars.charAt(Math.floor(Math.random()*aes_chars_len));}return retStr;}";

至此我们拿到了登陆所需要的所有数据,可以去请求登陆了

然后我们可以检查每个请求的响应体

image-20221005232314905

第一个重定向服务器给了一个location就是上面提到的第二个重定向地址,同时服务器还set了个Cookie,但不是JSESSIONID。

image-20221005232644653

第二个重定向这里请求标头莫名其妙多了这几个Cookie,我没看懂是哪来的,然后由重定向到第三个location

image-20221005232921460

第三个重定向这里请求标头还是这几个Cookie,然后由重定向到目标jsp文件,这里大概可以猜出,这个JSESSIONID、SERVERID=122就是客户端和服务端的SESSION会话标识。后面也可以证实这个SERVERID必需要为122。

image-20221005233252659

最后返回200的也是使用这个cookie,以及后面的操作也是一样。

到这里我们大概理通了整个登录过程,那么最普通的爬虫方法就是一步一步的模拟,然后每次记录并携带相应的cookie,但这里存在的问题是第二个重定向的cookie不知道是从哪里来的,所以这里自己构造cookie就会无从下手(至于为什么会这样我也没想明白),在一番尝试后能找到的方法就是Python的Request中的session方法,它能保持模拟客户端和服务端的连接状态(也就是自动保存COOKIE),当开启重定向访问后也依旧能拿到最终的Cookie。

那么同理在使用Java做模拟请求客户端时,也可以构建一个类似模拟自动管理Cookie的工具类,这样就可以使得每次访问不同页面都保持在同一连接中,那么该如何在Java中实现呢?这里推荐com.squareup.okhttp3这个包。然后对于html页面解析,推荐Jsoup,能够便捷的用JAVA通过模拟DOM解析html文本。

更新:
这里Http请求工具 已经替换为更为方便自动管理cookie的HttpClient

代码👀

//maven 依赖坐标
      <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.2.2</version>
        </dependency>
        <dependency>
            <!-- jsoup HTML parser library @ https://jsoup.org/ -->
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.13.1</version>
        </dependency>

这里注意,okhttp3默认不开启cookie自动管理,需要在 构建OkHttpClient对象的时候传入CookieJar来开启cookie自动管理,下面给出一个模拟Python的Request中的session的方法类。

public class Session {
    //okHttpClient 支持重定向 支持拦截器 支持cookiejar


    private final OkHttpClient mOkHttpClient = new OkHttpClient.Builder().followRedirects(true).addInterceptor(new BasicParamsInterceptor()).cookieJar(new CookieJar() {
        private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();

        
        //这里重写的两个方法就是实现了自动保存、附带cookie在请求头
        @Override
        public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {

            cookieStore.put(url.host(), cookies);
        }
        @Override
        public List<Cookie> loadForRequest(HttpUrl url) {
            List<Cookie> cookies = cookieStore.get(url.host());
            return  (cookies != null) ? cookies : new ArrayList<Cookie>();
        }
    }).build();




    /**
     * @param url  要请求的url
     * @param  paramsMap post的请求参数
     * @return  post的返回结果
     */
    public String post(String url, HashMap<String, String > paramsMap){

        FormBody.Builder formBodyBuilder = new FormBody.Builder();
        Set<String> keySet = paramsMap.keySet();
        for(String key:keySet) {
            String value = paramsMap.get(key);
            formBodyBuilder.add(key,value);

        }
        FormBody formBody = formBodyBuilder.build();

        Request request = new Request
                .Builder()
                .post(formBody)
                .url(url)
                .build();


        try (Response response = mOkHttpClient.newCall(request).execute()) {

            String  respStr = response.body().string();
            return respStr;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    public String get(String url)  {

        final Request.Builder builder = new Request.Builder();
        builder.url(url);
        final Request request = builder
                .build();
        try (Response response = mOkHttpClient.newCall(request).execute()) {

//            if(response.code() != 200){
//                return null;
//            }
            return response.body().string();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
    }

​ 这里还需要说明,这里有一个bug必须要再加一个拦截器并添加一个“SERVERID=122”的头,因为不知道为什么这里okhttp3开了重定向过程中serveid会变成123,124.必须让SERVERID=122,才能最终定位到jsp页面,这里我们将模拟浏览器的一些头,也全部放在请求拦截器中,下面给出代码示例,使用的时候只要在OkHttpClient builder过程中addInterceptor

**
 *  定义 okhttpclient 全局请求拦截器
 *  "Cookie","SERVERID=122" 这个很重要需要添加
 */

public class BasicParamsInterceptor  implements Interceptor{
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request()
                .newBuilder()
                .addHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8")
                .addHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36")
                .addHeader("Referer","http://authserver.csuft.edu.cn/authserver/login?service=http%3A%2F%2Fjwgl.csuft.edu.cn%2F")
                .addHeader("Cookie","SERVERID=122")
                .build();
        return chain.proceed(request);
    }
}

这些配置完成后就可以通过Java成功爬取页面并拿到html了,这里给出模拟登录的部分代码参考

@Override
    public User login(String sid, String pwd, HttpServletRequest request) {

        // 验证参数合法性
        if(sid == null || pwd == null || StringUtils.isAnyBlank(sid ,pwd )){

            throw new BusinessException(ErrorCode.PARAMS_ERROR,"请求参数错误");

        }

        Session mySession = new Session();

        //第一次进入登录 页面,并拿到 隐藏 表单数据
        String firstText  = mySession.get(UrlConstant.LOGIN_URL);

        //请求失败
        if(firstText == null){

            return null;
        }



        //请求成功
        //解析html
        Document document = Jsoup.parse(firstText);



        Element casLoginForm = document.getElementById("casLoginForm");

        Elements ltinput = casLoginForm.getElementsByAttributeValue("name", "lt");

        //获取lt值
        String lt = ltinput.get(0).attributes().get("value");

        //获取key值
        Elements script = document.getElementsByTag("script");
        Element element1 = script.get(1);
        String str = element1.toString();
        System.out.println(str);
        int i = str.lastIndexOf("= \"");
        int j = str.lastIndexOf("\";");
        int len = j-i+1;
        String key = str.substring(i+3,j);

        //使用js逆向加密获取加密密码
        String sign = JsMachine.encryptJs(pwd,key);

        //构造登录参数
        HashMap<String, String > paramsMap = new HashMap<>();
        //学号
        paramsMap.put("username",sid);
        //加密后的密码
        paramsMap.put("password",sign);
        //lt凭证
        paramsMap.put("lt",lt);
        //登录方式,写死
        paramsMap.put("dllt", "userNamePasswordLogin");
        //第一次登录错误一次 ,可以写死
        paramsMap.put("execution","e1s1");
        //写死
        paramsMap.put("_eventId","submit");
        //验证码相关,写死
        paramsMap.put("rmShown","1");


        //提交表单登录
        String resText = null;

        resText = mySession.post(UrlConstant.LOGIN_URL,paramsMap);
        //请求失败
        if(resText == null){

            return null;
        }
        //检查是否登陆成功
        Document document1 = Jsoup.parse(resText);

        Elements title = document1.getElementsByTag("title");

        if(title == null || (!"学生个人中心".equals(title.get(0).text()))){

           throw new BusinessException(ErrorCode.PARAMS_ERROR,"账号密码错误(或检查是否出现验证码)");
        }


        //拿到 姓名
        document = Jsoup.parse(resText);

        Elements el = document.getElementsByClass("Nsb_top_menu_nc");
        String strName = el.get(0).text();

        int index = strName.lastIndexOf("(");
        strName = strName.substring(0,index);

        //将mySession 和 User 信息 存入本次http请求session
        HttpSession session = request.getSession();
        session.setAttribute(USER_LOGIN_STATE,mySession);

        User user = new User(strName,sid);

        session.setAttribute(USER_LOGIN_INFO,user);

        return user;



    }
}

这里其实有个问题我没有解决就是当密码多次错误就会出现验证码,这里我的解决办法是通过正常途径去登陆一次就ok了,以后有机会再弄非正常情况解决验证码的问题吧。这里验证码已经解决,项目如今上线很久,用户人数也比较多,如果你希望了解源码欢迎来我的Github。

最后🎃

说到这里其实整个与爬虫相关的技术和思路都已经将得差不多了,但是离我整个项目的构建还才解决了一部分问题。因为项目最终是WEB项目,爬虫只解决了我的数据获取问题,那么如何将其应用在自己的项目中呢?难道每次获取数据我都要模拟登录再获取吗?请看这篇文章(后端设计

还有就是我的项目最终需要部署在服务器上,那么服务器上可没有校园网,我怎么让服务器访问到内网呢?请看这篇文章(服务器vpn搭建

最后我的CSUFTSPIDER项目前后端全部开源,如需使用请移步我的github

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值