一個基於nodejs的青果教務系統的爬蟲


今年學習了幾個月nodejs。暑假期間,閑的無事,都說學習爬蟲一定要爬一爬教務系統,不過更多的人爬教務系統用的都是python,正好最近在學nodejs,於是就想,我可以用nodejs實現一個嗎?說干就干。

我有兩種思路:一是利用selenium之類的自動化測試來實現爬蟲,二是分析教務系統的請求,仿造瀏覽器請求來實現這個爬蟲。於是我一 一按照這兩種思路去實現了爬蟲。

這里說一說分析教務系統請求的思路,自動化測試就不再說了。

 

首先我們打開教務系統的首頁(這里的例子是山西大學教務網絡管理系統--其它青果教務系統大同小異)

打開F12開發者工具,進入Network選項卡,勾選preserve log,以方便我們記錄每一步網絡請求。

我們要實現獲取成績獲取課表等等操作,首先第一步就是要登錄教務系統(也是相對獲取成績之類操作最難的一步)。

下來我們來分析一下登錄的過程。

 

 整個主頁的請求可以大致簡化為:

1.get:http://bkjw.sxu.edu.cn/,同時獲取到cookie

2.get:http://bkjw.sxu.edu.cn/_data/login.aspx,帶上第一步獲取的cookie值

3.get:http://bkjw.sxu.edu.cn/sys/ValidateCode.aspx,帶上第一步獲取的cookie值,獲取到特定的驗證碼

4.post:http://bkjw.sxu.edu.cn/_data/login.aspx,向這個地址發送post請求,同時帶上表單數據

這里的cookie記錄的是服務器端的session,關於cookie和session,請閱讀http協議相關部分

2-3兩者間隔時間不能太長,否則會失敗

大致一個登錄的過程就是這樣。

下來我們來分析一下發送的表單數據。

首先在瀏覽器里登錄教務系統,獲得一個post請求的記錄。

我們發現總共有以下幾個數據:

__VIEWSTATE:

這個數據是從哪里獲得的呢?

我們再回到表單處,發現有這么一個隱藏的表單,它的value值就是我們所需要的__VIEWSTATE

我們再看發送的表單,發現原來應該是密碼和驗證碼的地方是空,卻多了兩個dsdsdsdsdxcxdfgfg和fgfggfdgtyuuyyuuckjg

這樣看應該是密碼和驗證碼被加密了。

我們就來看看究竟是怎樣的加密過程。

function chkpwd(obj) { 
 if(obj.value!='')  {    
 var s=md5(document.all.txt_asmcdefsddsd.value+md5(obj.value).substring(0,30).toUpperCase()+'10108').substring(0,30).toUpperCase();   document.all.dsdsdsdsdxcxdfgfg.value=s;
} else {
 document.all.dsdsdsdsdxcxdfgfg.value=obj.value;} 
}  function chkyzm(obj) {  
if(obj.value!='') { 
  var s=md5(md5(obj.value.toUpperCase()).substring(0,30).toUpperCase()+'10108').substring(0,30).toUpperCase();  
}}

由於前端代碼我們可以看見,由此可以看到它的加密過程,而且我們的后端用的是nodejs,所以我們只需要小小的修改一下即可(等會討論)。

到現在我們的整個登錄過程就分析完畢了,下面我們開始實現關鍵過程。

首先先說這個密碼的加密和驗證碼的加密。

我們創建了一個類叫做spider

利用nodejs的crypto模塊去實現md5加密

var  crypto  =  require("crypto");
function md5(text){
    return crypto.createHash('md5').update(text).digest('hex');
}

對上面的加密方式稍加修改

//md5加密
spider.prototype.secret=function(option){
    if(option.type==="password"){
        return md5(option.id + md5(option.pwd).substring(0, 30).toUpperCase() + option.schoolNumber).substring(0, 30).toUpperCase();
    }else if(option.type==="check"){
        return md5(md5(option.checkNumber.toUpperCase()).substring(0, 30).toUpperCase() + option.schoolNumber).substring(0, 30).toUpperCase();
    }
};

這里我們接受一個對象,形如

{
            type:'check',//這里是要得到的md5碼類型,check或者password,驗證碼或是密碼
            id:'00000000000',//學號
            pwd:'000000',//密碼
            schoolNumber:'10108',//使用青果教務系統的學校代號
            checkNumber:checkNumber//驗證碼
        }

其次說一說如何獲得viewstate的值

spider.prototype.getViewState=function(cookie,callback){
    var that=this;
    console.log("開始獲取viewstate");
    console.log(`使用的Cookie是${cookie}`);
    var data='';
    var req=http.request({
        hostname:'bkjw.sxu.edu.cn',
        port:80,
        path:'/_data/login.aspx',
        method:'GET',
        headers:{
             'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
             'Accept-Encoding':'gzip, deflate',
             'Accept-Language':'zh-CN,zh;q=0.8',
             'Cache-Control':'no-cache',
             'Connection':'keep-alive',
             'Cookie':`${cookie}`,
             'Host':'bkjw.sxu.edu.cn',
             'Pragma':'no-cache',
             'Referer':'http://bkjw.sxu.edu.cn',
             'Upgrade-Insecure-Requests':'1',
             'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
        }
    },(res)=>{
        res.setEncoding('utf-8');
        res.on("data",(chunk)=>{
            data+=chunk;
        });
        res.on("end",()=>{
            console.log("數據接收完畢");
            console.log(data);
            var $=cheerio.load(data);
            that.viewstate=$("input[name='__VIEWSTATE']").val();
            callback();
        });
    });
    req.on("error",(err)=>{
        console.log(err);
    });
    req.end();
    //console.log(data);
};

這里我用了cheerio去解析源文件來取得我們所要的值。

如何獲取cookie的值

 

spider.prototype.getWeb=function(callback){
        var that=this;
         console.log(this.urls);
        superagent.get(this.urls)
            .end(function(err,res) {
                //console.log(that.urls);
                that.theCookie = res.header['set-cookie'][0].split(";")[0];
                console.log(`
--------------------------
-------------------------
${that.theCookie}
------------------------
------------------------`);
               // var $=cheerio.load(res.text);
                //var inputs=$('input');
               // console.log(res.text);
                setTimeout(()=>{
                    callback();
                },500);
            })
    };

關於異步

由於整個過程中有些步驟要等待上一步完成,但是在nodejs中會異步進行完成,所以在寫的時候出現了還沒有獲取cookie就開始進行了請求等等異步問題,我嘗試使用promise,但是發現promise並不能完全達到我的需要,最后,不得已用了setTimeout和事件來完成。(急待改進)

這里大體分析了一下整個爬蟲的思路和部分實現過程。

我在GitHub上放了我實現整個青果教務系統爬蟲的源代碼:https://github.com/cuijinyu/qingguospider

使用selenium實現的版本:https://github.com/cuijinyu/spiderForSXU

現在可以實現登錄過程,但是爬取課表等等還暫時未完成,獲取成績還有一些bug,我將會盡快改正。

現在有好幾個函數依然是僅適用於山西大學教務網絡管理系統,我將會盡快將它們改成適合所有青果系統的函數。

新人一只,希望大家可以給我提出意見。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM