Android实现模拟登陆教务系统并解析网页数据

前言

     时光飞逝,日月如梭,转眼间四年的大学生活已经结束啦!开始了程序员的加班生活。我的第二学位的毕业设计是实现一个学习小助手。这其中最重要的环节就是模拟登录学校的教务系统,获取到教务系统的数据并解析,用自己的数据库存储,展示在自己设计的界面上。例如课程表我是仿照超级课程表的界面来设计的。废话不多说下面先看看效果。

   

        

抓取教务系统登录时需要传递的参数

     模拟登录之前我们首先需要抓取我们登录时所需要传递的数据,可能有人会想用专业的抓包工具来抓包,其实不用那么复杂,用火狐浏览器或者Chrom浏览器的开发者模式就可以了。博主用的是火狐浏览器抓包,传的一些参数也是按照火狐浏览器来的。

首先,我们来看一下模拟器登录时需要传递的参数及HTTP请求头,我们可以看到请求是post请求,请求头字段基本差不多

传递的参数有这么些



其中这些参数中lt这个是搞的我最头疼的。本以为lt是一个固定值,但是模拟登录的时候发现不对,每次抓包lt是不一样的。最后,只能通过学习JS代码,研究这个lt的取值,最后发现lt及其他参数都隐藏在返回的HTML页面的代码中:(如下图)


搞清楚需要传什么参数和请求的HTTP头了,我们就可以模拟登录教务系统了。

自定义HTTPClient实现模拟登录

实现模拟登录首先要自定义HTTPClient,来模拟浏览器发送HTTP请求。自定义HTTP如下代码:

/**
 * Http请求工具类
 * @author leiqi
 * @date 2017-3-4
 */
public class HttpUtil {
	private static AsyncHttpClient client = new AsyncHttpClient(); // 实例话对象
	// Host地址
	public static final String HOST = "cas.hdu.edu.cn";
	// 基础地址
	public static final String URL_BASE = "http://i.hdu.edu.cn/dcp/xphone/";
	// 验证码地址
	public static final String URL_CODE = "http://cas.hdu.edu.cn/cas/Captcha.jpg";
	// 登陆地址
	public static final String URL_LOGIN = "http://cas.hdu.edu.cn/cas/login?service=http%3a%2f%2fi.hdu.edu.cn%2fdcp%2fxphone%2fm.jsp";
	// 登录成功的首页
	public static String URL_MAIN = "http://i.hdu.edu.cn/dcp/xphone/m.jsp";
	//当前学期课表
	public static String URL_Course= "http://i.hdu.edu.cn/dcp/xphone/kbcx0.jsp";
	//考试安排url
	public static String URl_KSAP = "http://i.hdu.edu.cn/dcp/xphone/ksap.jsp";
	//校园新闻
	public static String Url_XYXW = "http://m.hdu.edu.cn/";
	//上课时间
	public static String Url_Time = URL_BASE+"TimeTable.jsp";
	//我的学费
	public static String Url_WDXF = "http://yxt.hdu.edu.cn/EducationManager/xphone/m.jsp";

	/**
	 * 请求参数
	 */
	public static String captcha = "";//验证码
	public static String encodedService = "http%3a%2f%2fi.hdu.edu.cn%2fdcp%2fxphone%2fm.jsp";
	public static String loginErrCnt = "0";
	public static String lt = "LT-1552500-7P9jMewfE3wH2dWObBuJ";
	public static String password = "19191cf09b99b03ab0df1db04c3840ed";
	public static String username = null;
	public static String service = "http://i.hdu.edu.cn/dcp/xphone/m.jsp";
	public static String serviceName = null;
	public static String ticket = null;
	public static String Cookie = "key_dcp_cas=HHxRYyDMhr0GmDfQ6vgPxXQT8yCsvPSCg0MWBnnnWMGv4dQngpLS!748587538; route=4376efc7edf61c9fe699e82a2fb7a34f" ;
	// 静态初始化
	static {
		client.setTimeout(10000); // 设置链接超时,如果不设置,默认为10s
		// 设置请求头
		client.addHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
		client.addHeader("Accept-Encoding","gzip, deflate");
		client.addHeader("Accept-Language","zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");
		client.addHeader("Connection","keep-alive");
		client.addHeader("Cookie", Cookie);
		client.addHeader("Host", HOST);
		client.addHeader("Referer", URL_LOGIN);
		client.addHeader("User-Agent",
				"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:52.0) Gecko/20100101 Firefox/52.0");
		client.addHeader("Upgrade-Insecure-Requests","1");

	}



	/**
	 * get,用一个完整url获取一个string对象
	 * 
	 * @param urlString
	 * @param res
	 */
	public static void get(String urlString, AsyncHttpResponseHandler res) {
		client.get(urlString, res);
	}

	/**
	 * get,url里面带参数
	 *  @param urlString
	 * @param params
	 * @param res
	 */
	public static void get(String urlString, RequestParams params,
						   AsyncHttpResponseHandler res) {
		client.get(urlString, params, res);
	}

	/**
	 * get,不带参数,获取json对象或者数组
	 * 
	 * @param urlString
	 * @param res
	 */
	public static void get(String urlString, JsonHttpResponseHandler res) {
		client.get(urlString, res);
	}

	/**
	 * get,带参数,获取json对象或者数组
	 * 
	 * @param urlString
	 * @param params
	 * @param res
	 */
	public static void get(String urlString, RequestParams params,
                           JsonHttpResponseHandler res) {
		client.get(urlString, params, res);
	}

	/**
	 * get,下载数据使用,会返回byte数据
	 * 
	 * @param uString
	 * @param bHandler
	 */
	public static void get(String uString, BinaryHttpResponseHandler bHandler) {
		client.get(uString, bHandler);
	}

	/**
	 * post,不带参数
	 * 
	 * @param urlString
	 * @param res
	 */
	public static void post(String urlString, AsyncHttpResponseHandler res) {
		client.post(urlString, res);
	}

	/**
	 * post,带参数
	 * 
	 * @param urlString
	 * @param params
	 * @param res
	 */
	public static void post(String urlString, RequestParams params,
                            AsyncHttpResponseHandler res) {
		client.post(urlString, params, res);
	}

	/**
	 * post,不带参数,获取json对象或者数组
	 * 
	 * @param urlString
	 * @param res
	 */
	public static void post(String urlString, JsonHttpResponseHandler res) {
		client.post(urlString, res);
	}

	/**
	 * post,带参数,获取json对象或者数组
	 * 
	 * @param urlString
	 * @param params
	 * @param res
	 */
	public static void post(String urlString, RequestParams params,
                            JsonHttpResponseHandler res) {
		client.post(urlString, params, res);
	}

	/**
	 * post,返回二进制数据时使用,会返回byte数据
	 * 
	 * @param uString
	 * @param bHandler
	 */
	public static void post(String uString, BinaryHttpResponseHandler bHandler) {
		client.post(uString, bHandler);
	}

	/**
	 * 返回请求客户端
	 * 
	 * @return
	 */
	public static AsyncHttpClient getClient() {
		return client;
	}

	/**
	 * 获得登录时所需的请求参数
	 * 
	 * @return
	 */
	public static RequestParams getLoginRequestParams() {
		// 设置请求参数
		RequestParams params = new RequestParams();
		params.add("captcha", captcha);
		params.add("encodedService", encodedService);
		params.add("loginErrCnt", loginErrCnt);
		params.add("lt", lt);
		params.add("password", password);
		params.add("username", username);
		params.add("service", service);
		params.add("serviceName", serviceName);
		return params;
	}

	/**
	 * 获取请求首页参数
	 * @return
	 */
	public static RequestParams gethomeRequestParams(){
		RequestParams params = new RequestParams();
		params.add("ticket",ticket);
		return params;
	}


}
定义完HTTP客户端,我们就需要去模拟登录,当然模拟登录必须要设置CookieStore如下所示:

   /**
     * 初始化Cookie
     */
    private void initCookie(Context context) {
        //必须在请求前初始化
        cookie = new PersistentCookieStore(context);
        HttpUtil.getClient().setCookieStore(cookie);
        httpClient.setCookieStore(cookie);
    }
然后在请求登录页面时候就可以将返回的cookie设置给HTTPClient了。

难到这样就可以了?那那个lt值怎么获取传递过去昵?先贴一段获取lt的代码后面做解释:

/**
     * 获取lt
     */
    private void getlt(){
        Document doc = null;
            doc = Jsoup.parse(GetShouye());
            if (doc != null)
        if (doc != null) {
            Elements login = doc.body().getElementsByClass("login_form");
            Document containerDoc = Jsoup.parse(login.toString());
            Elements ddd = containerDoc.getElementsByTag("input");
            for (Element aaa : ddd) {
                if (aaa.toString().contains("lt")) {
                    String l = aaa.toString();
                    String lt = l.substring(38, l.length() - 4);
                    HttpUtil.lt = lt;
                }
            }
        }
    }

 /**
     * 请求首页
     * @return
     */
    public String GetShouye()
    {
        String result= "";

//    	创建HttpGet或HttpPost对象,将要请求的URL通过构造方法传入HttpGet或HttpPost对象。
        if (urlll == null){
            urlll = HttpUtil.URL_MAIN;
        }
        HttpGet httpRequst = new HttpGet(urlll);

//    	new DefaultHttpClient().execute(HttpUriRequst requst);
        try {
            //使用DefaultHttpClient类的execute方法发送HTTP GET请求,并返回HttpResponse对象。
            HttpResponse httpResponse = httpClient.execute(httpRequst);//其中HttpGet是HttpUriRequst的子类
            if (httpResponse.getStatusLine().getStatusCode() == 200) {
                HttpEntity httpEntity = httpResponse.getEntity();
                result = EntityUtils.toString(httpEntity);//取出应答字符串
                // 一般来说都要删除多余的字符
                result.replaceAll("\r", "");//去掉返回结果中的"\r"字符,否则会在结果字符串后面显示一个小方格
            } else{
                httpRequst.abort();
            }
        }catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            result = e.getMessage().toString();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            result = e.getMessage().toString();
        }
        return result;
    }
所有准备工作做好我们就可以发送登录请求了

 /**
     * 登录
     */
    private void login() {
        HttpUtil.username = username.getText().toString().trim();
        String md5 = "@";
        try {
            md5= MD5Until.GetMD5Code(password.getText().toString().trim());
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.d("md5  ",md5);
        HttpUtil.password = md5;
        //需要时打开验证码注释
        HttpUtil.captcha = secrectCode.getText().toString().trim();
        Log.d("cookie",HttpUtil.Cookie);
        if (TextUtils.isEmpty(HttpUtil.username)
                || TextUtils.isEmpty(HttpUtil.password)) {
            Toast.makeText(getApplicationContext(), "账号或者密码不能为空!",
                    Toast.LENGTH_SHORT).show();
            return;
        }
        final ProgressDialog dialog =CommonUtil.getProcessDialog(BindActivity.this,"正在登录中!!!");
        dialog.show();
        RequestParams params = HttpUtil.getLoginRequestParams();// 获得请求参数
        Log.d("http",params.toString());
        HttpUtil.getClient().setURLEncodingEnabled(true);
        HttpUtil.post(HttpUtil.URL_LOGIN, params,
                new AsyncHttpResponseHandler() {

                    @Override
                    public void onSuccess(int arg0, Header[] arg1, byte[] arg2) {
                        try {
                            String resultContent = new String(arg2, "gb2312");
                            Log.d("Header",arg1.toString());
                            Log.d("resunt",resultContent);
//							List<String> list = Getlt.match(resultContent,"input","name=\"lt\" ");
//							Log.d("list",list.toString());
                            if(linkService.isLogin(resultContent)!=null){
                                String ret = linkService.parseMenu(resultContent);
                                Log.d("cas", "login success:"+ret);
                                GetHerfUrl(resultContent);
                                Toast.makeText(getApplicationContext(),
                                        "登录成功!!!", Toast.LENGTH_SHORT).show();
                                new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        String s = GetShouye();
                                        Log.d("sdfsdaf",s);
                                        String ss = Getksap();
                                        Log.d("qqqqqq",ss);
                                        jump2Main();
                                    }
                                }).start();

                            }else{
                                getCode();
                                Toast.makeText(getApplicationContext(),"账号或者密码错误!!!", Toast.LENGTH_SHORT).show();
                            }

                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        } finally {
                            dialog.dismiss();
                        }

                    }
                    @Override
                    public void onFailure(int arg0, Header[] arg1, byte[] arg2,
                                          Throwable arg3) {
                        Toast.makeText(getApplicationContext(), "登录失败!!!!",
                                Toast.LENGTH_SHORT).show();
                        dialog.dismiss();
                    }
                });
    }
登录成功后会返回一个链接,并不是登录后的教务首页,我们还需要解析提取链接并用GET请求去请求,即可!下面我们就来说一下关于HTML网页数据的解析。

JSOUP对HTML网页数据的解析

       JSOUP是一个非常好用的,用JAVA来抓取网页数据的开源框架。使用起来也非常简单,就是根据HTML内容,先获取BODY====》》》提取字段对应的class====》》》对应的标签ID。我们还是以lt为例来看:

    /**
     * 获取lt
     */
    private void getlt(){
        Document doc = null;
            doc = Jsoup.parse(GetShouye());
            if (doc != null)
        if (doc != null) {
            //获取解析后的body及对应的class——login_form
            Elements login = doc.body().getElementsByClass("login_form");
            Document containerDoc = Jsoup.parse(login.toString());
            //获取到标签id对应的数据
            Elements ddd = containerDoc.getElementsByTag("input");
            for (Element aaa : ddd) {
                //从这些数据中提取出lt
                if (aaa.toString().contains("lt")) {
                    String l = aaa.toString();
                    String lt = l.substring(38, l.length() - 4);
                    HttpUtil.lt = lt;
                }
            }
        }
    }
后面所有的数据获取都是这样,不过后面都是解析table而已。下面觉得重点应该说一下对课程信息的存储。

课程表信息的存储与读取

      课程表信息的存储也是非常麻烦的一件事,当时没想明白,存储完之后发现读到的数据全乱了。

{\"Course_address\":\"第12教研楼405\",\"Course_name\":\"网站规划与设计\",
        \"Course_teacher\":\"李君君\",\"Course_type\":\"必修\",\"Course_week\":\"周一第3,4节{第1-16周}\"},
上面是用JSON数组来存储每节课信息的存储结构,绘制课程表时,根据课表中的字段。如下所示:

public class CourseActivity extends Activity {
    private ArrayList<CourseBean> mCourse;
    private SharedPreferences mShaerPreferences;
    // 每天有多少节课
    private int mMaxCouese;
    // 一共有多少周
    private int mMaxWeek;
    // 现在是第几周
    private int mNowWeek;
    // 左边一节课的高度
    private float mLeftHeight;
    // 左边一节课的宽度
    private float mLeftWidth;

    private TextView mChangeWeek;
    private LinearLayout mLeftNo;
    private LinearLayout mMonday;
    private LinearLayout mTuesday;
    private LinearLayout mWednesday;
    private LinearLayout mThursday;
    private LinearLayout mFirday;
    private LinearLayout mSaturday;
    private LinearLayout mWeekend;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_course);
        // 实例化所有对象
        initCtrl();
        // 初始化数据
        initData();

        // 绘制左边的课程节数
        drawLeftNo();
        // 绘制当前周
        drawNowWeek();
        // 绘制所有课程 其实可以使用redrawAll替代三步
        drawAllCourse();
    }

    /**
     * 实例化所有对象
     */
    private void initCtrl() {
        mChangeWeek = (TextView) findViewById(R.id.changeWeek);
        mLeftNo = (LinearLayout) findViewById(R.id.leftNo);
        mMonday = (LinearLayout) findViewById(R.id.monday);
        mTuesday = (LinearLayout) findViewById(R.id.tuesday);
        mWednesday = (LinearLayout) findViewById(R.id.wednesday);
        mThursday = (LinearLayout) findViewById(R.id.thursday);
        mFirday = (LinearLayout) findViewById(R.id.firday);
        mSaturday = (LinearLayout) findViewById(R.id.saturday);
        mWeekend = (LinearLayout) findViewById(R.id.weekend);
    }

    /**
     * 初始化所有数据
     */
    private void initData() {
        // 初始化课表
        praseJson();
        // 读取配置信息
        readIniFile();

        // 点击选择切换周
        mChangeWeek.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                showChangeWeekDlg(v);
            }
        });
    }

    /**
     * 绘制左边的课程节数
     */
    private void drawLeftNo() {
        mLeftHeight = getResources().getDimension(R.dimen.left_height);
        mLeftWidth = getResources().getDimension(R.dimen.left_width);
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                (int) mLeftWidth, (int) mLeftHeight);
        for (int i = 1; i <= mMaxCouese; i++) {
            TextView tv = new TextView(this);
            tv.setText(i + "");
            tv.setGravity(Gravity.CENTER);
            tv.setTextColor(getResources().getColor(R.color.font));
            tv.setBackgroundResource(R.drawable.boder);
            mLeftNo.addView(tv, lp);
        }
    }

    /**
     * 绘制课表
     *
     * @param ll
     *            绘制课表到哪一个LinearLayout上
     * @param dayOfWeek
     *            绘制的数据来自周几 一二三四五六七
     */
    private void drawCourse(LinearLayout ll, char dayOfWeek) {
        // 删除所有子View
        ll.removeAllViews();
        // 上一节课结束是第几节
        int perCourse = -1;
        for (CourseBean course : mCourse) {
            // 判断是否显示这节课
            // 是不是同一天 是不是这一周
            if (course.getDayOfWeek() != dayOfWeek
                    || !course.inThisWeek(mNowWeek))
                continue;

            // 设置TextView的属性样式
            TextView tv = new TextView(this);
            tv.setText(course.getCourse_name() + "\n@"
                    + course.getCourse_address());
            tv.setBackgroundResource(R.drawable.course);
            tv.setTextColor(getResources().getColor(R.color.course_font_color));
            tv.setTextSize(12);
            // 将数据绑定到TextView上
            tv.setTag(course);
            tv.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    CourseBean tag = (CourseBean) v.getTag();
                    showCouseDetails(tag);
                }
            });

            // 设置TextView的位置
            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                    LayoutParams.MATCH_PARENT,
                    (int) (course.getStep() * mLeftHeight));
            // 说明这节课为第一节课
            if (perCourse == -1) {
                lp.setMargins(1,
                        (int) ((course.getMinCourse() - 1) * mLeftHeight), 1, 0);
                // useHeight = (int) ((course.getMaxCourse()-1) * mLeftHeight);
            } else {
                lp.setMargins(1, (course.getMinCourse() - perCourse - 1)
                        * (int) mLeftHeight, 1, 0);
                // useHeight = useHeight + (course.getMaxCourse() - perCourse -
                // 1)* (int) mLeftHeight;
            }
            perCourse = course.getMaxCourse();
            ll.addView(tv, lp);
        }
    }

    /**
     * 绘制当前周
     */
    private void drawNowWeek() {
        mChangeWeek.setText("第" + mNowWeek + "周");
    }

    /**
     * 重新绘制所有,不包括标题栏和星期几 在修改每天的节数后调用
     */
	/*private void redrawAll() {
		drawLeftNo();
		drawNowWeek();
		drawAllCourse();
	}*/

    /**
     * 绘制课程,用于周数切换以后
     */
    private void drawAllCourse() {
        drawCourse(mMonday, '一');
        drawCourse(mTuesday, '二');
        drawCourse(mWednesday, '三');
        drawCourse(mThursday, '四');
        drawCourse(mFirday, '五');
        drawCourse(mSaturday, '六');
        drawCourse(mWeekend, '日');
    }

    /**
     * 读取配置信息
     */
    private void readIniFile() {
        mShaerPreferences = getSharedPreferences("iniFile",
                Context.MODE_PRIVATE);
        mMaxCouese = mShaerPreferences.getInt("mMaxCouese", -1);
        mMaxWeek = mShaerPreferences.getInt("mMaxWeek", -1);
        mNowWeek = mShaerPreferences.getInt("mNowWeek", -1);

        Editor edit = mShaerPreferences.edit();
        // 默认12节课
        if (mMaxCouese == -1) {
            edit.putInt("mMaxCouese", 12);
        }

        // 默认20周
        if (mMaxWeek == -1) {
            edit.putInt("mMaxWeek", 17);
        }

        // 默认第一周
        if (mNowWeek == -1) {
            edit.putInt("mNowWeek", 15);
        }
        edit.commit();
    }

    /**
     * 解析来自strings.xml里面的Json课表数据
     */
    private void praseJson() {
        String json = getResources().getString(R.string.kb);
        Gson gson = new Gson();
        mCourse = gson.fromJson(json, new TypeToken<ArrayList<CourseBean>>() {
        }.getType());
    }

    /**
     * 弹出窗口,显示课程详细信息
     *
     */
    public void showCouseDetails(CourseBean bean) {
        AlertDialog.Builder builder = new Builder(this);
        AlertDialog dialog = builder.create();
        dialog.show();
        dialog.setContentView(R.layout.details_layout);
        TextView textView = (TextView) dialog.findViewById(R.id.name);
        textView.setText(bean.getCourse_name());
        textView = (TextView) dialog.findViewById(R.id.type);
        textView.setVisibility(View.GONE);
        textView.setText(bean.getCourse_type());
        textView = (TextView) dialog.findViewById(R.id.teacher);
        textView.setText(bean.getCourse_teacher());
        textView = (TextView) dialog.findViewById(R.id.address);
        textView.setText(bean.getCourse_address());
        textView = (TextView) dialog.findViewById(R.id.week);
        textView.setText(bean.getCourse_week());
    }

    /**
     * 显示切换当前周的窗口
     */
    public void showChangeWeekDlg(View v) {
        View view = View.inflate(this, R.layout.changweek_layout, null);
        ListView weekList = (ListView) view.findViewById(R.id.weekList);

        ArrayList<String> strList = new ArrayList<String>();
        for (int i = 1; i < mMaxWeek; i++) {
            strList.add("第" + i + "周");
        }

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                R.layout.item, strList);

        weekList.setAdapter(adapter);
        view.measure(0, 0);
        final PopupWindow pop = new PopupWindow(view, 300, 500, true);
        pop.setBackgroundDrawable(new ColorDrawable(0x00000000));
        int xOffSet = -(pop.getWidth() - v.getWidth()) / 2;
        pop.showAsDropDown(v, xOffSet, 0);

        weekList.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> adapter, View view,
                                    int positon, long id) {
                mNowWeek = positon + 1;
                pop.dismiss();
                drawNowWeek();
                drawAllCourse();
            }
        });
    }

}
项目地址:https://github.com/Terrybthvi/HduStudyHelper







猜你喜欢

转载自blog.csdn.net/u013132758/article/details/73203820