这是本人android课设的时候选择的课题,爬取教务网实现基本的成绩课表查询,基本的登陆登出功能,这里记录一下(花了五天做的,有点粗糙QAQ)。
背景知识
数据爬取
本次从教务网爬取课程,成绩等数据采用了网络爬虫的形式。由于android 开发是以java为主要语言,本次并没有采用以Python为主流编程语言的网络爬虫框架,转而采用Java。通过引入第三方库(Jsoup)以及Connection类,对数据进行爬取,提取等工作。在与教务网建立连接时,考虑到个人隐私等因素,本设计并没有调用现成的接口,而转向破解比较有难度的教务网登录加密机制,获取最终cookies,从而获取数据的方法。
具体加密破解以及爬虫实现请参考本人的另外一篇博客----->传送门
界面设计
本模块针对课表查询,成绩查询等方面,设计页面虽并不多,但为提高用户体验以及观赏性,本设计采用了Fragment与Activity相结合的方法,提高了用户体验,增强了代码的结构性和可读性。
在下拉框的选择以及5*7课程表的显示上,本设计使用了Git-Hub 上开源的 NiceSpinner 和smartTable,NiceSpinner提供了较为方便同时简洁方便的下拉框,SmartTable 提供了易于数据绑定的以及样式更改的功能,提高了开发效率,美化了界面。
登录状态以及用户信息的保存
本功能通过SharedPerference 来实现,用户名,密码以及登录状态的保存,方便了用户登录的切换以及注销。同时考虑到网络状态切换以及用户密码问题,本设计都考虑在内,将一系列问题进行封装,而只需提醒用户哪地方出了错误,以提升用户的体验。
Preferences 是一种应用程序内部轻量级的数据存储方案。Preferences主要用于存储和查询简单数据类型的数据,这些简单数据类型包括boolean、int、float、long以及 String 等,存储方式以键值对的形式存放在应用程序私有的文件夹下。
数据展示
后端爬取的数据需要借用数据适配器在前端界面上展示,在多种Adapter 中,本设计选择了最合适的SimpleAdapter,简化了书写,减少了代码量。
设计大体规划
本次设计可以分为前后端两个方面,首先通过编写爬虫拿到身份识别的cookies,得以访问教务网各网页及其数据,拿到HTML文档之后,使用第三方库Jsoup来进行解析,拿到前端需要的数据。
前端以activity 为主,mainActivity 包括四个fragment,为主页,查询成绩页面,查询课程页面,基本信息页面,并可以通过fragment中的快捷按钮进行切换。
为方便切换以及登录状态的保存,本设计用到了SharedPreference来保存登录状态,在注销的时候来进行登录状态的切换,以此实现用户的自动登录以及用户的切换。
前后端的连接通过数据适配器来完成,使用SimpleAdapter 来对数据进行展示,在课程表等需要进行局部刷新的地方,我们使用GitHub上开源的SmartTable来进行数据展示,方便了数据操作。
数据结构
本设计的数据结构主要在于爬取的数据后数据的存储方式以便于数据结构化,以及复用性高。正是利用java面向对象的思想,本设计将各爬取的数据各建立一个类,并重写构造方法。
以考试安排为例:
package com.example.spider;
/*
考试安排
*/
public class Exam {
public int id;
public String place;
public String examId;
public String CourseId;
public String name;
public String teacher;
public String time;
public String room;
Exam(int id, String place, String examId, String CourseId, String name, String teacher, String time, String room) {
this.id = id;
this.place = place;
this.examId = examId;
this.CourseId = CourseId;
this.teacher = teacher;
this.time = time;
this.room = room;
this.name = name;
}
}
爬虫登录教务网的基本流程
具体加密破解以及爬虫实现请参考本人的另外一篇博客----->传送门
软件的基本逻辑流程
关键代码
登录状态的控制
btn4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), Login.class);
intent.putExtra("out", "true");
startActivity(intent);
}
});
try {
Intent intent = getIntent();
System.out.println("我giao" + intent.getStringExtra("out"));
if (intent.getStringExtra("out").equals("true")) {
Toast toast = Toast.makeText(getApplicationContext(), "退出成功!", Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER | Gravity.TOP, 0, 0);
toast.show();
SharedPreferences sp = getSharedPreferences("data", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("login", "");
editor.apply();
}
} catch (Exception e) {
System.out.println("我giao");
}
判断当前网络是否可用
public boolean isNetworkAvailable(Activity activity) {
Context context = activity.getApplicationContext();
// 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager == null) {
return false;
} else {
// 获取NetworkInfo对象
NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo();
if (networkInfo != null && networkInfo.length > 0) {
for (int i = 0; i < networkInfo.length; i++) {
// 判断当前网络状态是否为连接状态
if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
}
return false;
}
配置文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:hardwareAccelerated="false"
android:icon="@mipmap/ic_lnch"
android:label="@string/app_name"
android:largeHeap="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:usesCleartextTraffic="true">
<activity android:name=".Startview">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Login">
</activity>
<activity android:name=".MainActivity"></activity>
</application>
</manifest>
界面展示
登录界面
本界面提供了输入教务网账户以及密码的功能,注销账号之后也会跳转到当前界面,并会将存储在SharePerference的登录状态修改成为未登录状态。
主页界面
首先提供了多个按钮,一是为了美化界面,二是提供了注销按钮,便于用户切换账号,三是跳转到其他界面的按钮,方便了用户。并提供了今日课程,可以让用户在首页直观的看到今日需要上的课程以及地点,时间等。
本界面还提供了本学期课程即需要考核的课程,让用户一目了然。
课表查询界面
本界面采用了5*7的表格形式,详细展示了一周中每一节次需要上的课程,用户可以通过下拉框,以及EditText来自定义学期以及周数来进行查询,方便用户查询。
成绩查询界面
本界面提供了课程分数查询的功能,用户可以通过下拉框选择不同学期的课程结果,提供了科目的名称,分数,绩点,学分,性质等信息。
基本信息界面
本界面提供了等级考试查询,本学期考试安排,上学期成绩结果展示的功能,等级考试包括四六级等考试的分数,日期等,考试安排包括考试的科目考试的时间地点等,考试结果包括科目名称,分数,科目的性质,绩点学分等。
不足之处分析
本次设计由于时间与设备问题,许多细节问题并未处理妥当,比如界面上色彩显得比较单调,使用的android原生图标使得界面显得呆板,没有特别吸引人的地方,之后本人会对界面以及功能进行丰富。
同时,由于权限问题以及隐私问题,查询其他同学的成绩也没有完成。但有了爬取个人成绩的经验,之后条件允许的话,功能实现问题应该不会太大。
逻辑上,本设计虽然考虑到网络问题对于数据爬取的影响,但由于意外,数据在中途产生错误的异常并没有进行太系统的处理。
此外,在功能上,虽完成了大多数要求的功能,但教务网上好多数据可以进行发掘来开发成新的功能,比如教室查找,校历,教学评价,选课等功能,可以被开发,这也是日后本设计进行升级的方向。
小结
本次课程设计,收获颇丰,之前本人接触了爬虫,但通过本次爬取教务网并进行数据提取,对爬虫有了更加深层的理解,本次爬取工程,运用到了cookies,js逆向,jsoup等技术以及第三方库,也体会到了Python 与 java 的不同之处。两种语言在不同方面上各有优势,Python更适合于数据处理以及爬虫等。如果这次使用Python进行编程,代码量会变少许多。
在进行爬取到的HTML页面处理的时候,遇到过因为界面发生轻微改变的时候就会选择不到数据,这是因为数据的选择器发生改变,所以爬取数据的代码也并不是一直不变的,需要一直维护。
本次课程设计使用AndroidStudio 进行编程,这么多天的使用下来,逐渐熟悉了使用。并且将书上的理论知识得以实践。
本次爬取教务网获益颇丰,获得cookies 以及 破解加密的过程让本人对各方面都有所提高。而在获取cookies以及数据是本人与另外一位同学相互交流的结果,团队合作的益处不言而喻。