CLASSをweb scrapingする
とりあえず調査する
仕組み
ログイン
code:js
method: 'POST',
});
form1:htmlUserId:"学籍番号"
form1:htmlPassword:"password"
まさかpassword平文で送信してるの……?
謎parameters
from1:login:x
form1:login:y
com.sum.faces.VIEW:
form:1:"form1"
これのprogramからの送り方がわかればいい
応答
なんとhtmlファイルがそのまま返ってくる
連絡を開く
<a>タグ全てにIDが振られている
処理
該当部分
code:js
//-----------------------------------------------
// 子画面を開く(掲示)
//-----------------------------------------------
function openSubwindowFor201(thisObj, thisEvent) {
var url = "/up/faces/up/po/pPoa0202A.jsp";
var pName = 'PPoa0202A';
var pOption = 'width=720,height=400,scrollbars=yes,resizable=yes';
var urlParam = "?fieldId="+thisObj.id ;
var target = "PPoa0202A";
var status = "width=720,height=400,scrollbars=yes,resizable=yes";
var args = "com.jast.gakuen.up.po.PPoa0202A";
removeSession(args,url+urlParam,target,status);
return false;
}
code:js
AjaxUtil.prototype.removeSession = function (cls,url,name,opt) {
var ajaxServlet = "up/co/RemoveSessionAjax";
var args = new Object();
args.pcClass = cls;
var engine = new AjaxEngine();
engine.setCallbackMethod(
function(value) {
var windowPointer = window.open(url,name,opt);
windowPointer.focus();
}
);
engine.send(ajaxServlet,null,args);
}
分析結果
以下のURLを開くと取得できる
/up/faces/up/po/pPoa0202A.jsp?fieldId=:fieldId
fieldIdに<a>のidを指定する
window.openでもfetchでも取得できる
/icons/javascript.icon
なんとbundleも難読化もされずに提供されてる!
いいのかそれはtakker.icon
コメントも消してないし
やりたい放題じゃん
web pageの構造
sessionの保持
これが問題か
fetch経由で渡せるといいんだけど……
cookie
__utma
有効期限が1日くらいある
__utmc
sessionが切れるとなくなるやつ
__utmz
有効期限が4ヶ月くらいある
2021-02-11 01:45:25 上3つはいらなかった
おそらく古いversionのCLASSで使われていたやつかも
新規発行はされなかった
JSESSIONID
sessionが切れるとなくなるやつ
cookieを消したときの挙動
1. GET https://class.admin.tus.ac.jp/up/faces/up/po/Poa00601A.jsp
SET-COOKIEにJSESSIONIDが返ってくる
このステップは飛ばして大丈夫
2. POST https://class.admin.tus.ac.jp/up/faces/login/Com00504A.jsp
https://class.admin.tus.ac.jp/up/faces/login/Com00504A.jspがlog in画面?らしい
form data
from1:logout:x:"105"
form1:logout:y"13"
com.sum.faces.VIEW:"_id15001:_id15002"
form:1:"form1"
2. はGET https://class.admin.tus.ac.jp/up/faces/login/Com00505A.jspでも問題なし
SET-COOKIEにJSESSIONIDが返ってくる
3. POST https://class.admin.tus.ac.jp/up/faces/login/Com00505A.jsp
form data
ようやくわかった。これ<input>のidとvalueのペアだ
form1:htmlUserId:"学籍番号"
form1:htmlPassword:"password"
↑#form1:htmlUserIdなどの値
これは多分いらない
from1:login:x:"67"
form1:login:y:"11"
com.sum.faces.VIEW:"_id15001:_id15209"
form:1:"form1"
どうやら必要なのはJSESSIONIDだけで、それはserverに問い合わせれば返ってくるらしい
cookieがある時の挙動
1. GET https://class.admin.tus.ac.jp/up/faces/login/Com00501A.jsp
log in画面が表示される
以降は同じ
JSESSIONIDを取得する
code:js
function getJsessionId() {
return response.headers.get('Set-Cookie').split(';')0; }
2021-02-11 00:16:33 コードがしっちゃかめっちゃかになってきた
一旦整理するか。
困った
Denoでweb scrapingするしかなさそう……
2021-02-11 01:41:09 log in突破できた。行けそう。
あとはvercelでserverless functionを立てて、GASから定期的にfetchすればおkだ!
詳細ページの添付ファイルをdownloadする
POST https://class.admin.tus.ac.jp/up/faces/up/po/pPoa0202A.jsp
code:formData.json
{
"form1:htmlFileTable:0:downLoadButton.x": "5",
"form1:htmlFileTable:0:downLoadButton.y": "13",
"form1:htmlParentFormId": "",
"form1:htmlDelMark": "",
"form1:htmlRowKeep": "",
"com.sun.faces.VIEW": "_id28542:_id28543",
"form1": "form1"
}
これまだうまく動かせていない
programからデータを取得できるか試す
login 画面の突破
21:28:39 うーん、何故かlog in画面に戻ってしまう
なにか足りない?
from1:login:xとform1:login:y?
これが必要だった
といっても0を入れるだけで突破できた。ガバ運用
com.sum.faces.VIEWの数値?
21:49:36 スペルミスだった
s/com.sum.faces.VIEW/com.sun.faces.VIEW/
Refererは関係なさそう
code:js
(async () => {
function createURLSearchParams(data) {
const params = new URLSearchParams();
Object.keys(data).forEach(key => params.append(key, datakey)); return params;
}
const body = createURLSearchParams({
'form1:htmlUserId': document.getElementById('form1:htmlUserId').value,
'form1:htmlPassword': document.getElementById('form1:htmlPassword').value,
'form1:login.x': 0,
'form1.login.y': 0,
'com.sun.faces.VIEW': document.getElementById('com.sun.faces.VIEW').value,
'form1': 'form1',
});
const JSESSIONID = 'xxx';
const res = await fetch(url, {
method: 'POST',
headers: {
Cookie: JSESSIONID=${JSESSIONID},
},
body,
});
console.log(res);
console.log(await res.text());
})();
dash boardから情報を取得する
一つづつfetchしないと、同じデータが送られてきてしまうみたい
一つづつ処理しても同じだった
どうやら既読処理をしないといけないようだ
code:js
(async () => {
const sessionId = 'xxx';
const announces = document
.getElementById('form1:Poa00201A:htmlParentTable:0:htmlDetailTbl')
.querySelectorAll('td.title a');
.map(a => https://class.admin.tus.ac.jp/up/faces/up/po/pPoa0202A.jsp?fieldId=${a.id});
for (const url of announceURLs) {
console.log(Fetching ${url}...);
const response = await fetchCLASSPage({url, JSESSIONID: sessionId});
console.log(Finish.);
const html = await response.text();
const announceDOM = new DOMParser().parseFromString(html, 'text/html');
const title = announceDOM.getElementById('form1:htmlTitle')
.textContent.replace(/\n/g, ' ').trim();
const sender = announceDOM.getElementById('form1:htmlFrom')
.textContent.replace(/\n/g, ' ').trim();
const main = announceDOM.getElementById('form1:htmlMain')
.innerText.trim();
const fileTable = announceDOM.getElementById('form1:htmlFileTable');
const fileURIs = await Promise.all(fileTable ?
.map(async td => {
const a = document.createElement('a');
a.href = await getFileDataURI({
id: td.firstElementChild.id,
JSESSIONID: sessionId,
});
a.download = td.previousElementSibling
.previousElementSibling.firstElementChild.title;
a.style.display = 'none';
// downloadを実行
a.click();
// 後始末
window.URL.revokeObjectURL(url);
}) : []);
console.log({title, sender, main});
}
async function fetchCLASSPage({url, JSESSIONID}) {
{headers: {Cookie: JSESSIONID=${JSESSIONID},},});
return await fetch(url, {headers: {Cookie: JSESSIONID=${JSESSIONID},},});
}
async function getFileDataURI({id, JSESSIONID}) {
const body = createURLSearchParams({
[${id}.x]: 0,
[${id}.y]: 0,
'form1:htmlParentFormId': '',
'form1:htmlDelMark': '',
'form1:htmlRowKeep': '',
'com.sun.faces.VIEW': document.getElementById('com.sun.faces.VIEW').value,
'form1': 'form1',
});
const response = await fetch(url, {
method: 'POST',
headers: {
Cookie: JSESSIONID=${JSESSIONID},
},
body,
});
const blob = await response.blob();
return window.URL.createObjectURL(blob);
}
function createURLSearchParams(data) {
const params = new URLSearchParams();
Object.keys(data).forEach(key => params.append(key, datakey)); return params;
}
})();
code:js
(async () => {
const sessionId = 'xxx';
const res = await loginCLASS({JSESSIONID: sessionId});
const dom = new DOMParser().parseFromString(await res.text(), 'text/html');
const announces = dom
.getElementById('form1:Poa00201A:htmlParentTable')
.querySelectorAll('td.title a');
.map(a => https://class.admin.tus.ac.jp/up/faces/up/po/pPoa0202A.jsp?fieldId=${a.id});
console.log(announceURLs);
for (const url of announceURLs) {
console.log(Fetching ${url}...);
const response = await fetchCLASSPage({url, JSESSIONID: sessionId});
console.log(Finish.);
const html = await response.text();
const announceDOM = new DOMParser().parseFromString(html, 'text/html');
const title = announceDOM.getElementById('form1:htmlTitle')
.textContent.replace(/\n/g, ' ').trim();
const sender = announceDOM.getElementById('form1:htmlFrom')
.textContent.replace(/\n/g, ' ').trim();
const main = announceDOM.getElementById('form1:htmlMain')
.innerText.trim();
const fileTable = announceDOM.getElementById('form1:htmlFileTable');
.map(td => td.firstElementChild.id) : [];
console.log({title, sender, main, fileIds});
}
async function fetchCLASSPage({url, JSESSIONID}) {
return await fetch(url, {headers: {Cookie: JSESSIONID=${JSESSIONID},},});
}
async function loginCLASS({JSESSIONID}) {
const body = createURLSearchParams({
'form1:htmlUserId': document.getElementById('form1:htmlUserId').value,
'form1:htmlPassword': document.getElementById('form1:htmlPassword').value,
'form1:login.x': 0,
'form1.login.y': 0,
'com.sun.faces.VIEW': document.getElementById('com.sun.faces.VIEW').value,
'form1': 'form1',
});
return await fetch(url, {
method: 'POST',
headers: {
Cookie: JSESSIONID=${JSESSIONID},
},
body,
});
}
function createURLSearchParams(data) {
const params = new URLSearchParams();
Object.keys(data).forEach(key => params.append(key, datakey)); return params;
}
})();
Reference
おんなじシステムをpythonでscrapingしてるコード
AjaxのURLで検索したらたまたま出てきた