HttpClient + Jsoup模拟登录教务处并获取课表
1、概述
最近想做一个校园助手类的APP,由于第一次做,所以打算先把每个功能单独实现,防止乱了阵脚。利用教务处登录获取课表和成绩等是一个基本功能,所以以获取课表为例实现了这个功能。完整代码点这里,尝试了好几次的,所以写的比较乱。
2、涉及的关键知识
首先,明确获取课表的流程:其实,获取课表就是让手机模拟浏览器,给服务器传去账号、密码,然后服务器会返回cookies(不懂自行百度),利用cookie就可以穿梭自如了,比如查课表。但是,浏览器登录时,返回的html文件浏览器是会自动解析成网页展现在我们面前的,但是APP就不行了,所以需要我们自己从html字符流中解析出我们需要的信息。就我自己的情况来看,模拟登录花了不少时间(这方面很不熟),解析html很快就解决了(可能是之前实践偶解析JSON)。
这样一个APP主要涉及到3个重要知识点:
1、如何模拟登录:java有自己的HttpURLConnection,但是不够灵活,所以用 apache的HttpClient更方便
2、如何解析HTML:网上查一下,Jsoup很合适。
3、AsyncTask:显然获取网页并解析是不能再主线程进行的,所以需要使用异步任务。
4、回调函数:这个不是必须的,但是我为了降低程序耦合度把网络请求那部分单独成了一个类,这时候更新主UI就需要利用回调函数了,之后细说。
3、模拟登陆(以窝工UPR为例)
1、解析教务处登录界面,看到一些教程用的还是HttpWatch,确实过时了,显然直接Google浏览器F12就行了。如下图,选择Network

点一下登录,就可以看到一大堆信息,我们需要的是第一个:

点击就可以看到详细信息了:

重要的有两个,Cookie,拿到这个我们才能导出去访问,Form Data就是模拟登录时要提交(POST)的表单数据;
4、HTML解析
利用上一步模拟登录拿到的Cookie就可以去获取课表了,同理,需要解析网页,如下,点击我的课表

可以看到一大堆信息,找到需要的

这个Response就是会返回我们的字符流,我们需要的就是解析它,设计到Jsoup的详细使用就不细说了,自己也是今天才接触,
总之借助以上两步就可以写出主要类Login.java的代码了,如下:
1 package com.example.upr.net;
2
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.InputStreamReader;
7 import java.io.UnsupportedEncodingException;
8 import java.util.ArrayList;
9 import java.util.List;
10
11 import org.apache.http.HttpEntity;
12 import org.apache.http.HttpResponse;
13 import org.apache.http.NameValuePair;
14 import org.apache.http.client.ClientProtocolException;
15 import org.apache.http.client.HttpClient;
16 import org.apache.http.client.entity.UrlEncodedFormEntity;
17 import org.apache.http.client.methods.HttpGet;
18 import org.apache.http.client.methods.HttpPost;
19 import org.apache.http.cookie.Cookie;
20 import org.apache.http.impl.client.AbstractHttpClient;
21 import org.apache.http.impl.client.DefaultHttpClient;
22 import org.apache.http.message.BasicNameValuePair;
23 import org.jsoup.Jsoup;
24 import org.jsoup.nodes.Document;
25 import org.jsoup.nodes.Element;
26 import org.jsoup.select.Elements;
27
28 import com.example.upr.CompleteListener;
29
30 import android.R.integer;
31 import android.os.AsyncTask;
32 import android.provider.ContactsContract.Contacts.Data;
33 import android.util.Log;
34 import android.widget.Toast;
35
36 public class Login extends AsyncTask<Void, Void, String[][]>{
37
38 private String zjh;
39 private String mm;
40 private String webPage;
41 private String [][] kebiao = new String[15][8];
42 private CompleteListener listener;
43 public Login(String account, String password, CompleteListener listener) {
44 zjh = account;
45 mm = password;
46
47 this.listener = listener;
48 }
49 @Override
50 protected String[][] doInBackground(Void... arg0) {
51 List<NameValuePair> list = new ArrayList<NameValuePair>();
52 list.add(new BasicNameValuePair("zjh", zjh));
53 list.add(new BasicNameValuePair("mm", mm));//表单信息
54 String encode = "gb2312";//编码格式,从F12工具中可以找到
55 HttpClient httpClient = new DefaultHttpClient();
56 List<Cookie> cookies;
57 try {
58 HttpEntity entity = new UrlEncodedFormEntity(list ,encode);//封装数据
59 HttpPost post = new HttpPost(com.example.upr.Constant.LOGIN_URL);//建立POST请求
60 post.setEntity(entity);
61 HttpResponse httpResponse = httpClient.execute(post);
62 String data, result;
63 if (httpResponse.getStatusLine().getStatusCode() == 200) {//登录成功
64 cookies = ((AbstractHttpClient) httpClient).getCookieStore().getCookies();//获取Cookie
65 HttpGet httpGet = new HttpGet("http://zhjw.dlut.edu.cn/xkAction.do?actionType=6");
66 httpGet.setHeader("Cookie", "JSESSIONID="+ cookies.get(0).getValue()+";"+
67 "NSC_kjbpxv-iuuq="+cookies.get(1).getValue());//注意分号
68 httpResponse = new DefaultHttpClient().execute(httpGet);
69
70 StringBuffer sb = new StringBuffer();
71 entity = httpResponse.getEntity();
72 InputStream inputStream = entity.getContent();
73 BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, encode));
74 while ((data = br.readLine()) != null) {
75 sb.append(data); //读取返回的网页
76 }
77 result = sb.toString();
78 webPage = result;
79
80 } else {
81 return null;
82 }
83 } catch (UnsupportedEncodingException e) {
84 e.printStackTrace();
85 return null;
86 } catch (ClientProtocolException e) {
87 // TODO Auto-generated catch block
88 e.printStackTrace();
89 return null;
90 } catch (IOException e) {
91 // TODO Auto-generated catch block
92 e.printStackTrace();
93 return null;
94 }
95
96 Document document = Jsoup.parse(webPage);
97 if (document != null) {
98 Element element = document.getElementById("user");//通过id直接定位到数据
99 Elements trElements = element.select("tr");
100 //以下是提取课表信息,每个网站不同,一下代码要解析的html放在最下面了
101 for (int i = 0; i < trElements.size(); i++) {
102 Elements tdElements = trElements.get(i).select("td");
103 for (int j = 0; j < tdElements.size(); j++) {
104 String text = tdElements.get(j).text();
105 if (tdElements.size() == 1) {
106 continue;
107 } else if (tdElements.size()==9) {
108 if (j != 0) {
109 kebiao[i][j-1] = text;
110 }
111 }else {
112 kebiao[i][j] = text;
113 }
114
115 }
116 }
117 return kebiao;
118 } else {
119 return null;
120 }
121
122 }
123 @Override
124 protected void onPostExecute(String[][] result) {
125 super.onPostExecute(result);
126 listener.onConnectFinish(result);//执行回调函数,更新主UI
127 }
128 }
129 /*
130 <table cellpadding="0" width="100%" class="displayTag" cellspacing="0" border="1" id="user">
131 <tr>
132 <td colspan="2" class="sortable"> </td>
133 <td width="13%" class="sortable">
134 <div align="center">星期一</div></td>
135 <td width="13%" class="sortable">
136 <div align="center">星期二</div></td>
137 <td width="13%" class="sortable">
138 <div align="center">星期三</div></td>
139 <td width="13%" class="sortable">
140 <div align="center">星期四</div></td>
141 <td width="13%" class="sortable">
142 <div align="center">星期五</div></td>
143 <td width="13%" class="sortable">
144 <div align="center">星期六</div></td>
145 <td width="13%" class="sortable">
146 <div align="center">星期日</div></td>
147 </tr>
148 <tr bgcolor="#FFFFFF">
149 <td width="3%" rowspan="4">
150 <p class="style4">上午</p></td>
151 <td width="11%">第1节(08:00-08:45)</td>
152 <td> 计算机网络A_01(西部校区综合教学2号楼B406)
153 <br></td>
154 <td> </td>
155 <td> </td>
156 <td> 编译原理_01(校部综合教学1号楼综151)
157 <br></td>
158 <td> 计算机组成原理_01(西部校区综合教学2号楼B103)
159 <br></td>
160 <td> </td>
161 <td> </td></tr>
162 <tr bgcolor="#FFFFFF">
163 <td width="11%">第2节(08:50-09:35)</td>
164 <td> 计算机网络A_01(西部校区综合教学2号楼B406)
165 <br></td>
166 <td> </td>
167 <td> </td>
168 <td> 编译原理_01(校部综合教学1号楼综151)
169 <br></td>
170 <td> 计算机组成原理_01(西部校区综合教学2号楼B103)
171 <br></td>
172 <td> </td>
173 <td> </td></tr>
174 <tr bgcolor="#FFFFFF">
175 <td width="11%">第3节(10:05-10:50)</td>
176 <td> </td>
177 <td> 面向对象程序设计_01(西部校区综合教学2号楼A301)
178 <br></td>
179 <td> </td>
180 <td> 人工智能_01(西部校区综合教学2号楼A101)
181 <br></td>
182 <td> </td>
183 <td> </td>
184 <td> </td></tr>
185 <tr bgcolor="#FFFFFF">
186 <td width="11%">第4节(10:55-11:40)</td>
187 <td> </td>
188 <td> 面向对象程序设计_01(西部校区综合教学2号楼A301)
189 <br></td>
190 <td> </td>
191 <td> 人工智能_01(西部校区综合教学2号楼A101)
192 <br></td>
193 <td> </td>
194 <td> </td>
195 <td> </td>
196 <tr bgcolor="#FFFFFF">
197 <td colspan="9">
198 <p align="center" class="td2 style5">
199 <strong>午 休</strong></p>
200 </td>
201 </tr>
202 </tr>
203 <tr bgcolor="#FFFFFF">
204 <td width="3%" rowspan="4">
205 <p class="style4">下午</p></td>
206 <td width="11%">第5节(13:30-14:15)</td>
207 <td> 编译原理_01(校部综合教学1号楼综151)
208 <br></td>
209 <td> 计算机组成原理_01(西部校区综合教学2号楼B103)
210 <br></td>
211 <td> </td>
212 <td> 计算机网络A_01(西部校区综合教学2号楼B406)
213 <br></td>
214 <td> </td>
215 <td> </td>
216 <td> </td></tr>
217 <tr bgcolor="#FFFFFF">
218 <td width="11%">第6节(14:20-15:05)</td>
219 <td> 编译原理_01(校部综合教学1号楼综151)
220 <br></td>
221 <td> 计算机组成原理_01(西部校区综合教学2号楼B103)
222 <br></td>
223 <td> </td>
224 <td> 计算机网络A_01(西部校区综合教学2号楼B406)
225 <br></td>
226 <td> </td>
227 <td> </td>
228 <td> </td></tr>
229 <tr bgcolor="#FFFFFF">
230 <td width="11%">第7节(15:35-16:20)</td>
231 <td> 人工智能_01(西部校区综合教学2号楼A101)
232 <br></td>
233 <td> </td>
234 <td> </td>
235 <td> </td>
236 <td> 面向对象程序设计_01(西部校区综合教学2号楼A301)
237 <br></td>
238 <td> </td>
239 <td> </td></tr>
240 <tr bgcolor="#FFFFFF">
241 <td width="11%">第8节(16:25-17:10)</td>
242 <td> 人工智能_01(西部校区综合教学2号楼A101)
243 <br></td>
244 <td> </td>
245 <td> </td>
246 <td> </td>
247 <td> 面向对象程序设计_01(西部校区综合教学2号楼A301)
248 <br></td>
249 <td> </td>
250 <td> </td>
251 <tr bgcolor="#FFFFFF">
252 <td colspan="9">
253 <p align="center" class="td2 style5">
254 <strong>晚 饭</strong></p>
255 </td>
256 </tr>
257 </tr>
258 <tr bgcolor="#FFFFFF">
259 <td width="3%" rowspan="4">
260 <p class="style4">晚上</p></td>
261 <td width="11%">第9节(18:00-18:45)</td>
262 <td> 社会学*_886(校部综合教学1号楼综152)
263 <br></td>
264 <td> 人类文明史*_887(校部材料馆材101)
265 <br></td>
266 <td> </td>
267 <td> </td>
268 <td> </td>
269 <td> </td>
270 <td> </td></tr>
271 <tr bgcolor="#FFFFFF">
272 <td width="11%">第10节(18:55-19:40)</td>
273 <td> 社会学*_886(校部综合教学1号楼综152)
274 <br></td>
275 <td> 人类文明史*_887(校部材料馆材101)
276 <br></td>
277 <td> </td>
278 <td> </td>
279 <td> </td>
280 <td> </td>
281 <td> </td></tr>
282 <tr bgcolor="#FFFFFF">
283 <td width="11%">第11节(19:50-20:35)</td>
284 <td> 社会学*_886(校部综合教学1号楼综152)
285 <br></td>
286 <td> 人类文明史*_887(校部材料馆材101)
287 <br></td>
288 <td> </td>
289 <td> </td>
290 <td> </td>
291 <td> </td>
292 <td> </td></tr>
293 <tr bgcolor="#FFFFFF">
294 <td width="11%">第12节(20:45-21:30)</td>
295 <td> </td>
296 <td> </td>
297 <td> </td>
298 <td> </td>
299 <td> </td>
300 <td> </td>
301 <td> </td></tr>
302 </table>
303
304 */
View Code
5、关于回调函数的使用
这一部分之前完全没预料到,只是后来才发现,不用内部类,想更新UI就得用回调函数,其更深层次的思想是一种设计模式,还是自己学的太少!知识使用完全浮于表面。
6、Demo演示

7、总结
零零散散花了不少时间,一来确实第一次做类似工作,二来自己习惯非常不好,对于新工具,研究API和即学即用能力不强。明天应该好好研究一下HttpClient和Jsoup,达到熟练抓取各种网页的目的!毕竟这只是开始!现在的学习状态实在太低效了。
以上。