期末考完了闲得无聊,看到群里面某位兄台写了个爬虫通过学校临时成绩查询系统批量获取学生成绩(这儿本来想放链接的,但是那篇文章里面的敏感信息有点多。暂时决定不放链接了)。
不过网信中心及时更新了反爬措施,让这位兄台翻了车。
但是考完试实在闲得无聊,于是,准备用Java写一个非暴力式的查询脚本。
注意!本篇只是结合实例讲解Java中Post请求和JSON的解析
注意!!由于本篇中是一个实际案例,涉及到一部分敏感信息。因此,在尽量不影响阅读的基础上,做了一些遮挡。
首先,这里先看一下这个查询系统的网页:
然后利用了一下浏览器(Windows 10自带的Edge浏览器,自带中文环境,非常方便)的开发者工具(F12),看了一下数据的传输方式。
刚进网站,有一大堆的GET方式。这些内容与我们想要调查的内容无关,于是点击清除会话,先把这些记录清除掉。
之后,填写三个信息,然后点击查询。得到以下的几个连接记录。
可以看到,数据传输方式为POST,点击该行可以查看详细的信息。
点击正文即可看到请求正文数据(即我们发送的数据)和响应正文数据(服务器返还的数据)。
至此获取传输方式就已经完成了,现在看起来响应正文有很长的一串东西。这里我使用了一下Postman这个软件,方便我们更清楚的观察数据。
稍微介绍一下我们要填写的内容:
1. URL填写的应该是POST链接对应的URL,这里涉及到敏感信息不直接给出
2. URL前面是HTTP协议的请求方式,这里选择POST
3. param请求参数部分即我们的请求正文,按照请求正文的格式填好,系统会自动帮我们装载好完整的请求
填写好后的情况如下:
点击Send即可发送请求正文,重新得到了以下的响应正文:
得到了一个JSON格式的响应正文,在作出脱敏处理后的数据如下
{
"term": 1,
"xskscj": [
{
"point": 学分,浮点型,
"scorename": "这里是科目名称,字符串类型",
"type": "必修还是选修科目,字符串型",
"value": 成绩,浮点型,
"vapoint": 绩点数据,浮点型,
"xh": "这里是学号,字符串型"
},
{
"point": 学分,浮点型,
"scorename": "这里是科目名称,字符串类型",
"type": "必修还是选修科目,字符串型",
"value": 成绩,浮点型,
"vapoint": 绩点数据,浮点型,
"xh": "这里是学号,字符串型"
}
],
"year": "2019-2020"
}
简单说明一下,这里的JSON报文,属于一个嵌套型的稍微有点复杂的JSON。因此等会要针对这一嵌套的格式做出相应的处理后,才能得到数据。
以上即为获取数据传输方式相关的内容,本小节到此结束。
首先,在Java中建立一个接收数据的类(Post请求中不需要使用该类,提前写完主要方便于JSON的解析)
public class Result
{
//数据变量的定义
double point;
String scorename;
String type;
double value;
double vapoint;
String xh;
//基本的GET和SET方法
public double getPoint()
{
return point;
}
public void setPoint(double point)
{
this.point = point;
}
public String getScorename()
{
return scorename;
}
public void setScorename(String scorename)
{
this.scorename = scorename;
}
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
public double getValue()
{
return value;
}
public void setValue(double value)
{
this.value = value;
}
public double getVapoint()
{
return vapoint;
}
public void setVapoint(double vapoint)
{
this.vapoint = vapoint;
}
public String getXh()
{
return xh;
}
public void setXh(String xh)
{
this.xh = xh;
}
//内容输出,根据自身需求修改即可
public String toString()
{
String result=scorename+" "+value+"\n";
return result;
}
}
然后是一个Post链接的函数,这里借鉴了java使用Post和Get方式提交Http请求通用中关于Post的函数:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
public class HttpRequest
{
/**
* 向指定 URL 发送POST方法的请求
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static void sendPost(String url, String param)
{
PrintWriter out = null;
BufferedReader in = null;
String jsonString = "";
try
{
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18363");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
//1.获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
//2.中文有乱码的需要将PrintWriter改为如下
//out=new OutputStreamWriter(conn.getOutputStream(),"UTF-8")
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null)
{
jsonString += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!"+e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
System.out.println(jsonString);
}
public static void main(String[] args)
{
HttpRequest.sendPost("这里填写URL地址信息", "这里是组装好的param请求参数");
}
}
注意!!以上代码中注释内容未被CSDN识别,使用时请注意
完成这两段代码后,我们即可得到未处理过的相应报文数据。
至此,Java中使用Post方式提交Http请求的内容就暂时结束,接下来将继续探究如何解析JSON格式的文件。
这里查询信息得知Java中似乎没有直接解析JSON的方法。于是,几番查找后,找到了alibaba.fastjson这一包。下载地址及介绍:https://www.cnblogs.com/jajian/p/10051901.html
导入jar包的方法:https://blog.csdn.net/ClearLoveQ/article/details/85781624
代码的编写中参考了这一片文章中的一些思路:Java解析多层嵌套json字符串
首先,我们再来观察一下刚刚的JSON报文的结构:
{
"term": 1,
"xskscj": [
{
"point": 学分,浮点型,
"scorename": "这里是科目名称,字符串类型",
"type": "必修还是选修科目,字符串型",
"value": 成绩,浮点型,
"vapoint": 绩点数据,浮点型,
"xh": "这里是学号,字符串型"
},
{
"point": 学分,浮点型,
"scorename": "这里是科目名称,字符串类型",
"type": "必修还是选修科目,字符串型",
"value": 成绩,浮点型,
"vapoint": 绩点数据,浮点型,
"xh": "这里是学号,字符串型"
}
],
"year": "2019-2020"
}
这里我们可以发现,我们所需的数据放置在了xskscj这一对象当中,而另外两个对象term和year与我们本次的目的没有关联,可以抛去。
于是我们要解析的目标变为了,JSON报文中的xskscj部分。
然后编写的关于获取到的String类的JSON报文jsonString变量的处理:
//利用JSONObject.parseObject函数将jsonString变量转化为JSON对象
JSONObject jsonbje = JSONObject.parseObject(jsonString);
//利用JSONArray获取jsonbje对象中xskscj一类的数组
JSONArray jsonArray = jsonbje.getJSONArray("xskscj");
//创建resultList接收解析出来的数据
List<Result> resultList = new ArrayList<Result>();
//循环接收数据
for(int i = 0; i < jsonArray.size();++i)
{
Result result = new Result();
result.setPoint(jsonArray.getJSONObject(i).getDoubleValue("point"));
result.setScorename(jsonArray.getJSONObject(i).getString("scorename"));
result.setType(jsonArray.getJSONObject(i).getString("type"));
result.setValue(jsonArray.getJSONObject(i).getDoubleValue("value"));
result.setVapoint(jsonArray.getJSONObject(i).getDoubleValue("vapoint"));
result.setXh(jsonArray.getJSONObject(i).getString("xh"));
resultList.add(result);
}
之后是信息输出部分:
for(Result result:resultList)
{
System.out.println(result.toString());
}