本文共 12705 字,大约阅读时间需要 42 分钟。
你有没有想过自己来为一个网站做一个手机客户端呢?
想要设计一个客户端,一般来说都需要实现模拟登陆功能,这样才能获取用户的个人信息,不然都直接通过手机浏览器网页来访问的话,效果不好且界面不友好这里来模拟登陆我学校的图书馆,平台为安卓系统
需要用到的工具库有两个:
2.android-async-http
android-async-http是一个强大的网络请求库,这个网络请求库是基于Apache HttpClient库之上的一个异步网络请求处理库,网络处理均基于Android的非UI线程,通过回调方法处理请求结果下载然后将之导入工程中
我学校的图书馆网址是:
个人账号登录网址是:
正常来说我应该是在本界面登录的,输入学号、密码、验证码等信息,不过鼓捣了一下网站,发现了网站有个隐藏的登录页面:
根据网址名可以判断出该页面应该是在开发网站时用来测试的而这个页面居然不需要验证码=_=!
这样就省事很多了现在就开始来研究下如何实现模拟登录,看看需要向服务器发送什么信息
用谷歌浏览器打开登录页,按F12键,点击Application标签,查看Cookie图片箭头所指向的值即为当前用户的Cookie值,每次打开该页面,该值应该都是不同的,即用来唯一标示每位用户
转到Network标签,查看访问信息
箭头所指即为请求头,后边需要用到 可以看到请求头中的一项为Cookie点击右键查看网页源代码,删去一些无用的代码,重点在于中间的表单form
form标签中的action="test.aspx"
意思是提交的数据需要Post给谁,这里是直接Post到本页面即可
模拟登录的过程简单来说,即用户首先访问登录页,获得了Cookie值,然后输入数据将每一项input数据Post给服务器,如果登录成功,则之后访问个人信息页面只需要带上Cookie值即可,因为此时服务器已经知道该Cookie对应哪位用户了
现在就来正式敲代码实现模拟登陆了
为了简化网络请求操作,我简单封装了Get和Post操作
/** * Get和Post操作的简单封装 * Created by ZY on 2016/10/29. */public class NetAPI { /** * Get操作 * * @param client client * @param url url * @param charset 编码格式 * @param callback 回调函数 */ public static void HttpGet(AsyncHttpClient client, String url, String charset, final NetCallback callback) { client.get(url, new TextHttpResponseHandler(charset) { @Override public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { if (callback != null) { callback.onFailure("状态码:" + statusCode); } } @Override public void onSuccess(int statusCode, Header[] headers, String responseString) { if (callback == null) { return; } if (statusCode == HttpStatus.SC_OK) { callback.onSuccess(headers, responseString); } else { callback.onFailure(""); } } }); } /** * Post操作 * * @param client client * @param url url * @param params 请求参数 * @param charset 编码格式 * @param callback 回调函数 */ public static void HttpPost(AsyncHttpClient client, String url, RequestParams params, String charset, final NetCallback callback) { client.post(url, params, new TextHttpResponseHandler(charset) { @Override public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { if (callback != null) { callback.onFailure("状态码:" + statusCode); } } @Override public void onSuccess(int statusCode, Header[] headers, String responseString) { if (callback == null) { return; } if (statusCode == HttpStatus.SC_OK) { callback.onSuccess(headers, responseString); } else { callback.onFailure(""); } } }); }}
用到的回调函数
/** * 回调函数 * Created by ZY on 2016/10/29. */public interface NetCallback { void onFailure(String response); void onSuccess(Header[] headers, String response);}
新建一个LibraryAPI类,采用单例模式
//图书馆登录 private final static String LIBRARY_LOGIN_URL = "http://lib.wyu.edu.cn/opac/test.aspx"; //图书馆个人信息 private final static String USER_INFO_URL = "http://lib.wyu.edu.cn/opac/user/userinfo.aspx"; //当前借书 private final static String BOOK_BORROWED_URL = "http://lib.wyu.edu.cn/opac/user/bookborrowed.aspx"; //借书历史 private final static String BOOK_BORROWED_HISTORY_URL = "http://lib.wyu.edu.cn/opac/user/bookborrowedhistory.aspx?page="; private AsyncHttpClient client; private static LibraryAPI libraryAPI; //私有化构造函数 private LibraryAPI(Context context) { client = new AsyncHttpClient(); PersistentCookieStore cookieStore = new PersistentCookieStore(context); cookieStore.clear(); client.setCookieStore(cookieStore); //设置请求头 client.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); client.addHeader("Accept-Encoding", "gzip, deflate, sdch"); client.addHeader("Accept-Language", "zh-CN,zh;q=0.8"); client.addHeader("Cache-Control", "max-age=0"); client.addHeader("Connection", "Keep-Alive"); client.addHeader("Host", "lib.wyu.edu.cn"); client.addHeader("Referer", "http://lib.wyu.edu.cn/opac/search.aspx"); client.addHeader("Upgrade-Insecure-Requests", "1"); client.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"); } /** * 获取实例 * * @param context 上下文 * @return 实例 */ public static LibraryAPI getInstance(Context context) { if (libraryAPI == null) { libraryAPI = new LibraryAPI(context); } return libraryAPI; }
当中的请求头根据谷歌浏览器中查看的数据来设置即可,有些请求头不是必须的,不过为了省事就全带上了
需要注意的是以下一步:
PersistentCookieStore cookieStore = new PersistentCookieStore(context); cookieStore.clear(); client.setCookieStore(cookieStore);
即将Cooki保存到本地,即Cookie持久化,每次重新调用时清理本地Cookie
之后就是来登录图书馆了
/*** * 登录图书馆 * * @param studentID 学号 * @param password 密码 * @param callback 回调函数 */ public void login(String studentID, String password, final NetCallback callback) { //添加请求参数 final RequestParams params = new RequestParams(); params.add("__EVENTTARGET", ""); params.add("__EVENTARGUMENT", ""); params.add("__VIEWSTATE", "/wEPDwUKLTY2Njg2ODg0Mw9kFgICAw8WAh4GdGFyZ2V0BQZfYmxhbmsWBAIDD2QWBGYPD2QWAh4MYXV0b2NvbXBsZXRlBQNvZmZkAgQPDxYCHgRUZXh0ZWRkAgUPZBYGZg8QZGQWAWZkAgEPEGRkFgFmZAICDw9kFgIfAQUDb2ZmZGTcY8B98vBh8r3/5k/FWW0LQrvmCw=="); params.add("txtlogintype", "0"); params.add("btnLogin_Lib", "登录"); params.add("__EVENTVALIDATION", "/wEWBQK2h7H9DgLxkMjADwLfkqekBgLN05+fBgKe9OnfBhxoxkpnmMwQ62JlcaByWkgdRCZp"); params.add("txtUsername_Lib", studentID); params.add("txtPas_Lib", password); NetAPI.HttpGet(client, LIBRARY_LOGIN_URL, "UTF-8", new NetCallback() { @Override public void onFailure(String response) { callback.onFailure(response + " 获取图书馆登录页失败"); } @Override public void onSuccess(Header[] headers, String response) { NetAPI.HttpPost(client, LIBRARY_LOGIN_URL, params, "UTF-8", new NetCallback() { @Override public void onFailure(String response) { callback.onFailure(response + " 登录图书馆失败"); } @Override public void onSuccess(Header[] headers, String response) { for (Header header : headers) { //Post后返回的headers中必须含有该header,才证明Post成功 if (header.getName().equals("Set-Cookie")) { callback.onSuccess(headers, response); return; } } callback.onFailure("登录图书馆失败"); } }); } }); }
首先需要以Get方式访问登录页,此时Cookie值会被自动保存下来,Get成功后就可以来向登录页Post学号、密码等数据了,这些数据都保存在请求参数RequestParams当中
有些请求参数的名与值都是不变了,我也不知道Post到服务器到底有什么用处~~此时,即使Post成功了,也不代表就登录成功了,用谷歌浏览器来查看,可以发现当登录成功后,返回的Header[]当中会带上一个名为“Set-Cookie”的数据
所以检查返回的Header[]即可知道是否已经登录成功如果登录成功,就可以调用以下方法获取当前借阅书籍列表了
/** * 获取当前借阅情况 * * @param callback 回调函数 */ public void getBookBorrowed(NetCallback callback) { NetAPI.HttpGet(client, BOOK_BORROWED_URL, "UTF-8", callback); }
为了方便,新建一个书籍实体Book
/** * 在查询当前借书情况与借书历史时使用 * Created by ZY on 2016/10/29. */public class Book { /** * 书名 */ private String bookName; /** * 登录号 */ private String id; /** * 借书日期 */ private String borrowDate; /** * 书籍最迟应还日期/书籍还期 */ private String deadline; public Book(String bookName, String id, String borrowDate, String deadline) { this.bookName = bookName; this.id = id; this.borrowDate = borrowDate; this.deadline = deadline; } public String getBookName() { return bookName; } public String getId() { return id; } public String getBorrowDate() { return borrowDate; } public String getDeadline() { return deadline; } @Override public String toString() { return "Book{" + "bookName='" + bookName + '\'' + ", id='" + id + '\'' + ", borrowDate='" + borrowDate + '\'' + ", deadline='" + deadline + '\'' + '}' + "\n"; }}
现在即可来获取书籍借阅列表了,将获取到的数据显示在TextView上
private void getBookList() { final LibraryAPI libraryAPI = LibraryAPI.getInstance(this); libraryAPI.login("填入学号", "填入密码", new NetCallback() { @Override public void onFailure(String response) { tv_content.setText("登录失败"); } @Override public void onSuccess(Header[] headers, String response) { libraryAPI.getBookBorrowed(new NetCallback() { @Override public void onFailure(String response) { tv_content.setText("获取当前书籍借阅情况失败"); } @Override public void onSuccess(Header[] headers, String response) { ListbookList = HtmlParseHelper.parseBookBorrowed(response); if (bookList == null) { tv_content.setText("解析当前书籍借阅情况失败"); return; } if (bookList.size() == 0) { tv_content.setText("当前木有借书"); return; } StringBuilder builder = new StringBuilder(); for (Book book : bookList) { builder.append(book.toString()); } tv_content.setText(builder.toString()); System.out.println(builder.toString()); } }); } }); }
当中需要用到一个工具类HtmlParseHelper,因为服务器返回的是Html代码,需要将之解析为格式友好的数据
/** * 解析当前书籍借阅情况 * * @param html html文件 * @return 书籍列表 */ public static ListparseBookBorrowed(String html) { List bookList; try { Document document = Jsoup.parse(html); Element divElement = document.select("div#borrowedcontent").first(); Element tbodyElement = divElement.getElementsByTag("tbody").first(); Elements trElements = tbodyElement.getElementsByTag("tr"); bookList = new ArrayList<>(); Elements tdElements; Book book; String bookName; String id; String borrowDate; String deadline; for (Element trElem : trElements) { tdElements = trElem.getElementsByTag("td"); bookName = tdElements.get(2).text(); id = tdElements.get(5).text(); borrowDate = tdElements.get(6).text(); deadline = tdElements.get(1).text(); book = new Book(bookName, id, borrowDate, deadline); bookList.add(book); } } catch (Exception e) { return null; } return bookList; }
获取到的数据如下:
数据没错,的确是我当前借的书
按照这方法,就可以获取到所有的个人信息,再设计好看点的UI界面,就可以完成一个图书馆客户端了~
可能有人会说没有验证码的登录页面毕竟是少数,有验证码的页面又该如何操作?
其实即使有验证码,登录操作也就是麻烦了点,也并不难
这里再以我学校的学生服务子系统为例子,网址:
按F12查看Cookie,可以看到当中有一项名为“LogonNumber”的数据,值即为图片验证码当中的数字
这样只要先遍历Cookie值,取出验证码值,在Post时将之加入请求参数即可,也不需要用户来输入验证码值了,简化了操作
如果这样不行的话,也可以查看源代码获取验证码链接,在登录时下载该图片并显示,由用户输入验证码即可
方法有很多,只要多钻研下,总归是能够实现的
代码我已上传到GitHub上:
转载地址:http://nlazx.baihongyu.com/