超級課程表火了有一陣子,最近安裝體驗了下,對裏面自動導入課表這個功能很好奇——不清楚各大教學平臺網站的API情況下,怎麼獲得相應數據?
網上搜了下,找到這篇博文,講解很詳盡,做個收藏。
主要工作是抓包,然後再對數據報進行分析。
首先需要準備的工具是HttpWatch,這是抓包需要的工具,然後還有一個jar包,叫Jsoup,這是用來解析網頁HTML代碼的。其次所以要的類是HttpClient、HttpPost、HttpGet。
先來看看最後的效果圖,我實現了獲取教務平臺的考試成績和考試座位安排的數據,課程表數據一樣的原理獲取。
關於android的基本知識及我所用的相應組件就不介紹了,直接開始正文了。
一、使用HttpWatch抓取教務平臺的數據。
安裝好HttpWatch後,打開IE瀏覽器,打開HttpWatch,先別點記錄,因爲還沒有進入教務平臺網站的。 = =
這是我學校的教務平臺網站地址 http://210.43.188.41/ ,進入後,選擇用戶登錄。好的,此時點擊HttpWatch上的記錄。
然後輸入學號密碼, = = 這裏我就打上馬賽克了,如果開發者真的需要用我學校的教務平臺來進行學習,我願意給你我的學號密碼,不過請私下聯繫我。
輸入學號密碼後點擊登錄,等網頁完全加載完畢後點擊記錄邊上的取消,這個時候就要對抓下來的數據進行分析了。接下來的演示可能有點傻瓜制 = = 希望秒懂的人諒解一下像我這樣的新手。
大家可以看到HttpWatch有上下兩塊界面,首先看到上面的界面,找到“方法”爲Post的那行數據,單擊,就可以看到下面的界面出現了相應的內容。首先我們打開POST數據。
大家可以看到有很多參數和數值,但是!!除了我打鉤的這三個參數外,其餘的參數對於我們開發客戶端而言形同虛設。大家可能會問了,在之前那個登陸頁面中,明明有驗證碼需要輸入的啊,但爲什麼在POST數據中,連cCode(驗證碼)這個參數都形同虛設呢?關於這個問題,我問了很多人,可是最終得到的結果是。。。。應該是這個教務平臺的BUG = = 所以大家先別介意沒有輸入驗證碼,我跟大家保證,我們不需要輸入驗證碼,也可以登錄!!!!
好的,在分析完POST數據後,我們點擊另一個選項卡,“頭信息”。
同樣,在衆多發送的頭信息中,我們所需要的只是Cookie,Cookie是什麼?從本質上講,他可以看成你的×××,也就是說你在接下來的網頁操作中,Cookie可以證明操作對象是你而不是別人。好的,關於其餘參數的作用,如果你對抓包很有興趣的話可以繼續深究,但是對我們現在做客戶端已經無用了~
對了,其實還有一個參數還是相當重要的,那就是在HttpWatch中上方的那個頁面中有一列叫做URL,這個就是我們Post或者Get的直接網址,一定要注意!!不然你Post的時候沒有Post到相對應的網站就等於白Post了 = =
第一步基本就已經完成了,就是關於使用HttpWatch抓包和分析數據的事基本就已經搞定了~(不過這只是第一個抓包內容,之後還需要抓包的,就是抓成績或者課表的頁面)。
二、將抓下來的數據運用在代碼中
List<Cookie> cookies; //保存獲取的cookie
HttpClient client = new DefaultHttpClient();
HttpResponse httpResponse;
String uriAPI = "http://210.43.188.41/_data/index_LOGIN.aspx";
/* 建立HTTP Post連線 */
HttpPost httpRequest = new HttpPost(uriAPI);
List<NameValuePair> params = new ArrayList<NameValuePair>();
/**
* 以下三個數據就是我們的之前在POST裏的數據,不用在意驗證碼
*/
params.add(new BasicNameValuePair("PassWord", "*****"); //這裏的密碼我用*取代了
params.add(new BasicNameValuePair("UserID", "201150080223"); //這是學號
params.add(new BasicNameValuePair("Sel_Type", "STU")); //以學生身份登錄
try {
// 發出HTTP request
httpRequest.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
// 取得HTTP response
httpResponse = client.execute(httpRequest); //執行
// 若狀態碼爲200 ok
if (httpResponse.getStatusLine().getStatusCode() == 200) { //返回值正常
// 獲取返回的cookie
cookies = ((AbstractHttpClient) client).getCookieStore().getCookies();
} else {
}
} catch (Exception e) {
e.printStackTrace();
}
上面的代碼應該還是比較易懂的,關於部分不熟悉的類請大家自行閱讀API文檔哈。
第二步的目的一是將三個數據(學號、密碼、登陸身份)Post到教務網站上,另一個是獲取到登陸成功後的cookie。
三、繼續抓包,不過這次是非常有針對性的抓包
之前已經提到了,我們之所以在登陸頁面進行了第一次抓包操作,完全只是爲了登陸成功並且獲取成功後的cookie,這樣,我們才能帶着cookie繼續訪問我們接下來想要訪問的東西。下面我以成績爲例子演示,課程表也是一樣的!!
在教務平臺上找到成績查詢界面並進入。
同樣,先別記錄,你這時可以清除一下你之前抓下的數據 (= = 當然,如果你覺得不妨礙你分析抓下來的數據,你也可以不清除)。清除後,點擊HttpWatch的記錄,然後檢索,這時HttpWatch上又會出現很多很多數據。我們繼續分析。
同樣的,按照第一次抓包的方法,我們先找到POST數據的選項卡。
這些就是我們剛纔Post的數據,反正這些數據我們大體上能理解,比如說sel_xn就是select的學年的意思,sel_xq就是學期,按道理來說這個數據我們都應該在代碼裏Post的,但是經過我自己的嘗試,第一個參數btn_search可以不用post,這個估計就是我們點擊“檢索”那下所產生的效果。好的,這個時候你可以去看看“頭信息”裏的cookie,你會發現這個cookie和我們之前的那個cookie是一樣的 = = 這是當然的,因爲這樣才能說明從剛開始到現在一直是同一個對象在操作。
第三步的作用很明顯,就是爲了POST一些數據到我們想要查詢的網頁上。
慢着,這裏一個非常非常非常重要的東西沒有講到!第三步其實還有一個作用的,那就是!!!!!大家點擊“POST數據”邊上的那個“內容”選項卡。
看到沒?“內容”裏的數據原來就是我們網頁上成績的HTML源代碼!哈哈,大家懂了吧?其實抓包的目的很純粹的!就是爲了獲取相應的網頁信息。你像什麼QQ農場外掛啥的其實都是抓包的原理喲!
好的,這樣一來,我們大部分的事情就做完了,我們回憶一下之前的操作。
首先是在登陸頁面抓包,從而獲取相應的cookie,接着是在JAVA代碼中實現POST過程,然後我們再進行抓包操作,對查詢成績頁面進行的抓包,我們找到了我們所需要POST的數據和頁面所返回的內容,這樣我們就明朗了,我們只要合理的解析一下獲取的HTML代碼就可以了!不過在此之前,我們先把第三步的操作在JAVA代碼裏實現。
四、在代碼裏實現第三步操作
String result = "";
/* 聲明網址字符串 */
String uriAPI = "http://210.43.188.41/xscj/Stu_MyScore_rpt.aspx"; //這個網站之前說了,查看HttpWatch裏相應的URL
/* 建立HttpPost聯機 */
HttpPost httpRequest = new HttpPost(uriAPI);
List<NameValuePair> params = new ArrayList<NameValuePair>();
/**
* 以下六個參數必須要用
*/
params.add(new BasicNameValuePair("sel_xn", "2011")); // 學年
params.add(new BasicNameValuePair("SelXNXQ", "2"));
params.add(new BasicNameValuePair("SJ", "1"));
params.add(new BasicNameValuePair("sel_xq", term)); // 學期:0 第一學期,1 第二學期
params.add(new BasicNameValuePair("zfx_flag", "0"));
params.add(new BasicNameValuePair("zfx", "0"));
try {<span style="font-family: Arial, Helvetica, sans-serif;">//把之前的cookie放到此次POST所需要的頭信息中</span>
httpRequest.setHeader("Cookie","ASP.NET_SessionId="+ cookies.get(0).getValue());
httpRequest.setEntity(new UrlEncodedFormEntity(params2, HTTP.UTF_8));
/* 發出HTTP request */
HttpResponse httpResponse2 = new DefaultHttpClient().execute(httpRequest3);
/* 若狀態碼爲200 ok */
if (httpResponse2.getStatusLine().getStatusCode() == 200) {
<span style="white-space:pre"> </span>//接下來的代碼是爲了把從網頁獲取到的內容讀出來
StringBuffer sb = new StringBuffer();
HttpEntity entity = httpResponse2.getEntity();
InputStream is = entity.getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "GB2312"));
//是讀取要改編碼的源,源的格式是GB2312的,安源格式讀進來,然後再對源碼轉換成想要的編碼就行
String data = "";
while ((data = br.readLine()) != null) {
sb.append(data);
}
result = sb.toString(); //此時result中就是我們成績的HTML的源代碼了
} else {
}
} catch (Exception e) {
e.printStackTrace();
}
老規矩,對於一些大家不熟悉的類依舊自行查看API文檔。
五、使用Jsoup解析獲取的HTML代碼
好樣的,做完第四步的時候其實我們已經基本完成了百分之80的操作了,用Jsoup解析說難不難,說易不易,我這裏就只是把解析我所需要的內容進行一下講解。
先上代碼。
private String filterHtml(String source) {
if (null == source) {
return"";
}
StringBuffer sff = new StringBuffer();
String score[];
int i = 0, j = 0;
String html = source;
Document doc = Jsoup.parse(html); //把HTML代碼加載到doc中
Elements links_class = doc.select("td[width=23%]"); // 這是課程名,因爲課程名的HTML標籤事<td width=23% align=left>,然後我發現<span style="font-family: Arial, Helvetica, sans-serif;">width=23%是這個標籤特有的,所以我就把它給提出來了</span>
Elements links_grade = doc.select("td[width=5%]"); // 這是分數,原因同上
score = new String[links_grade.size()];
for (Element link_grade : links_grade) {
score[i++] = link_grade.text();
}
for (Element link : links_class) {
sff.append(link.text()).append(" : ").append(score[j]).append("\n");
j = j+2; //這裏之所以+2是因爲分數的標籤是<td width=5% align=right>,而學分也是這樣的標籤,所以我就每提取一次分數標籤跳過一次學分標籤
}
html = sff.toString();
return html;
}