https://blog.csdn.net/Dontla/article/details/132240693
https://blog.csdn.net/hlzdbk/article/details/130554646
主要参考以上两篇大牛博文,实现了批量查询博客文章质量分的小项目。
这两篇文章对于实现思路、步骤及代码都进行了详细介绍。这里我也记录一下自己实现的过程,学习了新知识,也对前端技术使用进行复习。
在今年(2023),我打算认真整理一下自己的博客,将部分博客添加了封面,而且目录也进行了规范。一个偶然的时间,我知道了CSDN博文质量检测,并将其添加到了收藏。将近期写的文章都进行了检测,只要分数大于85我就不管了,小于85的进行优化。近百篇博文,只有近期的进行质量分查询,感觉大部分都在90分左右,于是在100篇的时候,厚着脸皮着急去认证专家。结果未通过,【您的部分博文平均质量分稍微有些低,博客专家需要近两年博文平均质量分需要达到90分左右,还请您继续创作优质博文后再来申请哦。】
然后我就将大部分的文章进行了质量分查询,发现不少分数都很低,再往前的更低。这咋整,得优化文章呀。可是得有针对性优化,于是我一篇一篇的查询质量分数,把分数低的记录下来,择期进行优化。可是百篇文章,这工程量也不小呀。
想着是否可以批量查询博客质量分呢?
想着是否可以批量查询博客质量分呢?于是自己动手分析了一波,发现CSDN网站的请求和响应还是很容易看懂的,很快找了到相应的请求,也可以看到具体的响应数据。有戏呀。接下里就是思考使用什么样的技术去实现了,第一想到就是爬虫,而且是Java语言的爬虫,网上搜索了一些相关技术,比如 Apache HttpClient
、jsoup
、WebMagic
、Nutch
等等。在了解这些技术的同时我也在思考,目前的需求就是向服务器发送请求,获取响应的 JSON
数据而已,不需要爬取页面数据等,使用爬虫技术有点浪费呀,而且爬虫也比较危险。
这时候两篇大牛的博文就起到了决定作用,第一篇博客讲解了请求接口的分析,最后使用 Python 实现了,我的目标是使用熟悉的 Java 实现。但有幸在这篇博文中给出了参考第二篇文章的链接,进去一看,这才是我想要的。第二位博主同样是申请专家,但是质量分数不够,共情啊。然后他使用 Java 技术实现,就是 Spring
中的 RestTemplate
技术,只是去发送请求并接收响应数据,完全符合我的实现思路。
其实批量查询博客质量分的功能很简单,简简单单的把主要代码敲出来,运行程序就可以查询出分数,甚至在一个 main()
方法就行,直接打印在控制台。第二篇博客是使用 POI
技术将数据导出在 Excel
中,更加方便了。
我该实现怎样的功能呢?对于批量查询的这个需求来说,简单实现即可,毕竟我们只是偶尔查看质量分,也没有必要将其保存在数据库中,而且也许哪天CSDN也可以批量查询呢(但是感觉CSDN有自己的考虑)。
想来想去,最后决定自己就做个页面吧,将文章质量分一次性查询出来,展示在页面上,同时提供下载功能,再加上分数统计。也不会将数据保存,每次想查询就输入自己的用户ID,毕竟这个分数在每次更新博客内容后要变化,而且CSDN关于质量分的算法也会变的。所以只查看当下的博客质量分就可以。
这是最终的实现效果:
RestTemplate
技术是实现批量查询质量分的核心。它是 Spring
中的技术,是一个用于发送 HTTP
请求的工具,它提供了一组简单易用的 API 来执行各种 HTTP
请求操作,例如 GET
、POST
、PUT
、DELETE
等。RestTemplate
是 Spring
框架的一部分,也因此在使用之前需要先导入 Spring
的相关依赖。
查看该类可以知道,RestTemplate
是在 org.springframework.web.client
包下,继承了 InterceptingHttpAccessor抽象类
并实现了 RestOperations
接口。官方说明:
RestTemplate
是同步客户端执行HTTP
请求,在底层HTTP
客户端库(如JDK HttpURLConnection
、Apache HttpComponents
等)上公开一个简单的模板方法 API。RestTemplate
通过HTTP
方法为常见场景提供了模板,此外还提供了支持不太常见情况的通用交换和执行方法。
RestTemplate
主要有以下几个特点:
GET
、POST
、PUT
还是 DELETE
等 HTTP
请求,RestTemplate
都提供了一致的接口,使得代码复用性更高。HTTP
消息转换器:RestTemplate
支持多种 HTTP
消息转换器,如 StringHttpMessageConverter
、FormHttpMessageConverter
、ByteArrayHttpMessageConverter
等,可以根据需要选择合适的消息转换器。HttpEntity
可以将自定义的请求体和请求头发送给服务器,同时也可以获取服务器返回的自定义响应体和响应头。HTTP
客户端:RestTemplate
默认使用 Apache HttpClient
作为 HTTP
客户端,但也可以通过修改配置使用其他 HTTP
客户端,如 OkHttp
、Apache HttpCore
等。RestTemplate
提供了统一的异常处理机制,对于 HTTP
请求的各种异常都能进行合理地处理。RestTemplate
中部分方法如下:
在使用 RestTemplate
中 API 之前需要创建它的实例。这里使用 RestTemplateBuilder
类中的 build()
方法构建,可以对构建的 RestTemplate
实例进行其他配置,比如设置根 URL、设置拦截器等。
以下是配置类 RestTemplateConfig
,主要就是获取 RestTemplate
实例。
@Configuration
public class RestTemplateConfig {
/**
* 获取RestTemplate实例
*
* @return
*/
@Bean
public RestTemplate getRestTemplate(){
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
// 这里可以对构建的RestTemplate实例进行其他配置
// ......
return restTemplateBuilder.build();
}
}
之后新建 RestTemplateArticle
类,该类封装了使用 RestTemplate
执行博文相关的 HTTP
请求的方法。整体结构如下:
@Component
public class RestTemplateArticle {
@Autowired
private RestTemplate restTemplate;
/**
* 根据用户ID获取博客Tab标签页统计数据
*
* @param username 用户ID
* @return LinkedHashMap 标签页统计数量
*/
public LinkedHashMap<String, Object> getTabTotal(String username) {}
/**
* 获取全部博文列表数据
*
* @param username 用户ID
* @param total 文章总数,注意服务器每页最高可获取100条,在这里我们每页查询50条
* @return ArrayList> 文章列表数据
*/
public ArrayList<LinkedHashMap<String, Object>> getBusinessBlogList(String username, int total) {}
/**
* 获取文章质量分数
*
* @param blogList 文章列表
* @return ArrayList> 文章质量分(文章信息+质量分信息)
*/
public ArrayList<LinkedHashMap<String, Object>> getArticleScore(ArrayList<LinkedHashMap<String, Object>> blogList) {}
}
可以看到该类中共有三个方法,分别发送3个 HTTP
请求。整体思路是前获取文章总数量,然后分页查询文章列表,最后将列表中每篇文章的质量分查询出来。
第一个方法是根据用户ID获取博客Tab标签页统计数据。 就是下图这里的统计数据,这里可以拿到我们的文章数量,对下一步分页查询文章列表有用。
接下来看该请求,还是非常容易找到的,是一个 GET
请求。
查看该请求的响应结果,是 JSON
格式的,不乱,中规中矩的。
我们该如何实现请求,获取响应数据呢?代码很简单,发送的 url
拼接上我们的 username
,通过 RestTemplate
的 getForEntity()
方法发送请求,并接收响应。将响应结果中的 data
数据取出来强转为 LinkedHashMap
集合。我们通过 IDEA 打断点,很容易知道它的数据类型。
/**
* 根据用户ID获取博客Tab标签页统计数据
*
* @param username 用户ID
* @return LinkedHashMap 标签页统计数量
*/
public LinkedHashMap<String, Object> getTabTotal(String username) {
LinkedHashMap<String, Object> linkedHashMap = new LinkedHashMap<>();
// 远程请求url
String url = "https://blog.csdn.net/community/home-api/v1/get-tab-total?username=" + username;
try {
// 使用RestTemplate发送GET请求并返回响应对象
ResponseEntity<JSONObject> response = restTemplate.getForEntity(url, JSONObject.class);
// 获取响应数据:data(返回数据项有 code、message、traceId、data)
linkedHashMap = (LinkedHashMap<String, Object>) response.getBody().get("data");
} catch (RestClientException e) {
e.printStackTrace();
}
return linkedHashMap;
}
第二个方法是根据用户ID和文章总数分页查询文章列表。 文章列表中有文章的链接地址,获取 url
后才能去查询质量分。
我们看请求的 url
,也是一个 GET
请求。你会发现很多熟悉的东西:当前是第一页,每页展示20条记录,业务类型是 blog
等。
那么响应数据是什么样子的呢?数据还是放在 data
中,data
中的 list
里放了20篇文章的数据,数据中有文章的ID、标题、描述、文章url、发布时间等等。
有了这些之后,我们如何去请求呢?
/**
* 获取全部博文列表数据
*
* @param username 用户ID
* @param total 文章总数,注意服务器每页最高可获取100条,在这里我们每页查询50条
* @return ArrayList> 文章列表数据
*/
public ArrayList<LinkedHashMap<String, Object>> getBusinessBlogList(String username, int total) {
ArrayList<LinkedHashMap<String, Object>> blogList = new ArrayList<>(total);
// 计算页数:通过总数和每页展示数量
int pageNum = total % 50 == 0 ? total / 50 : (total / 50) + 1;
// 请求每页数据
for (int i = 1; i <= pageNum; i++) {
// 远程请求url,拼接参数:每页固定展示50条,需要拼接页数
String url = "https://blog.csdn.net/community/home-api/v1/get-business-list?page=" + i + "&size=50&businessType=blog&orderby=&noMore=false&year=&month=&username=" + username;
try {
// 使用RestTemplate发送GET请求并返回响应对象
ResponseEntity<JSONObject> response = restTemplate.getForEntity(url, JSONObject.class);
// 获取响应数据:data(返回数据项有 code、message、traceId、data)
LinkedHashMap<String, Object> dataMap = (LinkedHashMap<String, Object>) response.getBody().get("data");
// 从data中获取文章列表数据
blogList.addAll((ArrayList<LinkedHashMap<String, Object>>) dataMap.get("list"));
} catch (RestClientException e) {
e.printStackTrace();
}
}
return blogList;
}
方法中需要传递2个参数,一个是用户ID,一个是文章总数。文章总数主要是用来计算分页的。之前打算直接将总数带入到url中,也就是第一页就查询全部,结果发送102条数据只查询出100条,说明每页最多支持查询100条。那就自己计算呗,这里我们每页查询50条,计算一下,102条,分三页查,也就是说我们发送三次请求。并将每次查询出来的文章列表数据追加到一起。
第三个方法就是根据文章列表数据查询每篇的质量分数了。 这里只能一个链接一个链接的查询。
我们先看请求的 url
,该请求是 POST
方式。
POST
请求携带参数是 url
。
我们得到的响应数据:文章id、分数、消息、发布时间。
我们的方法如下:
/**
* 获取文章质量分数
*
* @param blogList 文章列表
* @return ArrayList> 文章质量分(文章信息+质量分信息)
*/
public ArrayList<LinkedHashMap<String, Object>> getArticleScore(ArrayList<LinkedHashMap<String, Object>> blogList) {
ArrayList<LinkedHashMap<String, Object>> scoreList = new ArrayList<>(blogList.size());
// 远程请求url
String url = "https://bizapi.csdn.net/trends/api/v1/get-article-score";
// 设置请求头信息,以下为必须项
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", "application/json, text/plain, */*");
headers.set("X-Ca-Key", "203930474");
headers.set("X-Ca-Nonce", "45504a89-2e60-48af-af04-1612fd9cc268");
headers.set("X-Ca-Signature", "ZA/7jL7Nh7Te2hUJLWFCNt900GNZZInhPf1UwtKdpPg=");
headers.set("X-Ca-Signature-Headers", "x-ca-key,x-ca-nonce");
headers.set("X-Ca-Signed-Content-Type", "multipart/form-data");
// 设置媒体类型
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// 循环远程请求查询所有文章质量分数
for (LinkedHashMap<String, Object> blog : blogList) {
// 设置请求报头和正文
MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
requestBody.put("url", Collections.singletonList((String) blog.get("url")));
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
// 创建请求URI
URI uri = URI.create(url);
try {
// 使用RestTemplate发送POST请求并返回响应对象
ResponseEntity<JSONObject> response = restTemplate.postForEntity(uri, requestEntity, JSONObject.class);
// 获取响应数据:data(返回数据项有 code、message、traceId、data)
LinkedHashMap<String, Object> dataMap = (LinkedHashMap<String, Object>) response.getBody().get("data");
// 将文章数据和质量分合并
dataMap.putAll(blog);
// 将单篇质量分数信息添加到list集合中
scoreList.add(dataMap);
} catch (RestClientException e) {
e.printStackTrace();
}
}
return scoreList;
}
这里我们要设置请求头,有一些是必须项。之后要将查询到的文章列表进行遍历,查询单个文章的质量分。这里使用 post
请求方式,用到
RestTemplate.postForEntity()
方法,需求构建请求报头和正文。最后将获取的数据添加到 list
集合中。
之后创建了服务层,有接口和接口实现类。以下是接口实现类,主要是调用 RestTemplateArticle
中的方法,实现批量文章质量分的查询,并将结果进行整合。
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private RestTemplateArticle restTemplateArticle;
/**
* 获取所有文章质量分列表
*
* @param username 用户ID
* @return ArrayList> 文章质量分数
*/
@Override
public ArrayList<LinkedHashMap<String, Object>> getArticleScoreList(String username) {
ArrayList<LinkedHashMap<String, Object>> articleScoreList = new ArrayList<>();
// 判断传入的用户ID不为空
if (username != null && !"".equals(username)){
// 查询用户下文章总数
int blogTotal = (int) restTemplateArticle.getTabTotal(username).get("blog");
// 判断文章总数
if (blogTotal > 0){
// 查询用户下所有文章
ArrayList<LinkedHashMap<String, Object>> blogList = restTemplateArticle.getBusinessBlogList(username, blogTotal);
// 判断获取的文章列表数量
if (blogList.size() > 0){
// 查询文章质量分
articleScoreList = restTemplateArticle.getArticleScore(blogList);
}
}
}
return articleScoreList;
}
}
先根据用户ID查询文章总数,之后根据文章总数查询用户下的所有文章,再根据文章列表查询出质量分,这时质量分数据中就包含了博客列表信息。
前端请求 /allArticleScore
,调用业务层方法获取所有文章质量分列表,然后封装到 JSON
中响应给前端。
@RestController
public class ArticleController {
@Autowired
private ArticleService articleService;
/**
* 查询所有文章质量分数
*
* @param username 用户ID
* @return
*/
@GetMapping("/allArticleScore")
public JSONObject allArticleScore(String username){
// 获取所有文章质量分列表
ArrayList<LinkedHashMap<String, Object>> articleScoreList = articleService.getArticleScoreList(username);
// 封装查询结果,前端分页要求:rows、total
JSONObject jsonObject = new JSONObject();
jsonObject.put("rows", articleScoreList);
jsonObject.put("total", articleScoreList.size());
return jsonObject;
}
}
前端页面使用了 BootStrap
框架,表格展示使用到了 BootStrap-Table
扩展,非常方便的进行数据展示,分页展示,这里是一次性查询出全部数据,通过 BootStrap-Table
扩展假分页。同时它还提供了诸多便利的功能,比如排序、导出等。
<!doctype html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>博文质量分批量查询</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/bootstrap-icons/1.10.4/font/bootstrap-icons.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/bootstrap-table/1.21.4/bootstrap-table.min.css" rel="stylesheet">
</head>
<body>
<div class="container pt-5">
<div class="mt-5">
<form class="row justify-content-md-center">
<div class="col-6">
<div class="input-group">
<input class="form-control form-control-lg" type="text" id="username" value="Li_Ya_Fei" placeholder="请输入用户ID">
<button class="btn btn-outline-secondary" type="button" onclick="searchScore()">批量查询</button>
</div>
</div>
</form>
<div id="articleScore" class="mt-5">
<div id="toolbar">
<button type="button" class="btn btn-primary">
100~90 <span class="badge text-bg-secondary">0</span>
</button>
<button type="button" class="btn btn-info">
89~80 <span class="badge text-bg-secondary">0</span>
</button>
<button type="button" class="btn btn-success">
79~70 <span class="badge text-bg-secondary">0</span>
</button>
<button type="button" class="btn btn-warning">
69~60 <span class="badge text-bg-secondary">0</span>
</button>
<button type="button" class="btn btn-danger">
60以下 <span class="badge text-bg-secondary">0</span>
</button>
</div>
<table id="table"
data-toolbar="#toolbar"
data-locale="zh-CN"
data-url="allArticleScore"
data-show-columns="true"
data-pagination="true"
data-search="true"
data-show-export="true"
data-show-refresh="true"
data-show-fullscreen="true"
data-show-toggle="true">
<thead>
<tr>
<th data-field="articleId">文章ID</th>
<th data-field="title" data-formatter="titleFormatter">标题</th>
<th data-field="postTime">发布时间</th>
<th data-field="viewCount" data-sortable="true">浏览量</th>
<th data-field="score" data-sortable="true" data-cell-style="scoreCellStyle">质量分</th>
<th data-field="message">提示信息</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/bootstrap-table/1.21.4/bootstrap-table.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/bootstrap-table/1.21.4/locale/bootstrap-table-zh-CN.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/xlsx/0.18.5/xlsx.core.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jspdf/2.5.1/polyfills.umd.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/TableExport/5.2.0/js/tableexport.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/bootstrap-table/1.21.4/extensions/export/bootstrap-table-export.min.js"></script>
<script>
$(document).ready(function() {
// 初始化表格
$("#table").bootstrapTable({
onLoadSuccess: function (data) {
// 统计各分数段文章数量
let levelArray = [0, 0, 0, 0, 0];
$.each(data.rows, function(index, value) {
if (value.score >= 90){
levelArray[0]++;
} else if(value.score < 90 && value.score >= 80){
levelArray[1]++;
} else if(value.score < 80 && value.score >= 70 ){
levelArray[2]++;
} else if(value.score < 70 && value.score >= 60){
levelArray[3]++;
} else if (value.score < 60) {
levelArray[4]++;
}
});
// 统计数量展示
let levelSpanArray = $("#toolbar > button >span");
$.each(levelSpanArray, function (index, value) {
$(value).text(levelArray[index]);
});
}
});
});
// 查询文章质量分
function searchScore(){
$("#table").bootstrapTable("refresh", {url: 'allArticleScore?username=' + $("#username").val()});
}
// 文章标题格式化
function titleFormatter(value, row, index){
return `<a href="${row.url}" target="_blank">${value}</a>`;
}
// 单元格样式,根据分数段设置背景颜色
function scoreCellStyle(value, row, index){
let c = "";
if (value >= 90){
c = "bg-primary";
} else if(value < 90 && value >= 80){
c = "bg-info";
} else if(value < 80 && value >= 70 ){
c = "bg-success"
} else if(value < 70 && value >= 60){
c = "bg-warning";
} else if (value < 60) {
c = "bg-danger";
}
return {classes : c};
}
</script>
</body>
</html>
① 批量查询博客质量分入口,需要输入用户ID,不是昵称哦。
② 质量分数段数量统计,其实应该把平均分计算展示的。
③ 使用 bootstrap-table
扩展的搜索功能,可以进行简单数据搜索。
④ 也是扩展自带功能,可以刷新数据、切换视图、最大化,数据导出等。
⑤ 就是文章质量分列表了,展示文章ID、标题、发布时间、浏览量、质量分和提示信息。
⑥ 标题处给了链接,点击可以跳转到该博客页面。
⑦ 质量分这一列根据分数段设置了单元格的背景颜色。
⑧ 对文章列表进行了假分页。
虽然在批量查询博客质量分数的小项目中,很多功能都不是必要的,毕竟实际的需求并不大。但是现在实现下来自己也是有收获的。学习或者说接触了 RestTemplate
,并进行了简单使用。还复习了前端 BootStrap
框架,比较头疼。