本文共 20739 字,大约阅读时间需要 69 分钟。
时光飞逝,日月如梭,转眼间四年的大学生活已经结束啦!开始了程序员的加班生活。我的第二学位的毕业设计是实现一个学习小助手。这其中最重要的环节就是模拟登录学校的教务系统,获取到教务系统的数据并解析,用自己的数据库存储,展示在自己设计的界面上。例如课程表我是仿照超级课程表的界面来设计的。废话不多说下面先看看效果。
模拟登录之前我们首先需要抓取我们登录时所需要传递的数据,可能有人会想用专业的抓包工具来抓包,其实不用那么复杂,用火狐浏览器或者Chrom浏览器的开发者模式就可以了。博主用的是火狐浏览器抓包,传的一些参数也是按照火狐浏览器来的。
首先,我们来看一下模拟器登录时需要传递的参数及HTTP请求头,我们可以看到请求是post请求,请求头字段基本差不多
传递的参数有这么些
其中这些参数中lt这个是搞的我最头疼的。本以为lt是一个固定值,但是模拟登录的时候发现不对,每次抓包lt是不一样的。最后,只能通过学习JS代码,研究这个lt的取值,最后发现lt及其他参数都隐藏在返回的HTML页面的代码中:(如下图)
搞清楚需要传什么参数和请求的HTTP头了,我们就可以模拟登录教务系统了。
实现模拟登录首先要自定义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登录成功后会返回一个链接,并不是登录后的教务首页,我们还需要解析提取链接并用GET请求去请求,即可!下面我们就来说一下关于HTML网页数据的解析。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(); } }); }
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 ArrayListmCourse; 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 >() { }.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 strList = new ArrayList (); for (int i = 1; i < mMaxWeek; i++) { strList.add("第" + i + "周"); } ArrayAdapter adapter = new ArrayAdapter (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(); } }); }}