教务系统(强智系统)、图书馆系统常用接口,可查询学生信息、学生课程信息、成绩信息、素拓修学情况、绩点情况、考试时间、图书馆推荐书目、搜索图书。实现了强智教务系统的模拟登录,网页解析,返回json数据格式。
项目地址| Github | 码云 |
|---|---|
| https://github.com/waiterxiaoyy/waiter-gdufe-api | https://gitee.com/waiterxiaoyy/gdufe-api |
项目介绍 教务系统登录https://docs.apipost.cn/preview/aadd819acb856ef2/66465aa51a4e56f1
你也可以在启动项目后访问:http://localhost:9093/swagger-ui.html
解决抓取教务系统信息最主要的就是登录问题,只要获得登录成功后的cookie,然后携带cookie去访问其他页面,再对页面进行解析即可,这里描述模拟登录的过程,首先是获取学号和密码,然后获取验证码,由于验证码是图片,所以需要对图片进行解析,这里采用二值化的方式,获得四位的code,再通过jsoup携带cookie、学号、密码、code模拟登录。
public class InitLogin {
private String url_Login = "http://jwxt.gdufe.edu.cn/jsxsd/xk/LoginToXkLdap"; // 登录
private String url_safecode = "http://jwxt.gdufe.edu.cn/jsxsd/verifycode.servlet"; // 验证码
//学号,密码
private String username;
private String password;
private GetSafeCode getSafeCode = new GetSafeCode();
public Map cookie;
public byte[] bytes;
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void getcookie() throws IOException {
Connection.Response response = Jsoup.connect(url_safecode).ignoreContentType(true) // 获取图片需设置忽略内容类型
.userAgent("Mozilla").method(Connection.Method.GET).timeout(3000).execute();
cookie = response.cookies();
bytes = response.bodyAsBytes();
}
public void initLogin() throws IOException {
String code = getSafeCode.getSafeCode(bytes);
try {
Map data = new HashMap();
data.put("USERNAME", username);
data.put("PASSWORD", password);
data.put("RANDOMCODE", code);
Connection connect = Jsoup.connect(url_Login)
.header("Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*
public class GetSafeCode {
private String url_safecode = "http://jwxt.gdufe.edu.cn/jsxsd/verifycode.servlet"; // 验证码
public String getSafeCode(byte[] bytes) throws IOException {
ImgIdenfy imgIdenfy = new ImgIdenfy();
//InputStream inputStream = new URL(url_safecode).openStream();
InputStream inputStream = new ByteArrayInputStream(bytes);
BufferedImage sourceImg = ImageIO.read(inputStream);
imgIdenfy.writeImage(sourceImg);
int[][] imgArr = imgIdenfy.binaryImg(sourceImg); // 二值化
imgIdenfy.removeByLine(imgArr); // 去除干扰先 引用传递
int[][][] imgArrArr = imgIdenfy.imgCut(imgArr,
new int[][]{new int[]{4, 13}, new int[]{14, 23}, new int[]{24, 33}, new int[]{34, 43}},
new int[][]{new int[]{4, 16}, new int[]{4, 16}, new int[]{4, 16}, new int[]{4, 16}},
4);
return imgIdenfy.matchCode(imgArrArr);
}
}
public class ImgIdenfy {
private int height = 22;
private int width = 62;
private int rgbThres = 150;
private String url_safecode = "http://jwxt.gdufe.edu.cn/jsxsd/verifycode.servlet"; // 验证码
public int[][] binaryImg(BufferedImage img) {
int[][] imgArr = new int[this.height][this.width];
for (int x = 0; x < this.width; ++x) {
for (int y = 0; y < this.height; ++y) {
if (x == 0 || y == 0 || x == this.width - 1 || y == this.height - 1) {
imgArr[y][x] = 1;
continue;
}
int pixel = img.getRGB(x, y);
if (((pixel & 0xff0000) >> 16) < this.rgbThres && ((pixel & 0xff00) >> 8) < this.rgbThres && (pixel & 0xff) < this.rgbThres) {
imgArr[y][x] = 0;
} else {
imgArr[y][x] = 1;
}
}
}
return imgArr;
}
// 去掉干扰线
public void removeByLin.e(int[][] imgArr) {
for (int y = 1; y < this.height - 1; ++y) {
for (int x = 1; x < this.width - 1; ++x) {
if (imgArr[y][x] == 0) {
int count = imgArr[y][x - 1] + imgArr[y][x + 1] + imgArr[y + 1][x] + imgArr[y - 1][x];
if (count > 2) imgArr[y][x] = 1;
}
}
}
}
// 裁剪
public int[][][] imgCut(int[][] imgArr, int[][] xCut, int[][] yCut, int num) {
int[][][] imgArrArr = new int[num][yCut[0][1] - yCut[0][0]][xCut[0][1] - xCut[0][0]];
for (int i = 0; i < num; ++i) {
for (int j = yCut[i][0]; j < yCut[i][1]; ++j) {
for (int k = xCut[i][0]; k < xCut[i][1]; ++k) {
imgArrArr[i][j-yCut[i][0]][k-xCut[i][0]] = imgArr[j][k];
}
}
}
return imgArrArr;
}
// 转字符串
public String getString(int[][] imgArr){
StringBuilder s = new StringBuilder();
int unitHeight = imgArr.length;
int unitWidth = imgArr[0].length;
for (int y = 0; y < unitHeight; ++y) {
for (int x = 0; x < unitWidth; ++x) {
s.append(imgArr[y][x]);
}
}
return s.toString();
}
// 相同大小直接对比
private int comparedText(String s1,String s2){
int n = s1.length();
int percent = 0;
for(int i = 0; i < n ; ++i) {
if (s1.charAt(i) == s2.charAt(i)) percent++;
}
return percent;
}
public String matchCode(int [][][] imgArrArr){
StringBuilder s = new StringBuilder();
Map charMap = CharMap.getCharMap();
for (int[][] imgArr : imgArrArr){
int maxMatch = 0;
String tempRecord = "";
for(Map.Entry m : charMap.entrySet()){
int percent = this.comparedText(this.getString(imgArr),m.getValue());
if(percent > maxMatch){
maxMatch = percent;
tempRecord = m.getKey();
}
}
s.append(tempRecord);
}
return s.toString();
}
// 写入硬盘
public void writeImage(BufferedImage sourceImg) {
File imageFile = new File("v.jpg");
try {
FileOutputStream outStream = new FileOutputStream(imageFile);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(sourceImg, "jpg", out);
byte[] data = out.toByteArray();
outStream.write(data);
} catch (Exception e) {
System.out.println(e.toString());
}
}
// 控制台打印
public void showImg(int[][] imgArr) {
int unitHeight = imgArr.length;
int unitWidth = imgArr[0].length;
for (int y = 0; y < unitHeight; ++y) {
for (int x = 0; x < unitWidth; ++x) {
System.out.print(imgArr[y][x]);
}
System.out.println();
}
}
}
Jsoup解析网页
jsoup 是一个用于处理真实世界 HTML 的 Java 库。它使用最好的 HTML5 DOM 方法和 CSS 选择器提供了一个非常方便的 API,用于获取 URL 以及提取和操作数据。具体学习可以参考https://jsoup.org/
此项目中几乎每个网页解析都用到了jsoup,jsoup是比较简单的,这里描述一个页面解析的过程,其他的类似。
public class SelectiveCourseJsoup {
public static List getHaveStudied(String studentId, String password) throws IOException {
Map haveStudiedMap = new HashMap();
Map data = new HashMap(); // 此map是用户请求体中的key-value对应,具体参数设置参考浏览器请求过程携带的参数
// 模拟登录,获取cookie
InitLogin initLogin = new InitLogin();
initLogin.setUsername(studentId);
initLogin.setPassword(password);
initLogin.getcookie();
initLogin.initLogin();
double gpaUp = 0;
double creditTotal = 0;
//设置请求体参数
data.put("kksj", "");
data.put("kcxz", "");
data.put("kcmc", "");
data.put("fxkc", "0");
data.put("xsfs", "all");
// jsoup连接,携带map和cookie,使用post请求
Connection connection = Jsoup.connect("http://jwxt.gdufe.edu.cn/jsxsd/kscj/cjcx_list")
.header("Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*
public class SelectiveCoursePoi {
private static HashMap selectiveCourseMap;
private static String filepath = "src\main\resources\static\List of elective courses.xlsx";
public static HashMap getSelectiveCourse() {
selectiveCourseMap = new HashMap();
try {
InputStream inputStream = new FileInputStream(filepath);
XSSFWorkbook workbook = new XSSFWorkbook(inputStream);
Sheet sheet = workbook.getSheetAt(0);
int maxRowNum = sheet.getPhysicalNumberOfRows();
for(int i = 0; i < maxRowNum; i++) {
Row row = sheet.getRow(i);
String courseid = getCellStringValue(row.getCell(1));
String coursename = getCellStringValue(row.getCell(2));
Integer credit = Integer.parseInt(getCellStringValue(row.getCell(3)));
String courseCollege = getCellStringValue(row.getCell(4));
String coursetype = getCellStringValue(row.getCell(5));
if(selectiveCourseMap.containsKey(coursename)) {
SelectiveCourse selectiveCourse = selectiveCourseMap.get(coursename);
List courseidList = selectiveCourse.getCourseid();
courseidList.add(courseid);
selectiveCourse.setCourseid(courseidList);
List creditList = selectiveCourse.getCredit();
creditList.add(credit);
selectiveCourse.setCredit(creditList);
selectiveCourseMap.put(coursename, selectiveCourse);
} else {
List courseidList1 = new ArrayList();
List creditList1 = new ArrayList();
courseidList1.add(courseid);
creditList1.add(credit);
// SelectiveCourse是对应的实体类
SelectiveCourse selectiveCourse = new SelectiveCourse(coursetype, courseidList1, coursename, creditList1, courseCollege);
selectiveCourseMap.put(coursename, selectiveCourse);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return selectiveCourseMap;
}
// 通用方法,处理excel单元格格式
public static String getCellStringValue(Cell cell) {
if (cell == null) {
return "";
}
int type = cell.getCellType();
String cellValue;
switch (type) {
case 3:
cellValue = "";
break;
case 5 :
cellValue = "";
break;
case 4:
cellValue = String.valueOf(cell.getBooleanCellValue());
break;
case 0:
cellValue = NumberToTextConverter.toText(cell.getNumericCellValue());
break;
case 1:
cellValue = cell.getStringCellValue();
break;
case 2:
cellValue = cell.getCellFormula();
break;
default:
cellValue = "";
break;
}
return cellValue;
}
}
配置文件
跨域请求
@Configuration
public class CorsConfig implements WebMvcConfigurer {
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("
@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {
@Bean //配置docket以配置Swagger具体参数
public Docket docket() {
return new Docket(documentationType.SWAGGER_2)
.apiInfo(apiInfo()) // 将文档信息注入
.groupName("waiterxiaoyy") // 分组名
.select()
.apis(RequestHandlerSelectors.basePackage("com.waiterxiaoyy.backandroiddesign.controller")) // 扫描接口路径
.build();
}
//配置文档信息
private ApiInfo apiInfo() {
Contact contact = new Contact("WaiterXiaoYY", "http://waiterxiaoyy.ltd/", "waiterxiaoyy@qq.com");
return new ApiInfo(
"接口文档", // 标题
"[状态码:200-请求成功(返回数据) // 202-请求获取失败(捕获异常返回)]" , // 描述
"v1.0", // 版本
"", // 组织链接
contact, // 联系人信息
"", // 许可
"", // 许可连接
new ArrayList()// 扩展
);
}
}
接口样式
获取学生课程
接口URL
请求方式http://localhost:9093/api/getstucourse
Content-TypePOST
请求Body参数json
{
"password": "xxx",
"studentId": "1825xxxxxxxx",
"termTime": "2021-2022-1"
}
| 参数名 | 示例值 | 参数类型 | 参数描述 |
|---|---|---|---|
| password | xxx | Text | 教务系统密码 |
| studentId | 1825xxxxxxx | Text | 学号 |
| termTime | 2021-2022-1 | Text | 学期(格式:2021-2022-1) |
{
"code": 200,
"msg": "获取学生课程成功",
"count": 1,
"data": [
{
"studentid": "1825xxxxxxx",
"coureseid": "2bf105e3-3",
"course": "应用软件系统综合设计",
"teacher": "胡建军副教授",
"week": [
"9-16(周)",
"9-16(周)",
"9-16(周)",
"9-16(周)",
"9-16(周)",
"9-16(周)",
"9-16(周)",
"9-16(周)",
"9-16(周)"
],
"position": [
"实验楼803(软件工程第一实验室)",
"实验楼803(软件工程第一实验室)",
"实验楼803(软件工程第一实验室)",
"实验楼803(软件工程第一实验室)",
"实验楼803(软件工程第一实验室)",
"实验楼803(软件工程第一实验室)",
"实验楼803(软件工程第一实验室)",
"实验楼803(软件工程第一实验室)",
"实验楼803(软件工程第一实验室)"
],
"count": [
"[01-02]节",
"[03-04]节",
"[05-06]节",
"[01-02]节",
"[03-04]节",
"[05-06]节",
"[01-02]节",
"[03-04]节",
"[05-06]节"
],
"weekCountPositon": [
"9-16(周)[01-02]节实验楼803(软件工程第一实验室)",
"9-16(周)[03-04]节实验楼803(软件工程第一实验室)",
"9-16(周)[05-06]节实验楼803(软件工程第一实验室)"
]
}
]
}
| 参数名 | 示例值 | 参数类型 | 参数描述 |
|---|---|---|---|
| code | 200 | Text | |
| msg | 获取学生课程成功 | Text | 返回信息描述 |
| count | 1 | Text | |
| data | Text | 返回数据 | |
| data.studentid | 1825xxxxxxx | Text | 学号 |
| data.coureseid | 2bf105e3-3 | Text | 课程代号(后台随机生成,不是真正的课程号) |
| data.course | 应用软件系统综合设计 | Text | 课程名称 |
| data.teacher | 胡建军副教授 | Text | 老师名称 |
| data.week | 9-16(周) | Text | 上课周期 |
| data.position | 实验楼803(软件工程第一实验室) | Text | 上课地点 |
| data.count | [01-02]节 | Text | 上课时间 |
| data.weekCountPositon | 9-16(周)[01-02]节实验楼803(软件工程第一实验室) | Text | 上课周期时间地点 |
{
"code": 202,
"msg": "获取学生课程失败",
"count": null,
"data": null
}
| 参数名 | 示例值 | 参数类型 | 参数描述 |
|---|---|---|---|
| code | 202 | Text | |
| msg | 获取学生课程失败 | Text | 返回文字描述 |
| count | Text | ||
| data | Text | 返回数据 |



