Android登入用戶端,驗證碼的擷取,網頁資料抓取與解析,HttpWatch基本使用
大家好,我是M1ko。在互連網時代的今天,如果一個App不接入互連網,那麼這個App一定不會有長時間的生命週期,因此Android網路編程是每一個Android開發人員必備的技能,博主是在校大學生,自學Android一年半多,正好通過一個類比登入校園網軟體,來給大家示範如何在網頁上抓取我們想要的資料,以及將資料Post給伺服器。如果有什麼錯誤或改進歡迎大家指正=-= ,如果想交流博主qq 136057505
好的廢話不多說看一下我們的重點
Httpwatch等軟體抓取Post請求
如何擷取驗證碼
使用Jsoup解析資料
Ok首先上項目Github網址:https://github.com/MikoGodZd/LoginNwuWeb1.git
下面是軟體的大家心裡能有項目的大體架構
一、Http基礎
有Http基礎的朋友都知道,我們是通過Get 與Post請求與伺服器進行互動的,Get顧名思義就是擷取資訊,Post就是想伺服器發請求,但是Post也可以用來擷取資訊並且比Get有很多優勢,我們這裡就是使用的Post。Java中有很多方式與伺服器進行串連,常見的有HttpUrlCollection,HttpClient。兩者的優缺點:
HttpUrlCollection優點:
- 輕量,拓展性強
- 節省資源
缺點:
- 代碼量大複雜
HttpClient優點:
- 便捷,代碼簡單
缺點:
-拓展性不足,耗費資源多
實際上在Android6.0中Google已經刪除了HttpClient的API官方理由是耗電量大,但是HttpCollection的代碼量實在是大,因此我們還是使用了HttpClient,只需在build.gradle中添加以下語句
android {
useLibrary 'org.apache.http.legacy'
}
即可。
二、伺服器位址的擷取 ##
我們要向伺服器Post資料要知道Post的地址是誰,這就要用我們的抓包軟體了,博主使用的是HttpWatch與Firefox的FireBug,兩個軟體的功能相互補充,兩個軟體基本的使用方法大家自行Google(百度,嘿嘿嘿=-=)
1.驗證碼的地址
ok首先開啟我們的校園網登入網頁:
http://jwxt.nwu.edu.cn/%28dlxrmg55j21wlaqv2z5rcdyi%29/Default2.aspx
我們點擊IE 中HttpWatch Record按鈕,然後重新整理這個網站我們可以看到如下的介面
我們能夠看到很多Get請求這些Get請求就能夠擷取開啟的介面的各個元素,右鍵G右方的網址,選擇Open in the new Tab就可以看到各個元素,細心(累死=-=)的尋找之後我們發現驗證碼的網址是
http://jwxt.nwu.edu.cn/%28dlxrmg55j21wlaqv2z5rcdyi%29/CheckCode.aspx 開啟後如顯示
2.登陸post地址
獲得驗證碼的地址之後,我們還要獲得登陸伺服器的地址,還是使用HttpWatch,我們輸入進去帳號密碼以及驗證碼之後,單擊登入開始抓取
開啟下面的POST DATA就可以看到我們發送的資料以及他們的類別
__VIEWSTATE
Button1
hidPdrs
hidsc
lbLanguag
RadioButtonLi
TextBox2 密碼
txtSecretCode 驗證碼
txtUserName 使用者名稱 這裡不給使用者名稱,有需要的QQ 加我136057505=-=
這些值很關鍵,我們在這裡面可以找到對應的值,如果沒有值那就是空,好的這些我們下面會詳細講,我們還沒有獲得Post地址,右鍵Post這一行Copy即可,ok這樣我們發送Post請求的地址就到手了。
http://jwxt.nwu.edu.cn/%28dlxrmg55j21wlaqv2z5rcdyi%29/Default2.aspx
三、登陸
1.驗證碼圖片的擷取
我們有了各種地址,下面就要登陸了,但是我們知道了帳號和密碼(嘿嘿嘿,就不告訴你們=-=),我們還要知道驗證碼,怎麼獲得圖片呢?上文中我們已經知道了驗證碼的地址了,我們可以Get請求,但之前也說過一般使用Post請求來進行,於是我們上代碼
HttpPost httPost = new HttpPost(VERIFATIONURL); HttpClient client = new DefaultHttpClient(); try { HttpResponse httpResponse = client.execute(httPost); byte[] bytes = new byte[1024]; bytes = EntityUtils.toByteArray(httpResponse.getEntity()); bmVerifation = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); } catch (IOException e) { e.printStackTrace(); }
HttpClient的詳細用法大家自行Google這裡不詳細展開
VERIFATIONURL就是上文中的驗證碼地址,首先建立post請求,再建立一個Httpclient ,然後是HttpResponse即響應,我們擷取的伺服器的傳回值就在通過他擷取,我們建立一個byte數組,首先將響應中獲得的資料轉化為byte數組,然後通過 BitmapFactory.decodeByteArray(bytes, 0, bytes.length);方法將byte數組編譯為bitmap,這時候我們就獲得了驗證碼的Bitmap了,我們能直接ImageView.SetBitmap嗎?當然不能,首先第一點我們在網頁上擷取圖片是一個耗時操作,所以我們不能在主線程中進行,第二點只有在主線程中才能更新UI因此,我們使用Thread+Handler解決方案,上代碼:
private void DoGetVerifation() { new Thread(new Runnable() { @Override public void run() { HttpPost httPost = new HttpPost(VERIFATIONURL); HttpClient client = new DefaultHttpClient(); try { HttpResponse httpResponse = client.execute(httPost); byte[] bytes = new byte[1024]; bytes = EntityUtils.toByteArray(httpResponse.getEntity()); bmVerifation = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); } catch (IOException e) { e.printStackTrace(); } Message msg = new Message(); msg.arg1 = 10; handler.sendMessage(msg); } }).start(); }
主線程中接收到msg之後ivVerifation.setImageBitmap(bmVerifation);這樣我們就得到了驗證碼的圖片並且在主介面上顯示出來了
2.發送Post請求登入
我們目前知道了使用者名稱,密碼,驗證碼,下一步就是要登入了,登陸同樣是個耗時操作,當然也要開啟一個新的線程,這沒什麼好說的了,我們同樣是發送Post請求,上代碼:
private void DoLogin(final String user, final String password, final String verifation) { new Thread(new Runnable() { @Override public void run() { DefaultHttpClient defaultclient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(LOGINURL); HttpResponse httpResponse; //設定post參數 List params = new ArrayList(); params.add(new BasicNameValuePair("__VIEWSTATE", "dDwyODE2NTM0OTg7Oz6ZmvWn7xzjizifHN9MgLoDNTRtjQ==")); params.add(new BasicNameValuePair("Button1", "")); params.add(new BasicNameValuePair("hidPdrs", "")); params.add(new BasicNameValuePair("hidsc", "")); params.add(new BasicNameValuePair("lbLanguage", "")); params.add(new BasicNameValuePair("RadioButtonList1", "%D1%A7%C9%FA")); params.add(new BasicNameValuePair("TextBox2", password)); params.add(new BasicNameValuePair("txtSecretCode", verifation)); params.add(new BasicNameValuePair("txtUserName", user)); //獲得個人主介面的HTML try { httpPost.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8)); httpResponse = defaultclient.execute(httpPost); Log.i("xyz", String.valueOf(httpResponse.getStatusLine().getStatusCode())); if (httpResponse.getStatusLine().getStatusCode() == 200) { StringBuffer sb = new StringBuffer(); HttpEntity entity = httpResponse.getEntity(); MAINBODYHTML = EntityUtils.toString(entity); IsLoginSuccessful(MAINBODYHTML); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }).start(); }
首先是建立post,client response,與前面無異,我們擷取資料的Post請求,比如說擷取驗證碼請求是不需要參數的,但是我們登入需要發送給伺服器 使用者名稱 密碼 驗證碼,於是我們為Post請求設定參數
//設定post參數 List params = new ArrayList(); params.add(new BasicNameValuePair("__VIEWSTATE", "dDwyODE2NTM0OTg7Oz6ZmvWn7xzjizifHN9MgLoDNTRtjQ==")); params.add(new BasicNameValuePair("Button1", "")); params.add(new BasicNameValuePair("hidPdrs", "")); params.add(new BasicNameValuePair("hidsc", "")); params.add(new BasicNameValuePair("lbLanguage", "")); params.add(new BasicNameValuePair("RadioButtonList1", "%D1%A7%C9%FA")); params.add(new BasicNameValuePair("TextBox2", password)); params.add(new BasicNameValuePair("txtSecretCode", verifation)); params.add(new BasicNameValuePair("txtUserName", user)); //獲得個人主介面的HTML try { httpPost.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8)); httpResponse = defaultclient.execute(httpPost); Log.i("xyz", String.valueOf(httpResponse.getStatusLine().getStatusCode())); if (httpResponse.getStatusLine().getStatusCode() == 200) { StringBuffer sb = new StringBuffer(); HttpEntity entity = httpResponse.getEntity(); MAINBODYHTML = EntityUtils.toString(entity); IsLoginSuccessful(MAINBODYHTML); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
這些參數就是我們之前使用HttpWatch擷取的Post data 只有類型沒有值的參數我們設定為“”空,使用httpPost.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));方法將我們設定的param 參數集合給httppost,下面和之前一樣,也是通過Response擷取響應值,只不過這裡返回的不是圖片而是赤果果的String=-=,IsLoginSuccessful這個函數是用來判斷我們是否登入成功,這就要用到我們下面要說的資料解析了。
四、資料解析
1.返回的響應?
我們登陸後會得到伺服器給我們的響應,我們在Firefox的Firebug(HttpWatch也可以個人感覺看響應值Firebug比較方便)上機型一次成功的登陸,看一下他究竟會給我們返回什麼
我們可以知道實際上響應值就是我們的網頁的HTML,如果登陸成功實際上就是返回個人首頁
那麼如果失敗呢?我們類比一次驗證碼錯誤如,可見同樣返回一個HTML
我們注意這一行
<script language='javascript' defer>alert('驗證碼不正確!!');document.getElementById('TextBox2').focus();</script>
這實際上是彈出一個視窗,提示我們驗證碼錯誤,下面我們就會利用裡面不同的提示來判斷我們登陸的狀態
2.使用Jsoup進行資料解析
我們建立之前提過的IsLoginSuccessful函數
private void IsLoginSuccessful(String loginresult) { Document doc = Jsoup.parse(loginresult); Elements alert = doc.select("script[language]"); Elements success = doc.select("a[href]"); Message msg = new Message(); //先判斷是否登入成功,若成功直接退出 for (Element link : success) { //擷取所要查詢的URL,這裡對應地址按鈕的名字叫成績查詢 if (link.text().equals("等級考試查詢")) { Log.i("xyz", "登入成功"); msg.arg1 = 6; handler.sendMessage(msg); return; } } for (Element link : alert) { //重新整理驗證碼 DoGetVerifation(); //擷取錯誤資訊 if (link.data().contains("驗證碼不正確")) { Log.i("xyz", "驗證碼錯誤"); msg.arg1 = 0; handler.sendMessage(msg); } else if (link.data().contains("使用者名稱不可為空")) { Log.i("xyz", "使用者名稱不可為空"); msg.arg1 = 1; handler.sendMessage(msg); } else if (link.data().contains("密碼錯誤")) { Log.i("xyz", "密碼或使用者名稱錯誤"); msg.arg1 = 2; handler.sendMessage(msg); } else if (link.data().contains("密碼不可為空")) { Log.i("xyz", "密碼不可為空"); msg.arg1 = 3; handler.sendMessage(msg); ...... } }
我使用的是一個開源的庫Jsoup進行解析當然還有很多方法,大家自行Google(百度=-=咋老躺槍)
http://www.open-open.com/jsoup/ Jsoup的中文API指南大家可以看一下
首先我們判斷登陸成功,我們在登陸成功的HTML中找到了如下語句
等級考試查詢
我們通過 Elements success = doc.select(“a[href]”);擷取到a href”開頭的資料 然後我們迴圈判斷是否存在等級考試查詢這一項,如果存在,那肯定返回的響應就是我們的個人首頁了,也就是說登陸成功,我們使用Thread+Handler在主線程中Toast提醒使用者
然後我們通過 Elements alert = doc.select(“script[language]”);擷取到script 開頭的語句,同樣迴圈判斷,這裡要注意的是,與a href不同的是,我們要用的是link.data(),前面用的是link.text();
JsoupAPI 這樣寫的
text()擷取常值內容text(String value) 設定常值內容data()擷取資料內容(例如:script和style標籤)
ok我們通過不同的類比操作抓取各種錯誤,這裡不詳寫 五、擷取個人資訊
我們登入到了個人首頁,我們想進一步擷取自己的資訊比如說四六級成績,怎麼辦呢?同樣還是抓取請求
跟擷取驗證碼網址的方法一樣,我們得到了成績表的網址
http://jwxt.nwu.edu.cn/(dlxrmg55j21wlaqv2z5rcdyi)/xsdjkscx.aspx?xh=2014117170&xm=鄒德宏&gnmkdm=N121606
前面http://jwxt.nwu.edu.cn/(dlxrmg55j21wlaqv2z5rcdyi)/是我們訪問的host是不變的,我們要做的就是擷取http://blog.csdn.net/mikogodzd/article/details/xsdjkscx.aspx?xh=2014117170&xm=鄒德宏&gnmkdm=N121606,這個問題困擾了很長時間,後來猛然發現這萬至竟然在前面說的deng’lu’cheng’登陸成功後返回的HTML中,所以還是使用Jsoup進行分析
Document doc = Jsoup.parse(MAINBODYHTML); Elements links = doc.select("a[href]"); StringBuffer sb = new StringBuffer(); for (Element link : links) { //擷取所要查詢的URL,這裡對應地址按鈕的名字叫成績查詢 if (link.text().equals("等級考試查詢")) { sb.append(link.attr("href")); } } GETSCOREURL = sb.toString();
GETSCOREURL就是我們要的後半個地址,然後我們加上前半個,進行Post請求抓取即可
這裡返回的是一個Html的表格
學年 |
學期 |
等級考試名稱 |
准考證號 |
考試日期 |
成績 |
聽力成績 |
閱讀成績 |
寫作成績 |
綜合成績 |
2014-2015 |
2 |
CET4 |
610041151112625 |
201506 |
483 |
167 |
173 |
143 |
0 |
2015-2016 |
1 |
CET6 |
610041152209503 |
201512 |
376 |
126 |
143 |
107 |
0 |
同樣使用Jsoup
private void parse(String parse) { Document doc = Jsoup.parse(parse); Elements trs = doc.select("table").select("tr"); for (int i = 0; i < trs.size(); i++) { Elements tds = trs.get(i).select("td"); for (int j = 0; j < tds.size(); j++) { String text = tds.get(j).text(); score[i][j] = text; Log.i("xyz", score[i][j]); } } }
解析完我們就得到成績的數組了
六、總結
博主大二黨自學Android開發1年半了,現在終於有入門的感覺了,這篇教程對新手來說坑能會有些難,但是一定要沉得住氣,博主剛下載HttpWatch那一會都懵逼了,啥都不懂,慢慢摸索之後終於有了頭緒,好了就這麼些,郵箱交流的朋友加我QQ 136057505=-=