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

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

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

一、准备工具

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

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

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

下载然后将之导入工程中

这里写图片描述

二、了解思路

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

这里写图片描述

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

这里写图片描述

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

这里写图片描述

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

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

这里写图片描述

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

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

这里写图片描述

点击右键查看网页源代码,删去一些无用的代码,重点在于中间的表单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;
    }

获取到的数据如下:

这里写图片描述

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

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

四、补充

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

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

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

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

这里写图片描述

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

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

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

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏信安之路

MetInfo 最新版代码审计漏洞合集

最近想给 X 天贡献点插件,时常会去留意 seebug 的最新漏洞列表,发现最近 MetInfo 的漏洞上座率蛮高的,就挑它来代码审计了一波。

941
来自专栏菩提树下的杨过

整理了二个基本的css库(高手请绕道)

前一阵公司唯一的前端开发人员走掉了,短期内也没人顶上来,所以切页面/js这种活儿只能咱自个儿揽过来了,花了大半天捣鼓了下css,整理了二个基本的css库,方便以...

17210
来自专栏java 成神之路

HTTP 方法

3788
来自专栏林德熙的博客

win10 uwp 渲染原理 DirectComposition 渲染 例子创建工程如何写显示CompositionSurfaceBrush

本文来告诉大家一个新的技术DirectComposition,在 win7 之后(实际上是 vista),微软正在考虑一个新的渲染机制。

591
来自专栏沈唁志

整合ThinkPHP功能系列之PHPExcel生成Excel表格文件

35419
来自专栏菩提树下的杨过

FluorineFx:远程共享对象(Remote SharedObjects)

单纯从客户端上来看,FluorineFx的RSO跟FMS中的RSO几乎没什么不同(参见Flash/Flex学习笔记(15):FMS 3.5之远程共享对象(Rem...

1826
来自专栏Windows Community

New Windows 10 SDK - Toast Notification

概述 Toast Notification 在 UWP App 中有很重要的作用,能够很大程度上增强 App 和用户之间的沟通,比如运营推广活动、版本更新、提醒...

3347
来自专栏编舟记

一步步编写SonarQube Plugin

插件确实不好写,因为插件是插入庞大的系统当中工作的,那也就意味着写插件需要具备一定的领域知识,包括系统架构、扩展点、业务共性及差异、API及其业务模型对应、安装...

1342
来自专栏Android 研究

OkHttp源码解析(十) OKHTTP中连接与请求及总结

主要看下ConnectInterceptor()方法,里面代码已经很简单了,受限了通过streamAllocation的newStream方法获取一个流(Htt...

1193
来自专栏XAI

微信企业号回调模式配置讲解 Java Servlet+Struts2版本 echostr校验失败解决

异常java.security.InvalidKeyException:illegal Key Size 也就是 echostr校验失败,请您检查是否正确解密并...

20310

扫码关注云+社区