移动端教务系统课程表数据抓取与解析实践
一、网络请求拦截与会话机制分析
移动终端访问传统Web教务系统的核心原理在于模拟标准HTTP通信流程。通过流量监控工具(如浏览器开发者网络面板或HttpWatch),可以完整还原身份验证与数据请求的交互链路。多数高校教务系统采用POST表单提交进行登录:客户端将学号与密码序列化后发送至认证接口,服务器校验通过后,会在响应头中下发Set-Cookie字段。该Cookie作为会话凭证,在后续访问受保护资源时必须携带,否则服务器将拒绝响应。
二、基于HTTP协议的认证模拟
明确请求特征后,即可使用Java原生网络组件构造认证请求。以下代码展示了如何发送凭证、处理响应流并持久化会话令牌:
public class AcademicAuthenticator {
private static final String AUTH_ENDPOINT = "http://202.118.88.140/loginAction.do";
private static final List<String> sessionTokens = Collections.synchronizedList(new ArrayList<>());
public static String performLogin(String studentId, String password) throws IOException {
String formData = String.format("zjh=%s&mm=%s",
URLEncoder.encode(studentId, StandardCharsets.UTF_8),
URLEncoder.encode(password, StandardCharsets.UTF_8));
URL target = new URL(AUTH_ENDPOINT);
HttpURLConnection session = (HttpURLConnection) target.openConnection();
session.setRequestMethod("POST");
session.setConnectTimeout(5000);
session.setDoOutput(true);
session.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
try (OutputStream writer = session.getOutputStream()) {
writer.write(formData.getBytes(StandardCharsets.UTF_8));
writer.flush();
}
if (session.getResponseCode() == 200) {
sessionTokens.addAll(session.getHeaderFields().getOrDefault("Set-Cookie", Collections.emptyList()));
try (InputStream reader = session.getInputStream()) {
return new String(reader.readAllBytes(), StandardCharsets.UTF_8);
}
}
throw new IOException("Authentication failed with code: " + session.getResponseCode());
}
}
实现要点在于开启输出通道写入URL编码后的表单参数,并在状态码为200时提取响应头中的Cookie列表。该列表必须全局缓存,供后续请求复用。
三、携带会话凭证获取目标页面
完成身份校验后,查询学期课表通常触发GET请求。此阶段无需提交表单数据,但需在请求头中注入已保存的会话标识。请求构造逻辑如下:
public static String fetchScheduleSource(String charset) throws IOException {
String scheduleUri = "http://202.118.88.140/xskbAction.do?actionType=1";
URL resource = new URL(scheduleUri);
HttpURLConnection request = (HttpURLConnection) resource.openConnection();
request.setRequestMethod("GET");
request.setConnectTimeout(5000);
// 注入会话凭证
if (!sessionTokens.isEmpty()) {
request.addRequestProperty("Cookie", String.join("; ", sessionTokens));
}
if (request.getResponseCode() == 200) {
try (InputStream is = request.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, charset))) {
StringBuilder buffer = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
buffer.append(line).append("\n");
}
return buffer.toString();
}
}
throw new IOException("Failed to retrieve schedule page");
}
该方法通过遍历缓存的令牌数组,将其拼接后附加至请求头。服务器验证会话有效性后,将返回包含完整课表结构的HTML源码。
四、利用DOM解析器提取结构化数据
原始HTML页面通常混杂大量CSS样式与JavaScript脚本。为高效提取课程信息,可引入Jsoup库构建DOM树并执行节点查询。针对课表现象的表格布局,解析策略如下:
public static void parseCourseGrid(String rawHtml, String baseUri, String encoding) throws IOException {
Document document = Jsoup.parse(
new ByteArrayInputStream(rawHtml.getBytes(encoding)),
encoding,
baseUri
);
// 标准化处理全角空格与HTML实体
String normalizedSpace = document.parser().unescapeEntities("\u00a0").text();
// 定位核心数据表格
Elements mainTable = document.select("table.displayTag");
for (Element row : mainTable.select("tr")) {
// 跳过表头与空行
if (row.select("th").isEmpty()) continue;
Elements cells = row.select("td");
cells.forEach(cell -> {
String cellData = cell.text().replace(normalizedSpace, " ").trim();
if (!cellData.isEmpty()) {
System.out.println("Extracted -> " + cellData);
}
});
}
}
解析流程首先根据页面编码加载字节流,随后通过CSS选择器精准定位包含课程数据的表格元素。在遍历行与单元格时,利用字符替换清理特殊空白符,最终输出清洗后的纯文本内容。获得结构化数据后,即可将其映射至移动端的列表或卡片视图,完成课表模块的完整开发。