文章目录
- 一、分析
- 二、代码
- 1、前端代码
- 2、后端代码
- 三、实现效果
- 四、总结
- 1、出现安全验证
- 2、401 Unauthorized: [no body]
一、分析
官方提供的质量分查询入口:CSDN质量分
输入我们要查的文章即可,比如:https://blog.csdn.net/qq_36433289/article/details/132909403?spm=1001.2014.3001.5502
请求接口:https://bizapi.csdn.net/trends/api/v1/get-article-score
请求参数:https://blog.csdn.net/qq_36433289/article/details/132909403?spm=1001.2014.3001.5502
返回参数:
{
"code": 200,
"message": "success",
"data": {
"article_id": "132909403",
"score": 93,
"message": "文章质量良好",
"post_time": "2023-09-18 20:42:24"
}
}
所以,只要我们得到我们的所有文章链接,然后遍历请求https://bizapi.csdn.net/trends/api/v1/get-article-score
,组装结果就行
二、代码
1、前端代码
个人博客质量分查看页面
<!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="qq_36433289" 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" data-sortable="true">发布时间</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: 'http://127.0.0.1:8880/ceshi/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>
注:http://127.0.0.1:8880/ceshi/allArticleScore?username=
需要替换你的接口
2、后端代码
业务接口
public interface IArticleService {
ArrayList<LinkedHashMap<String, Object>> getArticleScoreList(String username);
}
业务详细实现代码
@Service
public class ArticleServiceImpl implements IArticleService {
@Autowired
private RestTemplateArticle restTemplateArticle;
/**
* 获取所有文章质量分列表
*
* @param username 用户ID
* @return ArrayList<LinkedHashMap<String, Object>> 文章质量分数
*/
@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;
}
}
一共需要请求3个接口,具体封装如下:
@Component
public class RestTemplateArticle {
private static final String CA_KEY = "xxx1";
private static final String CA_Nonce = "xxx2";
private static final String CA_Signature = "xxx3";
@Autowired
private RestTemplate restTemplate;
/**
* 根据用户ID获取博客Tab标签页统计数据
*
* @param username 用户ID
* @return LinkedHashMap<String, Object> 标签页统计数量
*/
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;
}
/**
* 获取全部博文列表数据
*
* @param username 用户ID
* @param total 文章总数,注意服务器每页最高可获取100条,在这里我们每页查询50条
* @return ArrayList<LinkedHashMap < String, Object>> 文章列表数据
*/
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=&User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69&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;
}
/**
* 获取文章质量分数
*
* @param blogList 文章列表
* @return ArrayList<LinkedHashMap < String, Object>> 文章质量分(文章信息+质量分信息)
*/
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", CA_KEY);
headers.set("X-Ca-Nonce", CA_Nonce);
headers.set("X-Ca-Signature", CA_Signature);
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;
}
}
其中, CA_KEY = "xxx1";CA_Nonce = "xxx2";CA_Signature = "xxx3";
需要替换你自己博客的,具体可以随便打开一个页面,然后F12查看,具体如下:
对外暴露的接口
@RestController
public class ArticleController {
@Autowired
private IArticleService 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;
}
}
三、实现效果
四、总结
1、出现安全验证
获取获取全部博文列表数据,请求getBusinessBlogList出现以下异常
<head>
<meta charset="utf-8">
<title>请进行安全验证</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta content="width=device-width, initial-sc... (2383 bytes)]
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:123)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:782)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:740)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:674)
at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:342)
直接访问链接出现以下情况
我们可以在页面通过安全验证,得到返回的参数如下:
然后在代码中手动组装我们的参数
String jsonString = "data.list里面的参数";
ObjectMapper objectMapper = new ObjectMapper();
ArrayList<LinkedHashMap<String, Object>> blogList = objectMapper.readValue(jsonString, new TypeReference<ArrayList<LinkedHashMap<String, Object>>>() {});
2、401 Unauthorized: [no body]
确保这几个参数CA_KEY = “xxx1”;CA_Nonce = “xxx2”;CA_Signature = "xxx3"替换为正确的