这个作业属于哪个课程 | <班级的链接> |
---|---|
这个作业要求在哪里 | <作业要求的链接> |
这个作业的目标 | 实现爬取数据与json文件处理的实战作业 |
文章目录
一、Gitcode项目地址
二、PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 22 |
• Estimate | • 估计这个任务需要多少时间 | 5 | 5 |
Development | 开发 | 300 | 283 |
• Analysis | • 需求分析 (包括学习新技术) | 120 | 113 |
• Design Spec | • 生成设计文档 | 10 | 10 |
• Design Review | • 设计复审 | 10 | 10 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
• Design | • 具体设计 | 25 | 25 |
• Coding | • 具体编码 | 200 | 211 |
• Code Review | • 代码复审 | 20 | 22 |
• Test | • 测试(自我测试,修改代码,提交修改) | 60 | 48 |
Reporting | 报告 | 30 | 30 |
• Test Repor | • 测试报告 | 20 | 10 |
• Size Measurement | • 计算工作量 | 10 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 10 | 10 |
合计 | 860 | 829 |
三、解题思路描述
3.1 爬取json文件
先是尝试了使用jsoup对网页静态爬取,但是给出的网页是动态获取数据的,导致jsoup学得比较浅的我无法爬取数据,之后发现该网页是通过AJAX异步请求加载的数据,于是直接通过XHR获取数据接口来直接获取数据。
然后直接把json文件内容复制下来就好了。
3.2 json文件解析及配置
在开始编写代码前,通过了解后决定使用Jackson进行对json文件的解析及使用,并且使用lombok快速生成实体类的部分通用代码以减少代码量。
事实上通过Jackson有两种方法对json文件进行反序列化,一种是通过属性名来对应获取数据;另一种是直接与实体类建立映射关系,快速反序列化。在编写过程中,通过实际使用发现前一种能让程序的耦合度更高(当然也有可能是我对后一种的理解不够导致的)。这会在后面提到。
3.3 文件读写方式
当然还是使用经典款的IO包啦~~
3.4 代码整体结构
本次作业中,布置了三个功能,经过结合所学知识思考后,决定一个功能对应一个实体类和一个返回实体对象List的方法,再直接在main方法对文件进行读写和内容的识别筛选。
四、接口及实体类设计
4.1 功能一的设计
功能一要求从json文件中获取数据并写入output文件中,在这里我选择让json文件与我设计的Player类直接建立映射关系以减少代码量,并按照3.4中所说的建立了类和方法。
4.2 功能二的设计
功能二要求从json文件中获取决赛的排名数据。这里我同样选择了让json文件与我设计的Result类直接建立映射关系。值得一提的是,我还建立了Dive类用于适配json文件中数组之间的嵌套关系,便于该程序将来的功能拓展。
4.3 附加功能的设计
附加功能要求获取对应比赛所有场次的数据。乍一看我以为这个功能只需通过功能二设计的Result类并进行适当的处理即可,但深入分析后,我发现由于我功能二是直接通过映射获取数据,导致只能从决赛中获取数据,使得我不得不从新建DetailResult类及对应方法和修改功能二中的架构之间选择。最后我选择了前一种。
五、关键代码展示
以下代码展示中的实体类均添加了lombok库中的@Data和@NoArgsConstructor
5.1 功能一相关:
/**
* 运动员类
*/
public class Player {
@JsonProperty("Gender")
private int Gender;
@JsonProperty("PreferredLastName")
private String PreferredLastName;
@JsonProperty("PreferredFirstName")
private String PreferredFirstName;
@JsonProperty("NAT")
private String NAT;
public String toString(){
return "Full Name:" + PreferredLastName + " " + PreferredFirstName + "\n" +
"Gender:" + (Gender==0?"Male":"Female") + "\n" +
"Country:" + NAT + "\n" +
"-----" + "\n";
}
}
/**
* 获取运动员信息
* @param url 文件路径
* @return List形式的运动员信息
* @throws Exception 抛出异常
*/
public static List<Player> getPlayers(String url) throws Exception{
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
List<Player> players = new ArrayList<>();
JsonNode rootNode = mapper.readTree(new File(url));
if(rootNode.isArray()){
for (JsonNode country : rootNode){
for (JsonNode p : country.get("Participations")){
String message = mapper.writeValueAsString(p);
Player player = mapper.readValue(message,Player.class);
players.add(player);
}
}
}
return players;
}
5.2 功能二相关
/**
* 简略结果类
*/
public class Result {
@JsonProperty("FullName")
private String fullName;
@JsonProperty("Rank")
private int rank;
@JsonProperty("TotalPoints")
private String totalPoints;
@JsonProperty("Name")
private String name;
private List<Dive> points;
@Override
public String toString(){
return "Full Name:" + fullName + "\n" +
"Rank:" + rank + "\n" +
"Score:" + Function.getPoints(points,totalPoints) + "\n" +
"-----" + "\n";
}
}
/**
* 获取简略结果信息
* @param url 文件路径
* @return List形式的简略结果信息
* @throws Exception 抛出异常
*/
public static List<Result> getBriefResults(String url) throws Exception{
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
List<Result> results = new ArrayList<>();
JsonNode root = mapper.readTree(new File(url));
for (JsonNode node : root.get("Heats")) {
if(node.get("Name").asText().equals("Final")){
for (JsonNode n : node.get("Results")){
String message = mapper.writeValueAsString(n);
Result result = mapper.readValue(message,Result.class);
List<Dive> dives = new ArrayList<>();
for (JsonNode d : n.get("Dives")) {
message = mapper.writeValueAsString(d);
Dive dive = mapper.readValue(message,Dive.class);
dives.add(dive);
}
result.setPoints(dives);
results.add(result);
}
}
}
return results;
}
/**
* 获取对应的分数序列
* @param points 单轮分数
* @param totalPoints 总分
* @return 分数序列
*/
public static String getPoints(List<Dive> points, String totalPoints){
int f = 0;
StringBuilder sb = new StringBuilder("");
for (Dive point : points) {
sb.append(point + (++f == points.size() ? " = " : " + " ));
}
sb.append(totalPoints);
return sb.toString();
}
5.3 附加功能相关
public class DetailResult {
private String fullName;
private int[] ranks = new int[3];
private String[] scores = new String[3];
public DetailResult(){
this.ranks[0] = this.ranks[1] = this.ranks[2] = -1;
this.scores[0] = this.scores[1] = this.scores[2] = "*";
}
@Override
public String toString(){
return "Full Name:" + fullName + "\n" +
"Rank:" + (ranks[0] == -1 ? "*" : ranks[0]) + " | " + (ranks[1] == -1 ? "*" : ranks[1])+ " | " + (ranks[2] == -1 ? "*" : ranks[2]) + "\n" +
"Preliminary Score:" + scores[0] + "\n" +
"Semifinal Score:" + scores[1] + "\n" +
"Final Score:" + scores[2] + "\n" +
"-----" + "\n";
}
}
/**
* 获取详细结果信息
* @param url 文件路径
* @return List形式的详细结果信息
* @throws Exception 抛出异常
*/
public static List<DetailResult> getDetailResults(String url) throws Exception{
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Map<String,Integer> mp = Map.ofEntries(
Map.entry("Preliminary",0),
Map.entry("Semifinal",1),
Map.entry("Final",2)
);
List<DetailResult> results = new ArrayList<>();
JsonNode root = mapper.readTree(new File(url));
ArrayNode heatRoot = (ArrayNode) root.get("Heats");
int count = heatRoot.size();
for (int i = count - 1 ; i >= 0 ; i--) {
JsonNode heat = heatRoot.get(i);
String name = heat.get("Name").asText();
for (JsonNode result : heat.get("Results")) {
List<Dive> dives = new ArrayList<>();
for (JsonNode d : result.get("Dives")) {
Dive dive = new Dive(d.get("TotalPoints").asText());
dives.add(dive);
}
DetailResult detail = new DetailResult();
if(name.equals("Preliminary")){
detail.setFullName(result.get("FullName").asText());
results.add(detail);
}
else{
for (DetailResult dr : results) {
if(dr.getFullName().equals(result.get("FullName").asText())){
detail = dr;
break;
}
}
}
int[] ranks = detail.getRanks();
String[] scores = detail.getScores();
ranks[mp.get(name)] = result.get("Rank").asInt();
scores[mp.get(name)] = Function.getPoints(dives,result.get("TotalPoints").asText());
detail.setRanks(ranks);
detail.setScores(scores);
}
}
return results;
}
5.4 主函数(数据处理和文件读写操作)
public static void main(String[] args) {
if (args.length == 2) {
String inputURL = args[0];
String outputURL = args[1];
File inputFile = new File(inputURL);
File outputFile = new File(outputURL);
try (BufferedReader reader = new BufferedReader(new FileReader(inputFile));
FileWriter writer = new FileWriter(outputFile)) {
String line;
while ((line = reader.readLine()) != null) {
String[] words = line.split(" ");
if (words.length == 1 && words[0].equals("players")) {
List<Player> players = Function.getPlayers("./src/data/athletes.json");
StringBuilder sb = new StringBuilder("");
for (Player player : players) {
sb.append(player.toString());
}
writer.write(sb.toString());
} else if (words.length == 1 && words[0].equals("result")) {
writer.write("ERROR\n");
writer.write("-----\n");
} else if (words.length != 1 && words[0].equals("result")) {
StringBuilder sb = new StringBuilder("");
int limit = words.length;
if (words[words.length - 1].equals("detail")) {
limit--;
}
for (int i = 1; i < limit; i++) {
if (i != 1) sb.append(" ");
sb.append(words[i]);
}
String url = "./src/data/" + sb.toString() + ".json";
if (competitionList.indexOf(sb.toString()) != -1) {
var results = (words[words.length - 1].equals("detail") ? Function.getDetailResults(url) : Function.getBriefResults(url));
for (var result : results) {
writer.write(result.toString());
}
} else {
writer.write("N/A\n");
writer.write("-----\n");
}
} else {
writer.write("ERROR\n");
writer.write("-----\n");
}
}
} catch (Exception e) {
System.out.println(e);
}
}
}
六、单元测试
采用junit的单元测试方式,编写了测试类FunctionTest,测试结果如下:
七、心得体会
在本次作业中,我深深地体会到了我对完成一个项目所需知识的匮乏。在完成过程中,我大多数时间花在了学习和实践新知识上,包括jsoup,jackson,junit等功能性强大的库,同时对项目结构等知识仍然极度匮乏,并且在设计类结构时,由于对知识的掌握度不足,导致项目的耦合度低,冗余代码量大。只能说道阻且长啊~~