Node.js 爬取 UPR 教務(wù)系統(tǒng)獲得課表數(shù)據(jù)

起因

最近手機(jī)用谷歌日歷,發(fā)現(xiàn)可以把課表導(dǎo)入,桌面小插件看課表很方便,然后用js還要開瀏覽器,于是就去了解了下Node.js,發(fā)現(xiàn)可以很方便爬取數(shù)據(jù)

思路

首先獲取網(wǎng)站cookie,然后模擬登陸,再把課表文件下載下來

使用工具

Node.js
superagent, fs 模塊

superagent是一個(gè)輕量級(jí)ajaxAPI,是一個(gè)關(guān)于HTTP方面的一個(gè)庫(kù),使用鏈?zhǔn)綄懛ā?fs是Node.js自帶的模塊,用于與文件系統(tǒng)進(jìn)行交互。

安裝

$ npm install superagent --save

引用

const superagent = require("superagent")
const fs = require("fs")

使用到的代碼

superagent
    .[get/post/...](url)
    .set()
    .send()
    .end(function(err,res){
        //do something
    })
  • 設(shè)置請(qǐng)求,參數(shù)中加入請(qǐng)求地址(例如向baidu發(fā)送get請(qǐng)求)
    .get("https://www.baidu.com")
    
  • 設(shè)置請(qǐng)求頭
    • 單個(gè)設(shè)置
      .set('Referer','https://www.google.com')
      
    • 一起設(shè)置(傳入json數(shù)據(jù))
      .set({
          "Cache-Control": "max-age=0",
          "Content-Length": "57",
      })
      
  • 發(fā)送數(shù)據(jù)(傳入json數(shù)據(jù))
    .send({
        "sss":"xxx",
        "kkk":"yyy",
    })
    
  • 處理返回?cái)?shù)據(jù)
    .end(function(err, res){
        if(err){
            return console("xxx出錯(cuò)" + err)
        }
        //對(duì)得到的res做處理
    })
    
    • res
      • res.text包含為被解析的響應(yīng)數(shù)據(jù)
      • res.body將解析返回的數(shù)據(jù),但是目前只支持三種格式(application/x-www-form-urlencoded, application/json和multipart/form-data)
      • res.header響應(yīng)頭,是一個(gè)Object
      • res.type & res.charset 類型和編碼格式
      • res.status 狀態(tài)碼

關(guān)于superagent的詳細(xì)內(nèi)容,可以到簡(jiǎn)書這篇文章查看

  • fs
    fs.writeFile("result.json", JSON.stringify(res.body.xkxx), function (err) {
        if (err) {
            return console.log("文件寫入失敗" + err)
        }
        console.log("文件寫入成功")
    })
    
    fs.writeFile(file, data[, options], callback)是一個(gè)向本地寫入文件的函數(shù)。
    • 第一個(gè)參數(shù)是文件名
    • 第二個(gè)參數(shù)是寫入的數(shù)據(jù)
    • 第三個(gè)參數(shù)是可選參數(shù),指定編碼格式。
    • 第四個(gè)參數(shù)是回調(diào)函數(shù),回調(diào)函數(shù)只有一個(gè)參數(shù),就是error

關(guān)于fs模塊的詳細(xì)內(nèi)容,可以到這篇api文檔查看查看


接下來是爬蟲過程

分析頁(yè)面

打開登陸頁(yè)面,清除cookie,F(xiàn)12打開控制臺(tái),切換到Network選項(xiàng)卡,刷新頁(yè)面


network

發(fā)現(xiàn)4個(gè)文件,很明顯login文件是我們要的,其他都是網(wǎng)站資源文件
點(diǎn)擊login


login文件

查看General頭,發(fā)現(xiàn)請(qǐng)求發(fā)放是get
查看response Headers,找到set-Cookie
查看Request Headers,將請(qǐng)求頭復(fù)制下來,以模擬登陸

接下來進(jìn)行第一步,獲得cookie

//獲取cookie, url 和 headers根據(jù)上文在上方定義出來
var cookie
superagent.get(url)
    .set(headers)
    .end(function (err, res) {
        if (err) {
            return console.log("獲取cookie發(fā)生錯(cuò)誤")
        }
        cookie = res.headers["set-cookie"]
        console.log("獲得到的cookie為:" + cookie)

        //模擬登陸
        login()
    })

第二步,模擬登陸
勾選上Preserve log(在頁(yè)面刷新或更改之間保留控制臺(tái)歷史記錄。 消息將一直存儲(chǔ),直至清除控制臺(tái)或者關(guān)閉標(biāo)簽。)


Preserve log

然后點(diǎn)擊登錄按鈕


查找模擬登陸發(fā)送數(shù)據(jù)頁(yè)面

經(jīng)過分析發(fā)現(xiàn)是名為j_spring_security_check的文件接收到Form Data數(shù)據(jù),進(jìn)行登陸驗(yàn)證
點(diǎn)擊文件,查看


j_spring_security_check文件的Grneral

發(fā)現(xiàn)請(qǐng)求方法是POST,地址為http://xsjwxt.sxau.edu.cn:7872/j_spring_security_check
繼續(xù)看請(qǐng)求頭,

j_spring_security_check文件的Request Headers

發(fā)現(xiàn)里面有cookie請(qǐng)求頭,但是我們前面獲取過了,所以將除了cookie的請(qǐng)求頭復(fù)制下來
然后set里面設(shè)置cookie就好啦
接著查看Form Data


Form Data

多次試驗(yàn)發(fā)現(xiàn)j_captcha1是固定值error(ps: 無語)
然后將Form Data 需要的數(shù)據(jù)寫成一個(gè)json變量,send出去

function login() {
    superagent.post("http://xsjwxt.sxau.edu.cn:7872/j_spring_security_check")
        .set(headers1)
        .set("Cookie", cookie)
        .send(sxau)
        .end(function (err, res) {
            if (err) {
                return console.log("模擬登陸出錯(cuò)")
            }

            //爬取課表頁(yè)面
            course()
        })
}

然后就進(jìn)入課表頁(yè)面爬取數(shù)據(jù)啦
和上面一樣的操作爬取下來,發(fā)現(xiàn)并沒有得到數(shù)據(jù),經(jīng)過觀察發(fā)現(xiàn)課表是用js代碼在頁(yè)面加載完畢后寫入的,
于是在Network一一查看文件,最后發(fā)現(xiàn)是名為callback的文件返回的是課程數(shù)據(jù)的json格式


callback文件

于是直接爬取callback文件
經(jīng)過和上面一樣的操作,將課表成功下載到本地

function getClass() {
    superagent.get("http://xsjwxt.sxau.edu.cn:7872/student/courseSelect/thisSemesterCurriculum/ajaxStudentSchedule/callback")
        .set("DNT",1)
        .set("Referer", "xsjwxt.sxau.edu.cn:7872/student/courseSelect/thisSemesterCurriculum/index")
        .set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36")
        //.set("selectionBar",82022)
        .set("Host", "xsjwxt.sxau.edu.cn:7872")
        .set("X-Requested-With", "XMLHttpRequest")
        .set("Accept", "*/*")
        .set("Accept-Encoding", "gzip, deflate")
        .set("Proxy-Connection", "keep-alive")
        .set("Cookie", cookie)
        .end(function (err, res) {
            if (err) {
                return console.log("獲取課表文件失敗" + err)
            }
            fs.writeFile("result.json", res.text, function (err) {
                if (err) {
                    return console.log("課表文件寫入失敗" + err)
                }
                console.log("課表文件寫入成功")
            })
        })

}

查找課程數(shù)據(jù)時(shí),發(fā)現(xiàn)getSectionAndTime文件返回的是課程時(shí)間,于是把getSectionAndTime文件下載下來,然后配合課表文件,就可以很方便處理成Google日歷可以導(dǎo)入的ics文件了。

運(yùn)行結(jié)果:

運(yùn)行結(jié)果


所有代碼

const superagent = require("superagent")
const fs = require("fs")

//需要的登陸信息
var sxau = {
    "j_username": name,
    "j_password": password,
    "j_captcha1": "error",
}
var url = "http://xsjwxt.sxau.edu.cn:7872"

//請(qǐng)求頭
var headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
    "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.9,en-US;q=0.8,en;q=0.7",
    "Cache-Control": "max-age=0",
    "Content-Length": "57",
    "Content-Type": "application/x-www-form-urlencoded",
    "DNT": "1",
    "Host": "xsjwxt.sxau.edu.cn:7872",
    "Origin": "http://xsjwxt.sxau.edu.cn:7872",
    "Connection": "keep-alive",
    "Referer": "http://xsjwxt.sxau.edu.cn:7872/login",
    "Upgrade-Insecure-Requests": "1",
},
headers1 = {
    "DNT": 1,
    "Referer": "xsjwxt.sxau.edu.cn:7872/student/courseSelect/thisSemesterCurriculum/index",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
    "Host": "xsjwxt.sxau.edu.cn:7872",
    "X-Requested-With": "XMLHttpRequest",
    "Accept-Encoding": "gzip, deflate",
    "Proxy-Connection": "keep-alive",
}

//獲取cookie
var cookie
superagent.get(url)
    .set(headers)
    .end(function (err, res) {
        if (err) {
            return console.log("獲取cookie發(fā)生錯(cuò)誤")
        }
        cookie = res.headers["set-cookie"]
        console.log("獲取到的cookie是:" + cookie)

        login()
    })

//模擬登陸
function login() {
    superagent.post("http://xsjwxt.sxau.edu.cn:7872/j_spring_security_check")
        .set(headers)
        .set("Cookie", cookie)
        .send(sxau)
        .end(function (err, res) {
            if (err) {
                return console.log("模擬登陸出錯(cuò)")
            }

            getClass()
            getTime()
        })
}

function getClass() {
    superagent.get("http://xsjwxt.sxau.edu.cn:7872/student/courseSelect/thisSemesterCurriculum/ajaxStudentSchedule/callback")
        .set(headers1)
        .set("Accept", "*/*")
        .set("Cookie", cookie)
        .end(function (err, res) {
            if (err) {
                return console.log("獲取課表文件失敗" + err)
                
            }
            fs.writeFile("result.json", res.text, function (err) {
                if (err) {
                    return console.log("課表文件寫入失敗" + err)
                }
                console.log("課表文件寫入成功")
            })
        })

}

function getTime() {
    superagent.post("http://xsjwxt.sxau.edu.cn:7872/ajax/getSectionAndTime")
        .set(headers1)
        .set("Accept", "application/json, text/javascript, */*; q=0.01")
        .set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
        .set("Cookie", cookie)
        .end(function (err, res) {
            if (err) {
                return console.log("獲取上課時(shí)間失敗" + err)
            }
            fs.writeFile("time.json", res.text, function (err) {
                if (err) {
                    return console.log("上課時(shí)間文件寫入失敗")
                }
                console.log("上課時(shí)間文件寫入成功")
            })
        })

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容