JSoup模拟登录新版正方教务系统(内网-教务系统)爬取信息过程详解[通俗易懂]

JSoup模拟登录新版正方教务系统(内网-教务系统)爬取信息过程详解[通俗易懂]利用JSoup模拟登录校园内网和新版正方教务系统,然后爬取成绩和课表等信息,并展示在APP上。

大家好,又见面了,我是你们的朋友全栈君。

新版正方教务系统登录界面:
在这里插入图片描述

一、需求分析

  需要访问教务系统,爬取出课表成绩等信息,并在自己所写的APP上进行展示。由于访问教务系统需要连接校园网,所以本次爬取采用了“内网-教务系统”两级爬取策略,即先模拟登录校园内网,然后携带内网cookies登录教务系统,最终爬取相关信息。

二、模拟登录内网

内网登录界面:
在这里插入图片描述
URL:https://webvpn.ncepu.edu.cn/users/sign_in
主要步骤:

  1. 填好用户名以及登录密码,按下F12,并在Elements中搜索action:
    在这里插入图片描述
    可以看到,我们输入的表单数据最终被提交到了”/users/sign_in”里。

  2. 点击登录,在Network里面找到sign_in,可以看到我们模拟登录需要的各种信息:
    在这里插入图片描述

  3. 开始写代码。

Connection connection = Jsoup.connect(URL);
connection.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36");
Response res = connection.execute();   //获取res.cookies(),后面要用到
Document d = Jsoup.parse(res.body());
List<Element> elements = d.select("form");
Map<String, String> datas = new HashMap<>();
for (Element element : elements.get(0).getAllElements()) { 
   
    if (element.attr("name").equals("user[login]")) { 
   
        element.attr("value", "************");
    }
    if (element.attr("name").equals("user[password]")) { 
   
        e.attr("value", "******");
    }
    if (element.attr("name").length() > 0) { 
   
        datas.put(e.attr("name"), e.attr("value"));
    }
}

USER_AGENT等信息都在这里面:
在这里插入图片描述
我们可以打印一下datas:

{user[dymatice_code]=unknown, utf8=?, commit=登录 Login, user[login]=马赛克, user[password]=马赛克, authenticity_token=+BD3FgRXj+LsvgUpS81EKyU7SOF1B6eshSzfo3aMOSHD3LoMsx8ZP85vWNbm1PbPJGbgJqHVbFkTvHuSzDwI8A==}

  • 第二步就是提交表单信息以及cookies,进行模拟登录:
Connection connection2 = Jsoup.connect("https://webvpn.ncepu.edu.cn/users/sign_in");
connection2.header(USER_AGENT, USER_AGENT_VALUE);
Response response = connection2.ignoreContentType(true).followRedirects(true).method(Method.POST).data(datas).cookies(res.cookies()).execute();
  • 最后一步:打印一下获得的html以及获得的cookies:
System.out.println(response.body());
Map<String, String> map = response.cookies();
for (String s : map.keySet()) { 
   
    System.out.println(s + " : " + map.get(s));
} 

三、模拟登录教务系统

我们模拟登录进入到了内网界面:
在这里插入图片描述
现在我们要模拟登录到新教务系统这个网页,进入到它的登录页面,也就是文章一开始给出的界面:
在这里插入图片描述
主要步骤如下:

  1. 按照模拟登录校园内网的方式,查看需要提交哪些表单数据,这里就不再演示了,直接上代码:
//登录
public boolean beginLogin() throws Exception{ 
   

    connection = Jsoup.connect(url+ "/jwglxt/xtgl/login_slogin.html").cookies(cookies_innet);
    connection.header("Content-Type","application/x-www-form-urlencoded;charset=utf-8");
    connection.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36");

    connection.data("csrftoken",csrftoken);
    connection.data("yhm",stuNum);
    connection.data("mm",password);
    connection.data("mm",password);
    connection.cookies(cookies).ignoreContentType(true)
                .method(Connection.Method.POST).execute();

    response = connection.followRedirects(true).execute();
    document = Jsoup.parse(response.body());
        //登录成功
        //System.out.println(document);
    if(document.getElementById("tips") == null){ 
   
        System.out.println("欢迎登录");
        System.out.println(response.cookies());
        return true;
    }else{ 
   
        System.out.println(document.getElementById("tips").text());
        System.out.println(response.cookies());
        return false;
    }
}

代码里面的cookies_innet就是模拟登录内网获得的cookies。csrftoken需要额外获取,另外这里面的密码是加密了的,所以我们也需要获取对当前输入密码加密后的密码,代码如下:

// 获取csrftoken和Cookies,并没有出错
private void getCsrftoken(){ 
   
    try{ 
   
        connection = Jsoup.connect(url+ "/jwglxt/xtgl/login_slogin.html?language=zh_CN&_t="+new Date().getTime()).cookies(cookies_innet);
        connection.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36");
        response = connection.followRedirects(true).execute();
        cookies = response.cookies();
        //保存csrftoken
        document = Jsoup.parse(response.body());
        csrftoken = document.getElementById("csrftoken").val();
    }catch (Exception ex){ 
   
        ex.printStackTrace();
    }
}
    // 获取公钥并加密密码
public void getRSApublickey() throws Exception{ 
   
    connection = Jsoup.connect(url+ "/jwglxt/xtgl/login_getPublicKey.html?" +
                "time="+ new Date().getTime()).cookies(cookies_innet);
    connection.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36");
    response = connection.cookies(cookies).ignoreContentType(true).followRedirects(true).execute();

    JSONObject jsonObject = JSON.parseObject(response.body());
    modulus = jsonObject.getString("modulus");
    exponent = jsonObject.getString("exponent");
    password = RSAEncoder.RSAEncrypt(password, B64.b64tohex(modulus), B64.b64tohex(exponent));
    password = B64.hex2b64(password);
}

附加的B64.java与RSAEncoder.java代码:

import static java.lang.Integer.parseInt;

public class B64 { 
   

    public static String b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    private static char b64pad = '=';
    private static String hexCode = "0123456789abcdef";

    // 获取对应16进制字符
    public static char int2char(int a){ 
   
        return hexCode.charAt(a);
    }

    // Base64转16进制
    public static String b64tohex(String s) { 
   
        String ret = "";
        int k = 0;
        int slop = 0;
        for(int i = 0; i < s.length(); ++i) { 
   
            if(s.charAt(i) == b64pad) break;
            int v = b64map.indexOf(s.charAt(i));
            if(v < 0) continue;
            if(k == 0) { 
   
                ret += int2char(v >> 2);
                slop = v & 3;
                k = 1;
            }
            else if(k == 1) { 
   
                ret += int2char((slop << 2) | (v >> 4));
                slop = v & 0xf;
                k = 2;
            }
            else if(k == 2) { 
   
                ret += int2char(slop);
                ret += int2char(v >> 2);
                slop = v & 3;
                k = 3;
            }
            else { 
   
                ret += int2char((slop << 2) | (v >> 4));
                ret += int2char(v & 0xf);
                k = 0;
            }
        }
        if(k == 1)
            ret += int2char(slop << 2);
        return ret;
    }

    // 16进制转Base64
    public static String hex2b64(String h) { 
   
        int i , c;
        StringBuilder ret = new StringBuilder();
        for(i = 0; i+3 <= h.length(); i+=3) { 
   
            c = parseInt(h.substring(i,i+3),16);
            ret.append(b64map.charAt(c >> 6));
            ret.append(b64map.charAt(c & 63));
        }
        if(i+1 == h.length()) { 
   
            c = parseInt(h.substring(i,i+1),16);
            ret.append(b64map.charAt(c << 2));
        }
        else if(i+2 == h.length()) { 
   
            c = parseInt(h.substring(i,i+2),16);
            ret.append(b64map.charAt(c >> 2));
            ret.append(b64map.charAt((c & 3) << 4));
        }
        while((ret.length() & 3) > 0) ret.append(b64pad);
        return ret.toString();
    }
}

import java.math.BigInteger;
import java.util.Random;

public class RSAEncoder { 
   
    private static BigInteger n = null;
    private static BigInteger e = null;

    public static String RSAEncrypt(String pwd, String nStr, String eStr){ 
   
        n = new BigInteger(nStr,16);
        e = new BigInteger(eStr,16);

        BigInteger r = RSADoPublic(pkcs1pad2(pwd,(n.bitLength()+7)>>3));
        String sp = r.toString(16);
        if((sp.length()&1) != 0 )
            sp = "0" + sp;
        return sp;
    }

    private static BigInteger RSADoPublic(BigInteger x){ 
   
              return x.modPow(e, n);
    }

    private static BigInteger pkcs1pad2(String s, int n){ 
   
        if(n < s.length() + 11) { 
    // TODO: fix for utf-8
            System.err.println("Message too long for RSAEncoder");
            return null;
        }
        byte[] ba = new byte[n];
        int i = s.length()-1;
        while(i >= 0 && n > 0) { 
   
            int c = s.codePointAt(i--);
            if(c < 128) { 
    // encode using utf-8
                ba[--n] = new Byte(String.valueOf(c));
            }
            else if((c > 127) && (c < 2048)) { 
   
                 ba[--n] = new Byte(String.valueOf((c & 63) | 128));
                ba[--n] = new Byte(String.valueOf((c >> 6) | 192));
            } else { 
   
                ba[--n] = new Byte(String.valueOf((c & 63) | 128));
                ba[--n] = new Byte(String.valueOf(((c >> 6) & 63) | 128));
                ba[--n] = new Byte(String.valueOf((c >> 12) | 224));
            }
        }
        ba[--n] = new Byte("0");

        byte[] temp = new byte[1];
        Random rdm = new Random(47L);

        while(n > 2) { 
    // random non-zero pad
            temp[0] = new Byte("0");
            while(temp[0] == 0)
                rdm.nextBytes(temp);
            ba[--n] = temp[0];
        }
        ba[--n] = 2;
        ba[--n] = 0;
        return new BigInteger(ba);
    }
}

四、爬取成绩和课表信息

  终于进入到了教务系统界面,接下来就是爬取成绩和课表信息,然后在自己写的APP中进行展示,效果如下:
在这里插入图片描述
不过我们还是得一步步来:

  1. 获取成绩信息。与前面类似,也需要提交表单数据,过程一模一样,需要提交哪些数据可以参照这篇博文:爬虫时怎么查看需要提交哪些表单数据?
    这里直接上代码:
// 获取成绩信息
public void getStudentGrade(int year , int term) throws Exception { 
   
    Map<String,String> datas = new HashMap<>();
    datas.put("xnm",String.valueOf(year));
    datas.put("xqm",String.valueOf(term * term * 3));
    datas.put("_search","false");
    datas.put("nd",String.valueOf(new Date().getTime()));
    datas.put("queryModel.showCount","80");
    datas.put("queryModel.currentPage","1");
    datas.put("queryModel.sortName","");
    datas.put("queryModel.sortOrder","asc");
    datas.put("queryModel.sortName","");
    datas.put("time","0");

    System.out.println(datas);

    connection = Jsoup.connect(url+ "/jwglxt/cjcx/cjcx_cxDgXscj.html?gnmkdm=N305005&layout=default&su=" + stuNum);
    connection.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");
    response = connection.cookies(cookies_innet).cookies(cookies).method(Connection.Method.POST)
                .data(datas).ignoreContentType(true).execute();
    connection = Jsoup.connect(url+ "/jwglxt/cjcx/cjcx_cxDgXscj.html?doType=query&gnmkdm=N305005");
    connection.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");
    response = connection.cookies(cookies_innet).cookies(cookies).method(Connection.Method.POST)
                .data(datas).ignoreContentType(true).execute();
    System.out.println(response.body());
    JSONObject jsonObject = JSON.parseObject(response.body());


        //System.out.println(jsonObject);
    JSONArray gradeTable = JSON.parseArray(jsonObject.getString("items"));
        //System.out.println(gradeTable);
    for (Iterator iterator = gradeTable.iterator(); iterator.hasNext();) { 
   
        JSONObject lesson = (JSONObject) iterator.next();
        System.out.println(lesson.getString("kcmc") + " " +
                lesson.getString("jsxm") + " " +
                lesson.getString("bfzcj") + " " +
                lesson.getString("jd") + " " +
                lesson.getString("kcxzmc"));
    }
}

有一点需要注意:提交参数中的showCount最好大一点,因为我们默认只爬取了第一页的数据,在第一页显示所有成绩信息才能一次性爬取完。

  1. 成绩信息展示

安卓ExpandableListView的详细使用教程(附代码解析过程)

参考文章

这些文章都是我自己写的,算是对前面零散知识点的一点总结吧:

  1. JSoup模拟登录网站(以校园内网为例)
  2. JSoup利用获得的cookies访问该网页中的其它链接
  3. 爬虫时怎么查看需要提交哪些表单数据?
  4. JSoup携带cookies连续跳转登录多个界面
  5. Java爬虫简单判断是否模拟登录成功(以JSoup为例)
  6. 安卓ExpandableListView的详细使用教程(附代码解析过程)

  欢迎大家关注我的微信公众号:KI的算法杂记,有什么问题可以直接发私信。

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/150164.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • RDD:创建的几种方式(scala和java)[通俗易懂]

    RDD:创建的几种方式(scala和java)[通俗易懂]Spark编程每一个spark应用程序都包含一个驱动程序(driverprogram),他会运行用户的main函数,并在集群上执行各种并行操作(paralleloperations)spark提供的最主要的抽象概念有两种: 弹性分布式数据集(resilientdistributeddataset)简称RDD ,他是一个元素集合,被分区地分布到集群的不同节点上,可以被并行操作,RD…

  • golang 激活码 不对【在线破解激活】

    golang 激活码 不对【在线破解激活】,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • java数组乱码_java输入数组结果出现乱码怎么处理[通俗易懂]

    java数组乱码_java输入数组结果出现乱码怎么处理[通俗易懂]中文乱码是因为编码格式不一致导致的。进入Eclipse,导入一个项目工程,如果项目文件的编码与工具编码不一致将会造成乱码。如果要使插件开发应用能有更好的国际化支持,能够最大程度的支持中文输出,则最好使Java文件使用UTF-8编码。修改默认编码:在菜单导航栏上Window–>Preferences打开”首选项”对话框,左侧导航树,导航到General–>Workspace。…

  • python 字符串转成数字_python数字转十六进制字符串

    python 字符串转成数字_python数字转十六进制字符串在python列表操作中,面对需要把列表中的字符串转为礼拜的操作,无需强转,通过简单的几步就可以实现,本文介绍python中字符串转成数字的三种方法:1、使用join的方法;2、使用int函数将16进制字符串转化为10进制整数;3、使用列表生成式进行转换。方法一:使用join的方法num_list=[‘1′,’2′,’3’]str_list=”.join(num_str)#把列表中的元素连起来print(int(str_list))输出123方法二:使用int函数将16进制

  • goland2021.3.26激活破解方法

    goland2021.3.26激活破解方法,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • Linux上快速安装、卸载JDK「建议收藏」

    Linux上快速安装、卸载JDK「建议收藏」Linux上快速安装、卸载JDKLinux上安装JDK步骤1.安装JDK2.如何卸载JDKLinux上安装JDK步骤1.安装JDK准备工作:确保机器在安装之前,系统没有默认安装JDK,可通过以下命令进行检查java-version注意:如果是自己在虚拟机上安装的centos7.x或者centos6.x,则会默认帮你安装好OpenJDK,如下截图:安装步骤如下:新建jdk安装目录mkdir/usr/java把JDK上传到Linux服务器,可以采用FileZi

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号