背景
最近大创项目某一部分的功能实现需要模拟登陆教务在线并获取课表,借此机会复习了http相关的知识,学习了Okhttp的基本使用,在此记录与分享。
思路
1.用Fiddler抓包分析登陆教务在线和进入课表界面的过程,并用postman进行模拟登陆,观察响应报文,验证过程的正确性。
2.用Okhttp模拟登陆,进行相关的GET与POST操作。
3.将最后得到的HTML利用Jsoup进行解析。
遇到的问题
1.刚开始分析登陆过程由于对登录的逻辑不熟悉,有些无从下手,请教了@whojoe 大佬,并仔细分析了响应报文后终于理清了思路,并成功用postman模拟登陆。
2.这年头Okhttp多用于Android开发,大家导入它也不怎么用jar包导入的方法来导入了,而是用Maven或Gradle一句话的事,奈何我先用Maven一直报错没法解决,后用不明白Idea中的Gradle项目,无奈下只好想办法导入了jar包,注意需要导入 Okhttp,Okio,kotlin-stdlib三个包之后才能正常使用Okhttp。
3.当验证完验证码后服务器会返回cookie,需要将自己的请求cookie设为返回的那一个。刚开始无从下手,查找资料后发现要通过重写cookieJar的相关方法来实现,过程并没有我想像的那样麻烦(我以为要解析响应报文头找到Set-cookie再设置请求报文头,实际上这些通过重写cookieJar的相关方法可以自动实现。)
- 解析最终得到的包含课表信息的HTML后发现极其混乱,有表套表的情况,网上搜到的解析方法都无法成功解析。后来通过查阅Jsoup的文档以及分析该HTML文档的结构,先将html中标签内的数据都提取出来,再通过正则分割,最后成果实现预期中解析得到的效果,这里再此感谢@whojoe 大佬。
成果
这是其中的一门课。
使用逻辑与说明
每个学校登陆教务在线的逻辑是不同的,本人代码中的模拟登陆与HTML解析仅有效于我校教务在线课表的获取。出于对未知的可能造成的网络安全问题的担忧,相关url将会隐藏。(如果有我校校友需要,敬请私信。)
出于惰性与精力的原因, LoginClass 并未按照严格的面向对象思想去设计,而是粗鲁的直接将url数据写到方法中,不可取,但理解万岁。
先将主函数中loginCheck方法的数据替换为个人的,然后运行程序,程序会先向服务端请求验证码,返回的验证码会存放于项目路径下的/Result/captcha.jpeg中,请人工识别,并输入在终端中,之后便会打印解析好的课表。
代码
MainClass.java
import java.io.IOException;
public class MainClass {
public static void main(String[] args) throws IOException{
String filePath = "\\Result\\";
FileWriterClass fileWriterClass = new FileWriterClass(filePath);
// getCaptcha
LoginClass loginClass = new LoginClass();
byte [] result;
result = loginClass.getCaptcha();
fileWriterClass.WriteFileByByte(result,"captcha.jpeg");
// checkCaptcha
String isTrue = loginClass.checkCaptcha();
if(isTrue.equals("true")){
System.out.println("验证正确!");
}
else{
System.out.println("验证错误!");
}
//logCheck
String str = loginClass.loginCheck("username","password");//请修改为您的信息
String data = loginClass.getClassTableLocation();
fileWriterClass.WriteFileByString(data,"table.html");
//System.out.println(data);
HtmlParse htmlParse = new HtmlParse(data);
htmlParse.printResult();
System.out.println("over!");
}
}
FileWriterClass 工具类
import java.io.*;
public class FileWriterClass {
String filePath;
public FileWriterClass(String filePath) {
this.filePath = filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public void WriteFileByString(String data,String fileName) throws IOException {
File file = new File(filePath.concat(fileName));
FileWriter fw = new FileWriter(file);
fw.write(data);
fw.close();
}
public void WriteFileByByte(byte[] data,String fileName) throws IOException{
File file = new File(filePath.concat(fileName));
OutputStream outputStream = new FileOutputStream(file);
outputStream.write(data);
outputStream.close();
}
public String ReadFileByString(String fileName) throws IOException{
String result="";
File file = new File(filePath.concat(fileName));
FileReader fileReader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
// 一次读入一行数据
result = result.concat(line+"\n");
}
return result;
}
}
LoginClass 工具类
import okhttp3.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;
public class LoginClass {
private static final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
private static OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cookieJar(new CookieJar() {
@Override
public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list) {
cookieStore.put(httpUrl.host(), list);
}
@Override
public List<Cookie> loadForRequest(HttpUrl httpUrl) {
List<Cookie> cookies = cookieStore.get(httpUrl.host());
return cookies != null ? cookies : new ArrayList<Cookie>();
}
})
.build();
private String captcha;
public byte[] getCaptcha() throws IOException{
String url = "*/getCaptcha.do?*"; //*为隐藏内容
Request request = new Request.Builder()
.url(url)
.build();
try(Response response = okHttpClient.newCall(request).execute()){
//printHttpHeader(response);
return response.body().bytes();
}
}
public String checkCaptcha() throws IOException{
System.out.print("Please input the captcha:");
Scanner cin = new Scanner(System.in);
captcha = cin.next();
cin.close();
String checkUrl = "*/checkCaptcha.do?"; //*为隐藏内容
RequestBody requestBody = new FormBody.Builder().add("captchaCode",captcha).build();
Request request = new Request.Builder()
.post(requestBody)
.url(checkUrl)
.build();
try (Response response = okHttpClient.newCall(request).execute()) {
return response.body().string();
}
}
public String loginCheck(String username, String password)throws IOException{
String url = "*/j_acegi_security_check"; //*为隐藏内容
RequestBody requestBody = new FormBody.Builder()
.add("j_username",username)
.add("j_password",password)
.add("j_captcha",captcha)
.build();
Request request = new Request.Builder()
.header("Referer","http:*/login.jsp") //*为隐藏内容
.header("Upgrade-Insecure-Requests","1")
.post(requestBody)
.url(url)
.build();
try (Response response = okHttpClient.newCall(request).execute()) {
return response.body().string();
}
}
public void login() throws IOException{
String url = "*/index_new.jsp"; //*为隐藏内容
Request request = new Request.Builder()
.header("Referer","*/login.jsp") //*为隐藏内容
.url(url)
.build();
try(Response response = okHttpClient.newCall(request).execute()){
//printHttpHeader(response);
return ;
}
}
public String getClassTableLocation() throws IOException{
login();
String url ="http:*kT11"; //*为隐藏内容
Request request = new Request.Builder()
.url(url)
.build();
try(Response response = okHttpClient.newCall(request).execute()){
//printHttpHeader(response);
return response.body().string();
}
}
protected void printHttpHeader(Response response){
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
}
}
HtmlParse工具类
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class HtmlParse {
String data;
public HtmlParse(String data) {
this.data = data;
}
public void printResult() {
Document doc = Jsoup.parse(data);
Elements elements = doc.getElementsByClass("infolist_tab");
Element element = elements.first();
String str = element.wholeText();
String strings[] = str.split("([0-9]|[A-Z]){12}");
for(String s:strings){
boolean flag = false;
for(int i=0;i<s.length();i++){
if(s.charAt(i)!=' '&&s.charAt(i)!='\n')flag = true;
}
if(!s.trim().isEmpty()&&flag&&s!=null){
String [] strArr = s.split(" ");
for(String i:strArr){
if(!i.trim().isEmpty())
System.out.print(i);
}
}
System.out.println("------------------------------------------------");
}
}
}