☰
鸿蒙教务查询实战:用JSoup解析课表与成绩并渲染ArkUI
2026/10/7 13:11:43 网站建设 项目流程

简介:这份资源是面向高校学生与鸿蒙开发初学者的课程设计完整工程,围绕华为HarmonyOS平台,结合Java的JSoup库实现教务信息查询功能,可解决安卓应用向鸿蒙迁移、网页数据抓取与展示等实际问题。压缩包共183个文件,约3.1MB,以38个java源码、64个xml布局与配置、50个png图片资源为主,另含12个json、7个jpg及gradle构建脚本、har包与jar依赖等,覆盖工程配置、界面资源与核心逻辑。内容涉及鸿蒙开发环境搭建、JSoup集成与HTML解析、网络请求与HTTPS安全处理、课程表与成绩等数据提取展示、UI交互优化、分布式能力探索以及测试调试等环节,ConnectJWGL等类体现了教务系统对接思路。已有450人学习,适合作为课程设计参考或鸿蒙入门练手项目,帮助读者理解跨平台开发流程与网页解析在鸿蒙中的落地方式。

1. 鸿蒙教务查询软件:用 JSoup 把课表和成绩抓进 HarmonyOS 应用

很多同学做鸿蒙课程设计时,第一反应是写个静态页面交差,但真正能拿得出手、答辩时经得起追问的,是能连上学校教务系统、把课表和成绩实时拉下来的工具类应用。这个标题讲的就是这么一件事:在 HarmonyOS 应用里,用 JSoup 这个 Java 生态里成熟的 HTML 解析库,去请求教务系统页面、解析出成绩和课表数据,再渲染到鸿蒙的 ArkUI 界面上。它解决的是「教务系统只有网页版、手机上体验差」这个真实痛点,适合正在做鸿蒙课程设计、又想让作品有实际数据来源的开发者。核心难点不在界面,而在网络请求、登录态维持和 HTML 结构解析这三块,下面按落地顺序拆开讲。

2. 为什么在鸿蒙里选 JSoup 而不是自己写正则

2.1 JSoup 在 HarmonyOS 工程里的定位

教务系统返回的是完整的 HTML 文档,结构嵌套深、标签属性多,用正则去抠成绩数字,遇到页面改版或者标签顺序变化就会直接翻车。JSoup 提供的是 DOM 式的选择器语法,select("table#scoreTable tr")这种写法跟前端用 CSS 选择器一样直观,容错率比正则高一个量级。在鸿蒙工程里,JSoup 是一个纯 Java 库,不依赖 Android 运行时,通过 Gradle 引入后可以在 ArkTS 的 Native 侧或者 Java 侧调用,这也是它比很多 Android 专用解析库更适合鸿蒙的原因。

选型上还有一个现实考量:教务系统大多是十几年前的老系统,页面编码可能是 GBK,表格没有语义化标签,JSoup 的charset指定和Element遍历能力刚好能覆盖这些脏数据场景。自己写解析器不是不行,但课程设计的周期通常只有几周,把时间花在解析轮子上不划算。

2.2 用 Gradle 把 JSoup 依赖拉进鸿蒙工程

鸿蒙的 Java 侧依赖管理走的是 Gradle,和 Android 工程的配置思路一致,但目录结构不同。下面是在entry模块的build.gradle里加依赖的最小改法:

// entry/build.gradle dependencies { // JSoup 核心库,用于解析教务系统返回的 HTML implementation 'org.jsoup:jsoup:1.17.2' // 鸿蒙网络请求能力,ohos 提供的 http 模块 implementation files('libs/ohos-network.jar') }

逻辑说明:implementation表示该依赖只在当前模块编译和运行时可见,不会传递给上层模块,适合工具类库。版本号选 1.17.x 是因为它对 Java 8 语法兼容好,鸿蒙 Java 侧编译链对高版本 JSoup 的某些 API 支持不完整。参数上,如果你的工程开了api而不是implementation,依赖会暴露给调用方,课程设计里没必要,保持implementation即可。

配置完执行一次 Gradle Sync,如果卡在running gradle task 'assembleDebug'很久,大概率是仓库地址没配国内镜像,或者 Gradle 离线包没下全。常见做法是在gradle-wrapper.properties里确认 distributionUrl 指向本地已缓存的版本,避免每次构建都去拉远程。

2.3 鸿蒙侧发起 HTTP 请求拿到 HTML

JSoup 本身只负责解析,不负责发请求。鸿蒙里发请求要用@ohos.net.http模块,拿到响应字符串后再交给 JSoup。下面是一个封装好的请求方法:

// 使用鸿蒙 http 模块请求教务系统页面 import http from '@ohos.net.http'; async function fetchHtml(url: string, cookie: string): Promise<string> { let httpRequest = http.createHttp(); let response = await httpRequest.request(url, { method: http.RequestMethod.GET, header: { 'Cookie': cookie, // 维持登录态的关键 'User-Agent': 'Mozilla/5.0' // 部分教务系统会校验 UA }, connectTimeout: 10000, // 连接超时 10 秒 readTimeout: 15000 // 读取超时 15 秒 }); httpRequest.destroy(); // 用完必须销毁,否则连接池泄漏 return response.result as string; }

逻辑说明:createHttp()每次调用会创建一个请求对象,destroy()是必须的,否则在频繁查询成绩时会出现连接数耗尽。参数上,connectTimeout设 10 秒是因为校园网到教务服务器通常延迟不高,超过 10 秒基本是服务器挂了;readTimeout设 15 秒是给教务系统慢查询留余量,成绩页面在期末查分高峰期可能响应很慢。Cookie头是登录态的核心,没有它请求会被重定向到登录页,拿到的 HTML 里全是登录表单而不是成绩数据。

3. 登录态维持与 HTML 解析的完整链路

3.1 模拟登录拿到有效 Cookie

教务系统登录一般分两步:先 GET 登录页拿一个隐藏的 token 或者 session id,再 POST 用户名密码。直接用 JSoup 解析登录页表单,把隐藏字段一起提交,是成功率最高的做法:

// 第一步:GET 登录页,解析隐藏字段 let loginPage = await fetchHtml(loginUrl, ''); let doc = Jsoup.parse(loginPage); // 教务系统常见的隐藏字段名,不同学校可能不同 let token = doc.select('input[name=__VIEWSTATE]').attr('value'); let sessionId = doc.select('input[name=JSESSIONID]').attr('value'); // 第二步:POST 登录表单 let formData = `username=${user}&password=${pwd}&__VIEWSTATE=${token}`; let loginResp = await httpRequest.request(loginUrl, { method: http.RequestMethod.POST, header: { 'Content-Type': 'application/x-www-form-urlencoded' }, extraData: formData }); // 从响应头 Set-Cookie 里提取 JSESSIONID let cookie = loginResp.header['Set-Cookie'];

逻辑说明:__VIEWSTATE是 ASP.NET 系教务系统的典型隐藏字段,值每次刷新都变,必须动态取。JSESSIONID是 Java 系教务系统的会话标识。参数上,Content-Type必须是application/x-www-form-urlencoded,用 JSON 提交会被服务端拒绝。提取 Cookie 时要注意Set-Cookie可能返回数组,取第一个包含JSESSIONID的即可。

3.2 用 JSoup 选择器解析成绩表格

成绩页面通常是<table>结构,每行一个课程,列分别是课程名、学分、成绩。解析代码如下:

// 解析成绩表格 let scoreDoc = Jsoup.parse(scoreHtml); // 选择所有数据行,跳过表头 let rows = scoreDoc.select('table.datelist tr'); let scores = []; for (let row of rows) { let cols = row.select('td'); if (cols.size() < 4) continue; // 跳过表头或空行 scores.push({ courseName: cols.get(1).text().trim(), credit: cols.get(2).text().trim(), score: cols.get(3).text().trim() }); }

逻辑说明:table.datelist是教务系统常见的成绩表 class 名,实际项目中需要先用浏览器开发者工具确认。cols.size() < 4这个判断是为了过滤表头行和合并单元格产生的空行,这是血泪经验——不加这个判断,解析结果里会混入「课程名称 学分 成绩」这样的表头文字。参数上,.text()会自动去除 HTML 标签并做实体转义,.trim()去掉首尾空白,两者配合能处理大部分脏数据。

3.3 把解析结果渲染到 ArkUI 列表

拿到scores数组后,用 ArkUI 的List组件渲染。核心是把数据转成@State装饰的状态,列表项用ForEach遍历:

@Entry @Component struct ScorePage { @State scores: ScoreItem[] = []; build() { List() { ForEach(this.scores, (item: ScoreItem) => { ListItem() { Row() { Text(item.courseName).layoutWeight(2) Text(item.credit).layoutWeight(1) Text(item.score).layoutWeight(1) } } }) } } }

逻辑说明:@State装饰的变量变化时会触发 UI 重绘,这是鸿蒙状态管理的基础。layoutWeight控制三列宽度比例,课程名占两份,学分和成绩各占一份。参数上,ForEach的第二个参数是键生成函数,课程设计里用索引即可,生产环境建议用课程 ID 避免重复渲染。

4. 避坑与排查:教务查询最容易翻车的五个点

4.1 请求返回登录页而不是数据页

现象:解析出来的 HTML 里全是<input type="password">,没有成绩表格。原因:Cookie 没带上,或者 Cookie 过期了。解决:在每次请求前检查 Cookie 是否包含JSESSIONID,没有就重新走登录流程;另外确认header里的Cookie字段名大小写正确,部分服务端对大小写敏感。

4.2 JSoup 选择器返回空列表

现象:select("table.datelist tr")返回 size 为 0。原因:教务系统实际 class 名不是datelist,或者页面用了 iframe 嵌套。解决:先把fetchHtml拿到的原始 HTML 打印出来,用浏览器打开保存的 HTML 文件,右键检查元素确认真实选择器;如果是 iframe,需要先解析出 iframe 的 src 再请求一次。

4.3 中文乱码

现象:课程名显示成「课ç¨」这样的乱码。原因:教务系统返回 GBK 编码,但 JSoup 默认按 UTF-8 解析。解决:在Jsoup.parse时指定编码,Jsoup.parse(html, "GBK"),或者在 HTTP 请求头里加Accept-Charset: GBK。

4.4 Gradle 构建卡在下载依赖

现象:running gradle task 'assembleDebug'长时间无响应。原因:Gradle 仓库指向国外源,或者离线包不完整。解决:在项目根目录build.gradle里把repositories改成国内镜像地址,或者提前下载好 Gradle 离线包放到~/.gradle/wrapper/dists目录下。

4.5 频繁请求被教务系统封 IP

现象:连续查询几次后所有请求都超时。原因:教务系统有频率限制,短时间大量请求会被临时封禁。解决:在请求之间加setTimeout延迟,课程设计里建议每次查询间隔 2 秒以上;另外把查询结果缓存到本地,避免重复请求同一页面。

5. 进阶技巧:用缓存和增量更新把查询速度压下来

课程设计答辩时,老师经常会问「如果教务系统挂了怎么办」。一个能加分的回答是:本地缓存 + 增量更新。具体做法是,每次成功查询后把成绩数据序列化成 JSON 存到鸿蒙的Preferences里,下次打开应用先读缓存渲染,再后台请求最新数据,请求成功后再刷新 UI。这样即使教务系统临时不可用,应用也能展示上次的数据,不会白屏。

缓存键的设计上,建议用「学号 + 学期」作为 key,因为不同学期的成绩是独立数据集。增量更新则是在解析成绩表格时,用课程名 + 学分作为唯一标识,跟缓存里的记录做对比,只把新增的课程插入列表,避免全量刷新导致的闪烁。

验证缓存是否生效,可以在fetchHtml里加一行日志,打印请求耗时和是否命中缓存。我一般会在开发阶段把缓存过期时间设成 5 分钟,方便测试;上线前改成 24 小时,减少对教务系统的压力。这个习惯帮我省了很多次因为教务系统维护导致的答辩翻车,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询