Helu các bạn! Mình học theo tín chỉ cho nên lịch học không phải tuần nào cùng giống nhau nên hôm nào cũng phải vào trang trường xem lịch (hơi mất chút thời gian và giao diện không trực quan). Một cách nữa là viết lại thời khóa biểu (Mất nhiều thời gian mà có thể sai sót). Các cách này không phù hợp với một thằng lười như mình và mình muốn dùng nhiều lần… Dựng API lấy thời khóa biểu sau đó có thể viết chatbot nhắc lịch hay web/mobile xem lịch tùy ý nữa… Hừm .. Bắt đầu thôi.
Bước 0: Ý tưởng thực hiện
- Input: tài khoản, mật khẩu sử dụng trên trang trường.
- Lấy file thời khóa biểu.
- Phân tích file thời khóa biểu => dữ liệu (JSON).
Bước 1: Phân tích request
Password encrypted trước khi gửi lên!!
Password truyền lên đã mã hóa md5, có một số data khác
Chọn request login > Copy as cURL (bash). Mở Postman Chọn Import > Paste raw text > Ấn nút import Ta được:
Gửi POST thử xem sao !
Vậy là lấy được cookie rồi ^^ Đến với phần lấy file, mình sẽ phân tích request lấy file như request trên và đây là kết quả:
Phần Headers
Phần response – file excel là đây chứ đâu ^^
Bước 2: Tiến hành code
Mình sử dụng express của NodeJS
Cấu trúc project
File login
Nhiệm vụ là lấy cookie
let optionsGetCookies = (username,password,form) =>{
form['__EVENTTARGET'] = '';
form['txtUserName'] = username;
form['txtPassword'] = password;
form['btnSubmit'] = 'Đăng nhập';
return {
method : 'POST',
uri : urlLogin,
simple: false,
timeout: 20000,
followRedirect: true,
resolveWithFullResponse: true,
form:form,
headers: {
'Connection' : 'keep-alive',
'Cache-Control' : 'max-age=0',
'Origin' : urlOrigin,
'Upgrade-Insecure-Requests':'1',
'Content-Type' : 'application/x-www-form-urlencoded',
'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.18 Safari/537.36 Edg/75.0.139.4',
'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
'Referer' : urlLogin,
'Accept-Encoding' : 'gzip, deflate',
'Accept-Language' : 'en-US,en;q=0.9',
}
}
}
module.exports = (username,password) =>{
return new Promise((resolve,reject)=>{
getElements(urlLogin,null).then((result)=>{
let elements = result.elements;
requestPromise(optionsGetCookies(username,password,elements)).then((_result)=>{
return resolve({task:'getCookies',success:true, cookies:_result.headers['set-cookie']});
}).catch((_error)=>{
return reject({task:'getCookies',success: false, error:_error});
})
}).catch((error)=>{
return reject({task:'getElements',success: false, error:error});
})
});
}
File get elements
Mục đích là lấy các element trong body. Sử dụng cheerio
let optionsGetElement = (uri,cookies) =>{
return {
method : 'GET',
uri : uri,
followRedirect: true,
headers: {
'Connection' : 'keep-alive',
'Cache-Control' : 'max-age=0',
'Origin' : urlOrigin,
'Upgrade-Insecure-Requests':'1',
'Content-Type' : 'application/x-www-form-urlencoded',
'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.18 Safari/537.36 Edg/75.0.139.4',
'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
'Referer' : urlLogin,
'Accept-Encoding' : 'gzip, deflate',
'Accept-Language' : 'en-US,en;q=0.9',
'cookie' : formatCookieListToString(cookies),
}
}
}
module.exports = (uri,cookies) =>{
return new Promise((resolve,reject)=>{
requestPromise(optionsGetElement(uri,cookies)).then((response)=>{
let $ = cheerio.load(response);
let hiddenInputList = $('input[type="hidden"]');
let elements = {};
if (cookies != null){
let options = $('option[selected="selected"]');
elements['PageHeader1$drpNgonNgu'] = options[0].attribs.value;
elements['drpSemester'] = options[1].attribs.value;
elements['drpTerm'] = options[2].attribs.value;
elements['drpType'] = options[3].attribs.value;
elements['btnView'] = 'Xuất file Excel';
}
for (let i=0;i<hiddenInputList.length;i++){
if (hiddenInputList[i].attribs.value == undefined){
elements[hiddenInputList[i].attribs.name] = '';
}else{
elements[hiddenInputList[i].attribs.name] = hiddenInputList[i].attribs.value;
}
}
return resolve({task:'getElements',success:true,elements:elements})
}).catch((error)=>{
return reject({task:'getElements',success:false,error:error})
})
});
}
Lấy file excel thời khóa biểu
let optionsGetFile = (cookies,form) =>{
return {
resolveWithFullResponse: true,
method : 'POST',
encoding: null,
uri : urlStudentTimeTable,
headers: {
'Connection' : 'keep-alive',
'Cache-Control' : 'max-age=0',
'Origin' : urlOrigin,
'Upgrade-Insecure-Requests' : '1',
'Content-Type' : 'application/x-www-form-urlencoded',
'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
'Referer' : urlStudentTimeTable,
'Accept-Encoding' : 'gzip, deflate',
'Accept-Language' : 'en-US,en;q=0.9,vi;q=0.8',
'cookie' : formatCookieListToString(cookies),
},
form:form
}
}
module.exports = async(username,password) =>{
return new Promise(async(resolve,reject)=>{
await getCookies(username,password).then(async(result)=>{
let cookies = result.cookies;
await getElements(urlStudentTimeTable,cookies).then(async(_result)=>{
await requestPromise(optionsGetFile(cookies,_result.elements)).then(async(__result)=>{
let file = await fs.createWriteStream(path.resolve(`${__dirname}/files`,`${username}.xls`));
await file.write(__result.body);
await file.end();
return resolve({task:'getFile',success:true,filename:`${username}.xls`});
}).catch((__error)=>{
console.log(__error);
})
}).catch((_error)=>{
console.log(_error);
})
}).catch((error)=>{
console.log(error);
})
return reject('error');
});
}
Trích xuất dữ liệu từ file
Phân tích file và tiến hành code.
Sử dụng xlsx.
module.exports = (file) =>{
return new Promise((resolve,reject)=>{
try{
let schedule = [];
let getAddressCell = (str) =>{
let r = /d+/;
let row = parseInt(str.match(r)[0]);
let col = str.replace(row,"");
return {row:row,col:col}
}
let formatDateYMD= (str)=>{
str = str.split("/");
let day = str[0];
let month = str[1];
let year = str[2];
return new Date(`${year}/${month}/${day}`)
}
let formatDay = (day) =>{
return day -1;
}
let findDatewithDay = (start_date,end_date,day) =>{
let dateList =[];
for (let i = start_date; i < end_date; i+=60*60*24*1000){
let date_check = new Date(i);
if(formatDay(day) == date_check.getDay()){
dateList.push(date_check.getTime());
}
}
return dateList;
}
let findIndexDate = (date) =>{
let index = -1;
for (let i = 0; i < schedule.length;i++){
if (schedule[i].date==date){
return i;
}
}
return index;
}
let addToSchedule = (start_date,end_date,day,lesson,subject_name,address) =>{
let dateList = [];
// console.log(start_date,end_date,day,lesson,subject_name,address);
dateList = findDatewithDay(start_date,end_date,day);
for (let i = 0; i < dateList.length; i++){
var found = findIndexDate(dateList[i]);
if (found == -1 ){
let lessons = new Array();
lessons.push({lesson:lesson,subject_name:subject_name,address:address});
schedule.push({date:dateList[i],lessons:lessons});
}else{
schedule[found].lessons.push({lesson:lesson,subject_name:subject_name,address:address});
}
}
}
let lessonArray = (lesson) =>{
let lesson_array_new = [];
let lesson_array = ['1,2,3','4,5,6','7,8,9','10,11,12','13,14,15,16'];
for (let i = 0 ; i < lesson_array.length ; i++){
if (lesson.indexOf(lesson_array[i]) != -1){
lesson_array_new.push(lesson_array[i]);
}
}
return lesson_array_new;
}
let getInfoDetail = (start_date,end_date,subject_name,str) =>{
str = str.split("n");
for (let i=0;i < str.length; i++){
if (str[i] != '' && str[i] != null && str[i] != undefined){
//console.log("===>",str_start_date,str_end_date,str[i]);
let str_info = str[i].split("tại");
let address = str_info[1];
let day_and_lesson = str_info[0].split("tiết");
let lesson = day_and_lesson[1].replace(" ","").replace(" ","");
let day = day_and_lesson[0].replace(" ","").replace("Thứ","");
let lesson_array = lessonArray(lesson);
for (let i = 0; i < lesson_array.length;i++){
// console.log(start_date,end_date,day,lesson_array[i],subject_name,address);
addToSchedule(start_date,end_date,day,lesson_array[i],subject_name,address);
}
}
}
}
var workbook = XLSX.readFile(path.resolve(`${__dirname}/files`,file));
var worksheet = workbook.Sheets[workbook.SheetNames[0]];
let name = worksheet["C6"]["v"];
let student_id = worksheet["F6"]["v"];
let class_name = worksheet["C7"]["v"];
let major = worksheet["F8"]["v"];
for (z in worksheet) {
if(z[0] === '!') continue;
//console.log(getAddressCell(z),z,worksheet[z]["v"]);
if (worksheet[z]["v"].toString().indexOf("Từ")>=0 && worksheet[z]["v"].toString().indexOf("đến")>=0){
let address_cell = getAddressCell(z);
let subject_name = worksheet[`F${address_cell.row}`]["v"].split("-")[0];
let sessionList = worksheet[z]["v"].toString().split("Từ");
for (let i =0;i<sessionList.length;i++){
let str = sessionList[i].replace("n","").split(":");
let str_datetime = str[0].replace(" ","").split("đến");
let str_start_date = str_datetime[0];
let str_end_date = str_datetime[1];
if (str_start_date != undefined && str_end_date != undefined){
getInfoDetail(formatDateYMD(str_start_date).getTime(),formatDateYMD(str_end_date).getTime(),subject_name,str[1]);
}
}
}
}
fs.unlink(path.resolve(`${__dirname}/files`,file), function (err) {
if (err) throw err;
// if no error, file has been deleted successfully
console.log({task:'readFile',success:true,message:`File ${file} deleted!`});
});
return resolve({task:'readFile',success:true,student_id :student_id,name:name,class_name:class_name,major:major,schedule:schedule});
}catch(e){
fs.unlink(path.resolve(`${__dirname}/files`,file), function (err) {
if (err) throw err;
// if no error, file has been deleted successfully
console.log({task:'readFile',success:true,message:`File ${file} deleted!`});
});
return reject({task:'readFile',success:false,error:e});
}
});
}
Kết quả
{"task":"readFile","success":true,"student_id":"xxxxx","name":"xxxxx","class_name":"xxx","major":"Công nghệ thông tin","schedule":[{"date":1578355200000,"lessons":[{"lesson":"10,11,12","subject_name":"Hệ thống thông tin di động","address":" 301_TA2 TA2"}]},{"date":1578960000000,"lessons":[{"lesson":"10,11,12","subject_name":"Hệ thống thông tin di động","address":" 301_TA2 TA2"},{"lesson":"7,8,9","subject_name":"Kiến trúc máy tính","address":" 301_TA2 TA2"}]},{"date":1580688000000,"lessons":[{"lesson":"10,11,12","subject_name":"Hệ thống thông tin di động","address":" 301_TA2 TA2"},{"lesson":"7,8,9","subject_name":"Nguyên lý hệ điều hành","address":" 301_TA2 TA2"}]},{"date":1581292800000,"lessons":[{"lesson":"10,11,12","subject_name":"Hệ thống thông tin di động","address":" 301_TA2 TA2"},{"lesson":"7,8,9","subject_name":"Nguyên lý hệ điều hành","address":" 301_TA2 TA2"}]},{"date":1581897600000,"lessons":[{"lesson":"10,11,12","subject_name":"Hệ thống thông tin di động","address":" 301_TA2 TA2"},{"lesson":"7,8,9","subject_name":"Nguyên lý hệ điều hành","address":" 301_TA2 TA2"}]},{"date":1582502400000,"lessons":[{"lesson":"10,11,12","subject_name":"Hệ thống thông tin di động","address":" 301_TA2 TA2"},{"lesson":"7,8,9","subject_name":"Nguyên lý hệ điều hành","address":" 301_TA2 TA2"}]},{"date":1583107200000,"lessons":[{"lesson":"10,11,12","subject_name":"Hệ thống thông tin di động","address":" 301_TA2 TA2"},{"lesson":"7,8,9","subject_name":"Nguyên lý hệ điều hành","address":" 301_TA2 TA2"}]},{"date":1583712000000,"lessons":[{"lesson":"10,11,12","subject_name":"Hệ thống thông tin di động","address":" 301_TA2 TA2"},{"lesson":"7,8,9","subject_name":"Nguyên lý hệ điều hành","address":" 301_TA2 TA2"}]},{"date":1584316800000,"lessons":[{"lesson":"10,11,12","subject_name":"Hệ thống thông tin di động","address":" 301_TA2 TA2"},{"lesson":"7,8,9","subject_name":"Nguyên lý hệ điều hành","address":" 301_TA2 TA2"}]},{"date":1584057600000,"lessons":[{"lesson":"10,11,12","subject_name":"Hệ thống thông tin di động","address":" 301_TA2 TA2"}]}]}
Sản phẩm
API được sử dụng trong app viết bởi anh trong CLB của mình. Ứng dụng xem lịch học
TopDev via Traiit.github