Android 模拟登陆网站实现移动客户端

你有没有想过自己来为一个网站做一个手机客户端呢?
想要设计一个客户端,一般来说都需要实现模拟登陆功能,这样才能获取用户的个人信息,不然都直接通过手机浏览器网页来访问的话,效果不好且界面不友好

这里来模拟登陆我学校的图书馆,平台为安卓系统

一、准备工具

需要用到的工具库有两个:

  1. Jsoup
    jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据

2.android-async-http
android-async-http是一个强大的网络请求库,这个网络请求库是基于Apache HttpClient库之上的一个异步网络请求处理库,网络处理均基于Android的非UI线程,通过回调方法处理请求结果

下载然后将之导入工程中


img_80855100515fa318c15510f4fe0f7706.png
这里写图片描述

二、了解思路

我学校的图书馆网址是:http://lib.wyu.edu.cn/Html/index.Html

img_4950ff06723a1954ef88689c8831540d.png
这里写图片描述

个人账号登录网址是:http://lib.wyu.edu.cn/opac/login.aspx

img_81c26fb10b9e9088aec92c0440166245.png
这里写图片描述

正常来说我应该是在本界面登录的,输入学号、密码、验证码等信息,不过鼓捣了一下网站,发现了网站有个隐藏的登录页面:http://lib.wyu.edu.cn/opac/test.aspx
根据网址名可以判断出该页面应该是在开发网站时用来测试的

img_42e496ebdac79331cd01a2424b0c276c.png
这里写图片描述

而这个页面居然不需要验证码=_=!
这样就省事很多了

现在就开始来研究下如何实现模拟登录,看看需要向服务器发送什么信息
用谷歌浏览器打开登录页,按F12键,点击Application标签,查看Cookie

img_90de99ae9b1aeaf0043ec4cf6b0aea75.png
这里写图片描述

图片箭头所指向的值即为当前用户的Cookie值,每次打开该页面,该值应该都是不同的,即用来唯一标示每位用户

转到Network标签,查看访问信息
箭头所指即为请求头,后边需要用到
可以看到请求头中的一项为Cookie


img_0d57d08ac2c41a7b3ff8ba173b71ff6d.png
这里写图片描述

点击右键查看网页源代码,删去一些无用的代码,重点在于中间的表单form

<form name="form1" method="post" action="test.aspx" onsubmit="javascript:return WebForm_OnSubmit();" id="form1" target="_blank"> 
  
   <div> 
    <input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" /> 
    <input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" /> 
    <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTY2Njg2ODg0Mw9kFgICAw8WAh4GdGFyZ2V0BQZfYmxhbmsWBAIDD2QWBGYPD2QWAh4MYXV0b2NvbXBsZXRlBQNvZmZkAgQPDxYCHgRUZXh0ZWRkAgUPZBYGZg8QZGQWAWZkAgEPEGRkFgFmZAICDw9kFgIfAQUDb2ZmZGTcY8B98vBh8r3/5k/FWW0LQrvmCw==" /> 
   </div> 
   <div id="content"> 
    <input name="txtlogintype" type="hidden" id="txtlogintype" value="0" /> 
    <div class="LoginDiv"> 
     <div class="loginContent"> 
      <div class="in" style="margin-top:8px"> 
       <span class="leftInfo">图书证号:</span> 
       <span class="rightInfo"> 
      <input name="txtUsername_Lib" type="text" id="txtUsername_Lib" class="txtInput" autocomplete="off" style="width:100px;" /><span id="rfv_UserName_Lib" style="color:Red;display:none;">请输入证号</span> </span> 
      </div> 
      <div class="in" style="margin-top:8px"> 
       <span class="leftInfo">密&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;码:</span> 
       <span class="rightInfo"> 
       <input name="txtPas_Lib" type="password" id="txtPas_Lib" class="txtInput" style="width:100px;" /><span id="rfv_Password_Lib" style="color:Red;display:none;">请输入密码</span> </span> 
      </div> 
      <div> 
       <span id="lblErr_Lib"></span> 
      </div> 
      <div style="margin-top:15px"> 
       <input type="submit" name="btnLogin_Lib" value="登录" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;btnLogin_Lib&quot;, &quot;&quot;, true, &quot;&quot;, &quot;&quot;, false, false))" id="btnLogin_Lib" class="multiQuery" /> 
       <input type="button" value="清空" onclick="rset()" class="multiQuery" /> 
      </div> 
     </div> 
    </div> 
   </div> 
   <div> 
    <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWBQK2h7H9DgLxkDwLfkqekBgLN05+fBgKe9OnfBhxoxkpnmMwQ62JlcaByWkgdRCZp" /> 
   </div> 
   
  </form>

form标签中的action="test.aspx"意思是提交的数据需要Post给谁,这里是直接Post到本页面即可
而每一个input标签都是需要提交的数据,name值代表数据名,value即数据值
例如当登录时,提交的所有信息中就有名为“txtUsername_Lib”,值为学号的数据

模拟登录的过程简单来说,即用户首先访问登录页,获得了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) {
                        List<Book> bookList = 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 List<Book> parseBookBorrowed(String html) {
        List<Book> 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;
    }

获取到的数据如下:


img_2b8c8ea94d57912de9d1d5f316af57f0.png
这里写图片描述

数据没错,的确是我当前借的书

按照这方法,就可以获取到所有的个人信息,再设计好看点的UI界面,就可以完成一个图书馆客户端了~

四、补充

可能有人会说没有验证码的登录页面毕竟是少数,有验证码的页面又该如何操作?

其实即使有验证码,登录操作也就是麻烦了点,也并不难

这里再以我学校的学生服务子系统为例子,网址:http://jwc.wyu.edu.cn/student/body.htm

按F12查看Cookie,可以看到当中有一项名为“LogonNumber”的数据,值即为图片验证码当中的数字

img_4584bbab9485bccc0eb6573fd9b7ce41.png
这里写图片描述

这样只要先遍历Cookie值,取出验证码值,在Post时将之加入请求参数即可,也不需要用户来输入验证码值了,简化了操作

如果这样不行的话,也可以查看源代码获取验证码链接,在登录时下载该图片并显示,由用户输入验证码即可

方法有很多,只要多钻研下,总归是能够实现的

代码我已上传到GitHub上:模拟登陆网站实现移动客户端

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值