今年學習了幾個月nodejs。暑假期間,閒的無事,都說學習爬蟲必定要爬一爬教務系統,不過更多的人爬教務系統用的都是python,正好最近在學nodejs,因而就想,我能夠用nodejs實現一個嗎?說幹就幹。html
我有兩種思路:一是利用selenium之類的自動化測試來實現爬蟲,二是分析教務系統的請求,仿造瀏覽器請求來實現這個爬蟲。因而我一 一按照這兩種思路去實現了爬蟲。前端
這裏說一說分析教務系統請求的思路,自動化測試就再也不說了。node
首先咱們打開教務系統的首頁(這裏的例子是山西大學教務網絡管理系統--其它青果教務系統大同小異)python
打開F12開發者工具,進入Network選項卡,勾選preserve log,以方便咱們記錄每一步網絡請求。git
咱們要實現獲取成績獲取課表等等操做,首先第一步就是要登陸教務系統(也是相對獲取成績之類操做最難的一步)。github
下來咱們來分析一下登陸的過程。web
整個主頁的請求能夠大體簡化爲:後端
1.get:http://bkjw.sxu.edu.cn/,同時獲取到cookiepromise
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,我將會盡快改正。
如今有好幾個函數依然是僅適用於山西大學教務網絡管理系統,我將會盡快將它們改爲適合全部青果系統的函數。
新人一隻,但願你們能夠給我提出意見。