CSDN博客接口基于java调用的x-ca-signature签名算法研究

写在前面

本人业余时间会写写CSDN的博客,查看下博客数据,展现量、阅读量什么的。在“作品数据-单篇文章分析”菜单中可以看到每篇文章的总体展现量、阅读量,要是想看每篇文章每日的访问量需要再次点击列表后边的“查看详情”显示的曲线图,一个一个点击着实有些麻烦,所以想通过调用接口的方式返回数据,把每篇文章的每日数据存起来,再设置个定时任务,就解放双手了。

找到的参考代码都是python的,没有java的,自己编码后在这里记录一下。

 接口选择

点击“单篇文章分析”,可以得到每篇文章的总体展现、阅读量,那么使用某篇文章的阅读量减去昨天此文章的阅读量,就是每日的访问量了,也不需要调用每篇文章的数据的接口,新建的文章也都能自动获取到了。

head分析

在每次请求接口的时候都会在请求头中传入cookie;x-ca-key;x-ca-nonce;x-ca-signature;x-ca-signature-headers这五个参数。简单的分析不难得出x-ca-key和x-ca-signature-headers是两个固定值,cookie是登录后的标识,会保持数天不变。而x-ca-signature和x-ca-signature-headers 每次请求的时候都不同,这就有点难顶了

进一步分析

进一步分析下上述5个参数如何生成的,cookie没什么好说的。

通过查看js源代码,搜索x-ca-key的值,看到下边这个方法

X-Ca-Key是固定的

X-Ca-Nonce最终通过De函数生成

X-Ca-Signature由方法名称、url、参数、固定值等计算获得

X-Ca-Nonce生成方法

    private static List<String> chats = new ArrayList<>();

    static {
        //生成 [a, b, c, d, e, f, g, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        for (int i = 0; i < 7; i++) {
            char c = (char) (i + 97);
            chats.add(String.valueOf(c));
        }
        for (int i = 0; i < 10; i++) {
            char c = (char) (i + 48);
            chats.add(String.valueOf(c));
        }
    }
    /**
     * 获取一次性访问key,参考js方法:
     * De = function() {
          return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (function(e) {
          var t = 16 * Math.random() | 0,
          n = "x" === e ? t: 3 & t | 8;
          return n.toString(16)
      }))}
     *
     * @return
     */
    private static String onceKey() {
        List<String> strs = new ArrayList<>();
        Random rd = new Random();
        for (int i = 0; i < 30; i++) {
            strs.add(chats.get(rd.nextInt(chats.size())));
        }
        return String.format("%s%s%s%s%s%s%s%s-%s%s%s%s-4%s%s%s-9%s%s%s-%s%s%s%s%s%s%s%s%s%s%s%s", strs.toArray());
    }

X-Ca-Signature生成方法

这里特别需要注意的是在生成x-ca-signature时,POST请求和GET请求并不相同,我们需要区分处理。最明显的两个区别是请求方法Method不同,content_type不同,下面POST方法并没有写完

private static String sign(String fullUrl, String method, String onceKey) throws Exception {
        final String ekey = "9znpamsyl2c7cdrr9sas0le9vbc3r6ba";
        final String xcakey = "203803574"; // 开发工具 network 请求头 x-ca-key

        String[] wholdUrl = fullUrl.split("\\?");
        String url, params = "";
        if ("get".equals(method)) {
            url = wholdUrl[0];
            params = wholdUrl[1];
        } else {
            url = wholdUrl[0];
        }
        String _url = url + (!"".equals(params) ? "?" + params : "");
        String to_enc = "";
        if ("get".equals(method)) {
            to_enc = String.format("GET\napplication/json, text/plain, */*\n\n\n\nx-ca-key:%s\nx-ca-nonce:%s\n%s", xcakey, onceKey, _url);
        } else {
             to_enc = String.format("POST\n%s\n\n%s\n\nx-ca-key:%s\nx-ca-nonce:%s\n%s"
                    , "application/json, text/plain, */*", "application/json;charset=UTF-8", xcakey, onceKey, _url);
        }
        return getSHA256StrJava(to_enc, ekey);
    }

    private static String getSHA256StrJava(String content, String secret) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
        mac.init(secret_key);
       // System.out.println("key: " + secret + " | 内容是:\n" + content);
        byte[] binaryData = mac.doFinal(content.getBytes());
        return Base64.encodeBase64String(binaryData);
    }

执行接口调用

       <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.8.1</version>
        </dependency>
//GET举例,我发布的文章列表
 public JSONObject getAllMyArticle() throws Exception {
        String host = "https://bizapi.csdn.net";
        String path = "/blog/phoenix/console/v1/data/single-article-list?action=down&page=1&size=40&type=date";
        String onceKey = onceKey();
        String sign = sign(path, "get", onceKey);
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(host + path)
                .addHeader("Accept", "application/json, text/plain, */*")
                // .addHeader("Accept-Encoding", "gzip, deflate, br") //若有设置,response为乱码
                .addHeader("Accept-Language", "zh-CN,zh;q=0.9")
                .addHeader("Connection", "keep-alive")
                .addHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36")
                .addHeader("origin", "https://mp.csdn.net")
                .addHeader("referer", "https://mp.csdn.net/mp_blog/analysis/article/single")
                .addHeader("sec-ch-ua", "\"Google Chrome\";v=\"105\", \"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"105\"")
                .addHeader("sec-ch-ua-mobile", "?0")
                .addHeader("sec-ch-ua-platform", "\"Windows\"")
                .addHeader("sec-fetch-dest", "empty")
                .addHeader("sec-fetch-mode", "cors")
                .addHeader("sec-fetch-site", "same-site")
                .addHeader("uri-name", "feige")
                //登录后的cokie
                .addHeader("cookie", cookie)
                .addHeader("x-ca-key", "203803574")  //根据浏览器开发者工具请求头设置
                .addHeader("x-ca-nonce", onceKey) //请求1次后作废
                .addHeader("x-ca-signature", sign) // 根据url、排序参数、onceKey、加密key,使用HmacSHA256算法生成的摘要
                .addHeader("x-ca-signature-headers", "x-ca-key,x-ca-nonce") //根据浏览器开发者工具请求头设置
                .build();
        Response response = client.newCall(request).execute();
        response.header("content-type", "application/json;charset=utf-8");
//        出现错误可以将此打开获取报错内容
//        System.out.println(response);
//        System.out.println(response.header("X-Ca-Error-Message"));
        String responseData = response.body().string();
       // System.out.println(responseData);
        return JSONObject.parseObject(responseData);
    }
//POST举例,修改个人信息
public static JSONObject updateMyInfo(JSONObject requestBodyStr) throws Exception {
        MediaType mediaType = MediaType.parse("application/json;charset=UTF-8");//!!!这个要大写
        RequestBody requestBody = RequestBody.create(requestBodyStr.toJSONString(),mediaType);
        String host = "https://bizapi.csdn.net";
        String path = "/community-personal/v1/edit-userInfo";
        String onceKey = onceKey();
        String sign = sign(path, "post", onceKey);
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(host + path)
                .addHeader("Accept", "application/json, text/plain, */*")
                // .addHeader("Accept-Encoding", "gzip, deflate, br") //若有设置,response为乱码
                .addHeader("Accept-Language", "zh-CN,zh;q=0.9")
                .addHeader("Connection", "keep-alive")
                .addHeader("Content-Type", "application/json;charset=UTF-8")
                .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36")
                .addHeader("Origin", "https://i.csdn.net")
                .addHeader("Referer", "https://i.csdn.net/")
                .addHeader("Sec-Ch-Ua", "\"Google Chrome\";v=\"105\", \"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"105\"")
                .addHeader("Sec-Ch-Ua-Mobile", "?0")
                .addHeader("Sec-Ch-Ua-Platform", "\"Windows\"")
                .addHeader("Sec-Fetch-Dest", "empty")
                .addHeader("Sec-Fetch-Mode", "cors")
                .addHeader("Sec-Fetch-Site", "same-site")
                .addHeader("uri-name", "feige")
                //登录后的cokie
                .addHeader("cookie", cookie)
                .addHeader("X-Ca-Key", xcakey)  //根据浏览器开发者工具请求头设置
                .addHeader("X-Ca-Nonce", onceKey) //请求1次后作废
                .addHeader("X-Ca-Signature", sign) // 根据url、排序参数、onceKey、加密key,使用HmacSHA256算法生成的摘要
                .addHeader("X-Ca-Signature-Headers", "x-ca-key,x-ca-nonce") //根据浏览器开发者工具请求头设置
                .post(requestBody)
                .build();
        Response response = client.newCall(request).execute();
        response.header("content-type", "application/json;charset=utf-8");
//        出现错误可以将此打开获取报错内容
//        System.out.println(response);
//        System.out.println(response.header("X-Ca-Error-Message"));
        String responseData = response.body().string();
     /*   System.out.println("--------------------------------");
        System.out.println(responseData);*/
        return JSONObject.parseObject(responseData);
    }

这里特别注意,生成X-Ca-Signature传入的path是不带域名的,且url的参数部分需要按照字母顺序升序排列,header里其他参数使用浏览器的的,否则容易出错

X-Ca-Signature签名排错方法

打开response的输出,获取header中X-Ca-Error-Message会返回具体的错误信息。如下图会提示正确的签名生成方式,将自己的重新调整就好,因为HTTP Header中无法表示换行,因此返回信息中的换行符都被替换成#,反过来传参时# 号使用 \n 替换

 总结

需要注意cookie会过期问题。工具本质上不难,就是调用CSDN的接口。难点主要是在于请求头中x-ca-nonce和x-ca-signature这两个参数比较难搞。搞定了这两个参数后面的调用逻辑就比较简单。比如本人使用今天获取的数据与前一天的数据做差,计算出每篇文章昨日访问量数据。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

as350144

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值