我用java分析了原神抽卡记录

起因

我们都知道原神抽卡是有保底机制的,但是游戏里面只能按页查看抽卡记录,并没有各种数据统计,为了能够优化大家的游戏体验,本文就带大家用java爬虫来获取抽卡信息。

抽卡信息api解析

由于我用的是安卓(鸿蒙)手机,所有就介绍安卓获取信息的办法。

对于每一个角色,在游戏内查看记录的时候都会有一个特定的api,我们需要获得这个api。

安卓(鸿蒙)的获取方法是 祈愿->查看历史记录->断网(手动断开手机的网络连接),然后把相应的api复制下来。

就是上面这个页面,断网后点击右上方橘色框中的按钮,然后就能得到你的查询链接 

这个链接看起来是个get请求,将这个链接复制到浏览器中就可以得到查询页面

大概就是上面这个样子。 

现在来详细看一下连接中带有的参数。

https://webstatic.mihoyo.com/hk4e/event/e20190909gacha/index.html?
authkey_ver=1&
sign_type=2&
auth_appid=webview_gacha&
init_type=301&
gacha_id=50eef7be3b7945930041f9f20310e0bbcb8a7a&
timestamp=1648597804&
lang=zh-cn&
device_type=mobile&
ext=%7b%22loc%22%3a%7b%22x%22%3a-206.83416748046876%2c%22y%22%3a201.1728973388672%2c%22z%22%3a-993.37060546875%7d%2c%22platform%22%3a%22Android%22%7d&
game_version=CNRELAndroid2.6.0_R6626848_S6641322_D6692205&
plat_type=android&
region=cn_gf01&
authkey=wR9769ie0pACSLWz2W3ggPLgAe6jwLyaCX8%2f7qE2ywEWqPDFceqGlSewiU7u0%2fS7osSNc%2fyZKtg2LOrM%2fzAsw5W%2bOEKRrtqMC4CNmARgbGag0PC5q071zjwKbQyBx4LpkPjr7%2f%2bkaBLORncy8NcB6IMiqsXDYYot6DmhbytWKPRGH6XHxZOK6avncs1B7dxJYISjv5OuyqIzfQ8fynDtei7EclBKT8GXpdfaPd3M0Dq4QfD9qd8757g%2f4wSPqbxBHwvl5yFUv0k4VQsmladDiEbeseLPDxHzB6VggTijG3UoMGgVLqSlprUAdLEpYDIsOjqMUTa5oywDBUqXF%2fvhAt7fzyx8Cf2orSUjjpJZUEEtJSltDLCJlNUJllysfbo84lOJnNeUEJdPn91D0%2bLN1UG8J7%2fRgmF9TBtbLGHzFXkMk0TGWSkuF%2f1FNKm6VJtNX7K1V4PoPdk6u%2bLbmH3Wz4SR6ZJXCIgNwor6SKrKa0RxtY6skwPuNHlMPTO9FwSo0c1FN75X8iJaNUO5MuZrPVHbLkRoTZdpjFOiRh1R0P2UY%2b5MIR6wErfS6u7kK0Zxyz8n9IUG%2fc2IfHrys0WogXmmikxXQwB8LR9%2b6N%2bB2xvNzr6tRMVIHdamAetaHMM3cXXt2Iy7FiANW2B8gYbW2ts1zvN38Bf4zC6RNfRk2w9oSvKnEUsNA5po9eaBs1YSeBYf1FCj21jRj11K8x%2ffuwRO4zSLEvdYBHp2PJzM56Ddiyxflo5Im8p3HlR1dd8IguzhYySzw7uy%2bw2F00jx9gW%2fDFR5CGKOXil%2bUfOUFsI7V1qg7GX%2fLi3dzJBu6Nt1YXmJZf6SjKuOmUNU7vEBd14MPsc%2bUen80MXUCN6a28GovAixc1IWbcS%2fBhNsxR3ZkSIaMGRLvSssgY91Ah8QJXkUr6zbFZcTXUz4LGIQUmpolI22Nj5wPX5iiDwexLGqQgwn%2fhS5MYuWDAPxeaSOIlqQuVvmva3S8d083E%2bN6XWZLs%2bSS0SZ%2bCnYQmM59G1sNNoQJUwx8krxFoWQVPSASQy4QM6pLXUYYzP2sY%2bm9TD%2bzdWcm4Z%2fe16%2bClAFbCk5Y9rYXgxIshLDUIMjhSlQJkp7rqalYPQu8IewhX1VjDbQ6fEjD%2b8gg6QeBKGytwivPCZJoSKh%2fO7ebEawvTeSVHBvw1M3iztKHB2nk6QxQiGMfh%2b56qu3Tr8KxxqI9Dq1CePwbfIRhHHP7oujqBoJ2xuAY0VCgzRFfUViPdT8gTrHE7QEE6hNi4ZpZvqkQYTTXUW%2fgqvNFYYvmGUDs19g%2b8M0gELlcmNN%2bBUFl4TUUH0n%2fjiAy1CXyMf00wUtuNHfw8smHW9f8ELlLIgQd%2bWsZA%3d%3d&
game_biz=hk4e_cn#/log

为了能看的清晰一点,我把参数都用换行隔开了,如果你要用这个链接对服务器进行请求的话,要把空格删掉。

我们按照传统的爬虫方法区看html标签,会发现,换页标签中并没有包含超链接。

这个时候就要去看网络流信息了,我们打开开发者工具后,点击Network标签页,就能看到网络流。

打开Network标签页然后刷新页面,会得到下列信息,其中包括了页面请求的各种资源,这些都不重要,重要的是我下图选中的请求

这个请求在每次换页的时候都要发一起,现在来看一下该请求的详细参数

https://hk4e-api.mihoyo.com/event/gacha_info/api/getGachaLog?
authkey_ver=1&
sign_type=2&
auth_appid=webview_gacha&
init_type=301&
gacha_id=50eef7be3b7945930041f9f20310e0bbcb8a7a&
timestamp=1648597804&
lang=zh-cn&
device_type=mobile&
ext=%7b%22loc%22%3a%7b%22x%22%3a-206.83416748046876%2c%22y%22%3a201.1728973388672%2c%22z%22%3a-993.37060546875%7d%2c%22platform%22%3a%22Android%22%7d&
game_version=CNRELAndroid2.6.0_R6626848_S6641322_D6692205&
plat_type=android&
region=cn_gf01& 
authkey=wR9769ie0pACSLWz2W3ggPLgAe6jwLyaCX8%2f7qE2ywEWqPDFceqGlSewiU7u0%2fS7osSNc%2fyZKtg2LOrM%2fzAsw5W%2bOEKRrtqMC4CNmARgbGag0PC5q071zjwKbQyBx4LpkPjr7%2f%2bkaBLORncy8NcB6IMiqsXDYYot6DmhbytWKPRGH6XHxZOK6avncs1B7dxJYISjv5OuyqIzfQ8fynDtei7EclBKT8GXpdfaPd3M0Dq4QfD9qd8757g%2f4wSPqbxBHwvl5yFUv0k4VQsmladDiEbeseLPDxHzB6VggTijG3UoMGgVLqSlprUAdLEpYDIsOjqMUTa5oywDBUqXF%2fvhAt7fzyx8Cf2orSUjjpJZUEEtJSltDLCJlNUJllysfbo84lOJnNeUEJdPn91D0%2bLN1UG8J7%2fRgmF9TBtbLGHzFXkMk0TGWSkuF%2f1FNKm6VJtNX7K1V4PoPdk6u%2bLbmH3Wz4SR6ZJXCIgNwor6SKrKa0RxtY6skwPuNHlMPTO9FwSo0c1FN75X8iJaNUO5MuZrPVHbLkRoTZdpjFOiRh1R0P2UY%2b5MIR6wErfS6u7kK0Zxyz8n9IUG%2fc2IfHrys0WogXmmikxXQwB8LR9%2b6N%2bB2xvNzr6tRMVIHdamAetaHMM3cXXt2Iy7FiANW2B8gYbW2ts1zvN38Bf4zC6RNfRk2w9oSvKnEUsNA5po9eaBs1YSeBYf1FCj21jRj11K8x%2ffuwRO4zSLEvdYBHp2PJzM56Ddiyxflo5Im8p3HlR1dd8IguzhYySzw7uy%2bw2F00jx9gW%2fDFR5CGKOXil%2bUfOUFsI7V1qg7GX%2fLi3dzJBu6Nt1YXmJZf6SjKuOmUNU7vEBd14MPsc%2bUen80MXUCN6a28GovAixc1IWbcS%2fBhNsxR3ZkSIaMGRLvSssgY91Ah8QJXkUr6zbFZcTXUz4LGIQUmpolI22Nj5wPX5iiDwexLGqQgwn%2fhS5MYuWDAPxeaSOIlqQuVvmva3S8d083E%2bN6XWZLs%2bSS0SZ%2bCnYQmM59G1sNNoQJUwx8krxFoWQVPSASQy4QM6pLXUYYzP2sY%2bm9TD%2bzdWcm4Z%2fe16%2bClAFbCk5Y9rYXgxIshLDUIMjhSlQJkp7rqalYPQu8IewhX1VjDbQ6fEjD%2b8gg6QeBKGytwivPCZJoSKh%2fO7ebEawvTeSVHBvw1M3iztKHB2nk6QxQiGMfh%2b56qu3Tr8KxxqI9Dq1CePwbfIRhHHP7oujqBoJ2xuAY0VCgzRFfUViPdT8gTrHE7QEE6hNi4ZpZvqkQYTTXUW%2fgqvNFYYvmGUDs19g%2b8M0gELlcmNN%2bBUFl4TUUH0n%2fjiAy1CXyMf00wUtuNHfw8smHW9f8ELlLIgQd%2bWsZA%3d%3d&
game_biz=hk4e_cn&
gacha_type=301&
page=1&
size=6&
end_id=0

 对比我们从游戏中获得的原始地址就会发现,他们的参数几乎一致,我把不一致的参数提取取出来,他们分别是

gacha_type=301&
page=1&
size=6&
end_id=0

这个就是我们分页的请求参数啦。

其中gacha_type表示的是卡池信息,游戏中一共提供了4个卡池

四个卡池的对应的数字分别是("新手祈愿",100),("常驻祈愿", 200),("活动祈愿1&2",301 ),("武器祈愿",302)。

pagesize就是分页的两个参数,比较容易理解,就不说了。

比较重要的是end_id这个参数,这个参数的值如果为0的话表示从头开始查,如果不为0就从传入的那个id开始,并且一次最多查询20条数据。

下面来看一个具体的请求例子,为了简单期间,我们把请求数量定为2,这样看起来比较直观。

{
	retcode: 0,
	message: "OK",
	data: {
		page: "5",
		size: "2",
		total: "0",
		list: [{
				uid: "198952358",
				gacha_type: "400",
				item_id: "",
				count: "1",
				time: "2022-04-09 10:59:41",
				name: "翡玉法球",
				lang: "zh-cn",
				item_type: "武器",
				rank_type: "3",
				id: "1649469960001828158"
			},
			{
				uid: "198952358",
				gacha_type: "400",
				item_id: "",
				count: "1",
				time: "2022-04-08 21:48:31",
				name: "以理服人",
				lang: "zh-cn",
				item_type: "武器",
				rank_type: "3",
				id: "1649423160001716358"
			}
		],
		region: "cn_gf01"
	}
}

使用java获取数据

有了上面的基础信息,相信你已经胸有成竹了。

在解析之前,需要准备好两个jar包

我们先定义一个实体类,接收接口返回的信息

public class ItemEntity {

    private String uid;
    private String item_id;
    private String item_type;
    private String count;
    private String name;
    private String gacha_type;
    private String time;
    private String id;
    private String lang;
    private String rank_type;

    public ItemEntity() {
    }

    public ItemEntity(String uid, String item_id, String item_type, String count, String name, String gacha_type, String time, String id, String lang, String rank_type) {
        this.uid = uid;
        this.item_id = item_id;
        this.item_type = item_type;
        this.count = count;
        this.name = name;
        this.gacha_type = gacha_type;
        this.time = time;
        this.id = id;
        this.lang = lang;
        this.rank_type = rank_type;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getItem_id() {
        return item_id;
    }

    public void setItem_id(String item_id) {
        this.item_id = item_id;
    }

    public String getItem_type() {
        return item_type;
    }

    public void setItem_type(String item_type) {
        this.item_type = item_type;
    }

    public String getCount() {
        return count;
    }

    public void setCount(String count) {
        this.count = count;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGacha_type() {
        return gacha_type;
    }

    public void setGacha_type(String gacha_type) {
        this.gacha_type = gacha_type;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getLang() {
        return lang;
    }

    public void setLang(String lang) {
        this.lang = lang;
    }

    public String getRank_type() {
        return rank_type;
    }

    public void setRank_type(String rank_type) {
        this.rank_type = rank_type;
    }

    @Override
    public String toString() {
        return "ItemEntity{" +
                "uid='" + uid + '\'' +
                ", item_id='" + item_id + '\'' +
                ", item_type='" + item_type + '\'' +
                ", count='" + count + '\'' +
                ", name='" + name + '\'' +
                ", gacha_type='" + gacha_type + '\'' +
                ", time='" + time + '\'' +
                ", id='" + id + '\'' +
                ", lang='" + lang + '\'' +
                ", rank_type='" + rank_type + '\'' +
                '}';
    }
}

然后使用一个小案例,解析json,并且将相应信息变成上面的实体类

public class MiHaYouSoup {

    public static void main(String[] args) throws Exception {
        // 这里填你自己的url
        String url = "https://hk4e-api.mihoyo.com/event/gacha_info/api/getGachaLog?authkey_ver=1&sign_type=2&auth_appid=webview_gacha&init_type=301&gacha_id=50eef7be3b7945930041f9f20310e0bbcb8a7a&timestamp=1648597804&lang=zh-cn&device_type=mobile&ext=%7b%22loc%22%3a%7b%22x%22%3a-206.83416748046876%2c%22y%22%3a201.1728973388672%2c%22z%22%3a-993.37060546875%7d%2c%22platform%22%3a%22Android%22%7d&game_version=CNRELAndroid2.6.0_R6626848_S6641322_D6692205&plat_type=android&region=cn_gf01&authkey=wR9769ie0pACSLWz2W3ggPLgAe6jwLyaCX8%2f7qE2ywEWqPDFceqGlSewiU7u0%2fS7osSNc%2fyZKtg2LOrM%2fzAsw5W%2bOEKRrtqMC4CNmARgbGag0PC5q071zjwKbQyBx4LpkPjr7%2f%2bkaBLORncy8NcB6IMiqsXDYYot6DmhbytWKPRGH6XHxZOK6avncs1B7dxJYISjv5OuyqIzfQ8fynDtei7EclBKT8GXpdfaPd3M0Dq4QfD9qd8757g%2f4wSPqbxBHwvl5yFUv0k4VQsmladDiEbeseLPDxHzB6VggTijG3UoMGgVLqSlprUAdLEpYDIsOjqMUTa5oywDBUqXF%2fvhAt7fzyx8Cf2orSUjjpJZUEEtJSltDLCJlNUJllysfbo84lOJnNeUEJdPn91D0%2bLN1UG8J7%2fRgmF9TBtbLGHzFXkMk0TGWSkuF%2f1FNKm6VJtNX7K1V4PoPdk6u%2bLbmH3Wz4SR6ZJXCIgNwor6SKrKa0RxtY6skwPuNHlMPTO9FwSo0c1FN75X8iJaNUO5MuZrPVHbLkRoTZdpjFOiRh1R0P2UY%2b5MIR6wErfS6u7kK0Zxyz8n9IUG%2fc2IfHrys0WogXmmikxXQwB8LR9%2b6N%2bB2xvNzr6tRMVIHdamAetaHMM3cXXt2Iy7FiANW2B8gYbW2ts1zvN38Bf4zC6RNfRk2w9oSvKnEUsNA5po9eaBs1YSeBYf1FCj21jRj11K8x%2ffuwRO4zSLEvdYBHp2PJzM56Ddiyxflo5Im8p3HlR1dd8IguzhYySzw7uy%2bw2F00jx9gW%2fDFR5CGKOXil%2bUfOUFsI7V1qg7GX%2fLi3dzJBu6Nt1YXmJZf6SjKuOmUNU7vEBd14MPsc%2bUen80MXUCN6a28GovAixc1IWbcS%2fBhNsxR3ZkSIaMGRLvSssgY91Ah8QJXkUr6zbFZcTXUz4LGIQUmpolI22Nj5wPX5iiDwexLGqQgwn%2fhS5MYuWDAPxeaSOIlqQuVvmva3S8d083E%2bN6XWZLs%2bSS0SZ%2bCnYQmM59G1sNNoQJUwx8krxFoWQVPSASQy4QM6pLXUYYzP2sY%2bm9TD%2bzdWcm4Z%2fe16%2bClAFbCk5Y9rYXgxIshLDUIMjhSlQJkp7rqalYPQu8IewhX1VjDbQ6fEjD%2b8gg6QeBKGytwivPCZJoSKh%2fO7ebEawvTeSVHBvw1M3iztKHB2nk6QxQiGMfh%2b56qu3Tr8KxxqI9Dq1CePwbfIRhHHP7oujqBoJ2xuAY0VCgzRFfUViPdT8gTrHE7QEE6hNi4ZpZvqkQYTTXUW%2fgqvNFYYvmGUDs19g%2b8M0gELlcmNN%2bBUFl4TUUH0n%2fjiAy1CXyMf00wUtuNHfw8smHW9f8ELlLIgQd%2bWsZA%3d%3d&game_biz=hk4e_cn&gacha_type=301&page=5&size=2&end_id=1649513160000977558";
        Document document = Jsoup.connect(url).header("Accept", "*/*").header("Accept-Encoding", "gzip, deflate")
                .header("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")
                .header("Content-Type", "application/json;charset=UTF-8")
                .header("Content-Type", "application/x-javascript;charset=UTF-8")
                .header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0")
                .timeout(10000).ignoreContentType(true).get();
        System.out.println(document.body().text());
        String jsonstr = document.body().text();
        // 将json字符串转为map
        Map<String, Object> map1 = (Map<String, Object>) JSON.parse(jsonstr);
        System.out.println(map1.get("data"));
        // 获取data中的list信息
        Map<String, Object> map2 = (Map<String, Object>) JSON.parse(map1.get("data").toString());
        List<ItemEntity> itemEntityList = JSON.parseArray(map2.get("list").toString(), ItemEntity.class);
        System.out.println(itemEntityList);
        for(int i=0; i<itemEntityList.size(); i++){
            System.out.println(itemEntityList.get(i));
        }
    }

}

运行上述代码,就可以发现,我们已经能够解析数据了

接下来要做的事情,就是编写更加复杂的逻辑,具体要做的事情就是,一个主接口,接收玩家的url然后将这个url进行一定处理后请求json数据,将所有数据都爬下来后再进行数据分析,最后返回结果。

首先将接收请求并且解析json的函数做一个封装

    // 接收url,并且将数据解析成实体类列表返回
    private List<ItemEntity> parsejson(String url){
        try {
            // 这里填你自己的url
            Document document = Jsoup.connect(url).header("Accept", "*/*").header("Accept-Encoding", "gzip, deflate")
                    .header("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")
                    .header("Content-Type", "application/json;charset=UTF-8")
                    .header("Content-Type", "application/x-javascript;charset=UTF-8")
                    .header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0")
                    .timeout(10000).ignoreContentType(true).get();
            //System.out.println(document.body().text());
            String jsonstr = document.body().text();
            // 将json字符串转为map
            Map<String, Object> map1 = (Map<String, Object>) JSON.parse(jsonstr);
            //System.out.println(map1.get("data"));
            // 获取data中的list信息
            Map<String, Object> map2 = (Map<String, Object>) JSON.parse(map1.get("data").toString());
            List<ItemEntity> itemEntityList = JSON.parseArray(map2.get("list").toString(), ItemEntity.class);
            /*System.out.println(itemEntityList);
            for (int i = 0; i < itemEntityList.size(); i++) {
                System.out.println(itemEntityList.get(i));
            }*/
            return itemEntityList;
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

 接着我们写一个转换url的函数,这个是最基础的转换,将原始的url转换为可以查询json的url

    // 处理url,将传入的url修改为接收json的url
    private String getURLDiffType(String url){
        StringBuilder sb = new StringBuilder();
        String base = "https://hk4e-api.mihoyo.com/event/gacha_info/api/getGachaLog?";
        sb.append(base);
        String attr = url.split("\\?")[1];
        String[] attrs = attr.split("#")[0].split("&");
        // 将参数拼装
        for(int i=0; i<attrs.length; i++){
            sb.append(attrs[i]);
            sb.append("&");
        }
        return sb.toString();
    }

然后我们需要一个主启动函数,调用这个函数就可以把四个卡池都遍历一遍,得到所有的数据

经过测试,我发现,接口中page参数不重要,只需要设置end_id和size就行,当end_id为0的时候,就表示从头开始读数据。

我们需要不断修改end_id知道返回的数据为空为止

    // 主启动函数,遍历卡池,得到所有数据
    private Map<String, List<ItemEntity>> getAllItem(String url){
        String[] gachatypes = new String[]{"100", "200", "301", "302"};
        // 定义一个Map用来存放四个卡池的数据
        Map<String, List<ItemEntity>> map = new HashMap<>();
        map.put("100", new ArrayList<>());
        map.put("200", new ArrayList<>());
        map.put("301", new ArrayList<>());
        map.put("302", new ArrayList<>());
        String baseurl = getURLDiffType(url);
        for(int i=0; i<gachatypes.length; i++){
            StringBuilder sb = new StringBuilder();
            sb.append(baseurl);
            sb.append("gacha_type=" + gachatypes[i] + "&");
            sb.append("page=1&");
            sb.append("size=20&");
            String baseurlplus = sb.toString();
            String end_id = "0";
            while(true){
                StringBuilder str = new StringBuilder();
                str.append(baseurlplus);
                str.append("end_id=" + end_id);
                System.out.println("查询卡池:" + gachatypes[i] + " end_id=" + end_id);
                List<ItemEntity> itemEntityList = parsejson(str.toString());
                // 如果查询到的数据为空表示查完了
                if(itemEntityList.size() == 0){
                    System.out.println("卡池(" + gachatypes[i] + ")查询完毕");
                    break;
                }
                // 将数据装到map里
                for(int j=0; j<itemEntityList.size(); j++){
                    map.get(gachatypes[i]).add(itemEntityList.get(j));
                }
                // 得到最后一个元素的end_id
                end_id = itemEntityList.get(itemEntityList.size()-1).getId();
                try {
                    // 每个查询需要间隔一段时间
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return map;
    }

最后我们可以针对得到的map进行数据分析

    // 对查询到的结果进行后处理操作
    private void postprocess(Map<String, List<ItemEntity>> ItemEntityMap){
        // 首先取出四个卡池的信息
        // 卡池分别是 新手卡池,常驻卡池,活动up卡池和武器卡池
        List<ItemEntity> kachi1 = ItemEntityMap.get("100");
        List<ItemEntity> kachi2 = ItemEntityMap.get("200");
        List<ItemEntity> kachi3 = ItemEntityMap.get("301");
        List<ItemEntity> kachi4 = ItemEntityMap.get("302");
        // 统计所有的数量
        int total_all = kachi1.size() + kachi2.size() + kachi3.size() + kachi4.size();
        // 统计武器的数量
        int total_wuqi = 0;
        // 统计角色的数量
        int total_juese = 0;
        // 统计3星数量
        int total_3star = 0;
        // 统计4星的数量
        int total_4star = 0;
        // 统计5星的数量
        int total_5star = 0;

        // 遍历四个卡池得到数据
        for(int i=0; i<kachi1.size(); i++){
            ItemEntity item = kachi1.get(i);
            if(item.getItem_type().equals("武器")){
                total_wuqi++;
            }
            if(item.getItem_type().equals("角色")){
                total_juese++;
            }
            if(item.getRank_type().equals("3")){
                total_3star++;
            }
            if(item.getRank_type().equals("4")){
                total_4star++;
            }
            if(item.getRank_type().equals("5")){
                total_5star++;
            }
        }
        for(int i=0; i<kachi2.size(); i++){
            ItemEntity item = kachi2.get(i);
            if(item.getItem_type().equals("武器")){
                total_wuqi++;
            }
            if(item.getItem_type().equals("角色")){
                total_juese++;
            }
            if(item.getRank_type().equals("3")){
                total_3star++;
            }
            if(item.getRank_type().equals("4")){
                total_4star++;
            }
            if(item.getRank_type().equals("5")){
                total_5star++;
            }
        }
        for(int i=0; i<kachi3.size(); i++){
            ItemEntity item = kachi3.get(i);
            if(item.getItem_type().equals("武器")){
                total_wuqi++;
            }
            if(item.getItem_type().equals("角色")){
                total_juese++;
            }
            if(item.getRank_type().equals("3")){
                total_3star++;
            }
            if(item.getRank_type().equals("4")){
                total_4star++;
            }
            if(item.getRank_type().equals("5")){
                total_5star++;
            }
        }
        for(int i=0; i<kachi4.size(); i++){
            ItemEntity item = kachi4.get(i);
            if(item.getItem_type().equals("武器")){
                total_wuqi++;
            }
            if(item.getItem_type().equals("角色")){
                total_juese++;
            }
            if(item.getRank_type().equals("3")){
                total_3star++;
            }
            if(item.getRank_type().equals("4")){
                total_4star++;
            }
            if(item.getRank_type().equals("5")){
                total_5star++;
            }
        }

        // 打印数据
        System.out.println("所有卡池的总物品数是:" + total_all);
        System.out.println(String.format("新手卡池抽卡总数为:%d \n常驻卡池抽卡总数为:%d \n活动up卡池抽卡总数为:%d \n武器卡池抽卡总数为:%d",
                kachi1.size(), kachi2.size(), kachi3.size(), kachi4.size()));
        System.out.println("总角色数是:" + total_juese + " 抽到角色的概率为:" + String.format("%.4f", (double)total_juese/total_all));
        System.out.println("总武器数是:" + total_wuqi + " 抽到武器的概率为:" + String.format("%.4f", (double)total_wuqi/total_all));
        System.out.println("3星的数量是:" + total_3star + " 抽到3星的概率为:" + String.format("%.4f", (double)total_3star/total_all));
        System.out.println("4星的数量是:" + total_4star + " 抽到4星的概率为:" + String.format("%.4f", (double)total_4star/total_all));
        System.out.println("3星的数量是:" + total_5star + " 抽到3星的概率为:" + String.format("%.4f", (double)total_5star/total_all));
    }

总的代码如下

public class MiHaYouSoup {

    public static void main(String[] args) throws Exception {
        //String url = "https://hk4e-api.mihoyo.com/event/gacha_info/api/getGachaLog?authkey_ver=1&sign_type=2&auth_appid=webview_gacha&init_type=301&gacha_id=50eef7be3b7945930041f9f20310e0bbcb8a7a&timestamp=1648597804&lang=zh-cn&device_type=mobile&ext=%7b%22loc%22%3a%7b%22x%22%3a-206.83416748046876%2c%22y%22%3a201.1728973388672%2c%22z%22%3a-993.37060546875%7d%2c%22platform%22%3a%22Android%22%7d&game_version=CNRELAndroid2.6.0_R6626848_S6641322_D6692205&plat_type=android&region=cn_gf01&authkey=wR9769ie0pACSLWz2W3ggPLgAe6jwLyaCX8%2f7qE2ywEWqPDFceqGlSewiU7u0%2fS7osSNc%2fyZKtg2LOrM%2fzAsw5W%2bOEKRrtqMC4CNmARgbGag0PC5q071zjwKbQyBx4LpkPjr7%2f%2bkaBLORncy8NcB6IMiqsXDYYot6DmhbytWKPRGH6XHxZOK6avncs1B7dxJYISjv5OuyqIzfQ8fynDtei7EclBKT8GXpdfaPd3M0Dq4QfD9qd8757g%2f4wSPqbxBHwvl5yFUv0k4VQsmladDiEbeseLPDxHzB6VggTijG3UoMGgVLqSlprUAdLEpYDIsOjqMUTa5oywDBUqXF%2fvhAt7fzyx8Cf2orSUjjpJZUEEtJSltDLCJlNUJllysfbo84lOJnNeUEJdPn91D0%2bLN1UG8J7%2fRgmF9TBtbLGHzFXkMk0TGWSkuF%2f1FNKm6VJtNX7K1V4PoPdk6u%2bLbmH3Wz4SR6ZJXCIgNwor6SKrKa0RxtY6skwPuNHlMPTO9FwSo0c1FN75X8iJaNUO5MuZrPVHbLkRoTZdpjFOiRh1R0P2UY%2b5MIR6wErfS6u7kK0Zxyz8n9IUG%2fc2IfHrys0WogXmmikxXQwB8LR9%2b6N%2bB2xvNzr6tRMVIHdamAetaHMM3cXXt2Iy7FiANW2B8gYbW2ts1zvN38Bf4zC6RNfRk2w9oSvKnEUsNA5po9eaBs1YSeBYf1FCj21jRj11K8x%2ffuwRO4zSLEvdYBHp2PJzM56Ddiyxflo5Im8p3HlR1dd8IguzhYySzw7uy%2bw2F00jx9gW%2fDFR5CGKOXil%2bUfOUFsI7V1qg7GX%2fLi3dzJBu6Nt1YXmJZf6SjKuOmUNU7vEBd14MPsc%2bUen80MXUCN6a28GovAixc1IWbcS%2fBhNsxR3ZkSIaMGRLvSssgY91Ah8QJXkUr6zbFZcTXUz4LGIQUmpolI22Nj5wPX5iiDwexLGqQgwn%2fhS5MYuWDAPxeaSOIlqQuVvmva3S8d083E%2bN6XWZLs%2bSS0SZ%2bCnYQmM59G1sNNoQJUwx8krxFoWQVPSASQy4QM6pLXUYYzP2sY%2bm9TD%2bzdWcm4Z%2fe16%2bClAFbCk5Y9rYXgxIshLDUIMjhSlQJkp7rqalYPQu8IewhX1VjDbQ6fEjD%2b8gg6QeBKGytwivPCZJoSKh%2fO7ebEawvTeSVHBvw1M3iztKHB2nk6QxQiGMfh%2b56qu3Tr8KxxqI9Dq1CePwbfIRhHHP7oujqBoJ2xuAY0VCgzRFfUViPdT8gTrHE7QEE6hNi4ZpZvqkQYTTXUW%2fgqvNFYYvmGUDs19g%2b8M0gELlcmNN%2bBUFl4TUUH0n%2fjiAy1CXyMf00wUtuNHfw8smHW9f8ELlLIgQd%2bWsZA%3d%3d&game_biz=hk4e_cn&gacha_type=301&page=5&size=2&end_id=1649513160000977558";
        String url = "https://webstatic.mihoyo.com/hk4e/event/e20190909gacha/index.html?authkey_ver=1&sign_type=2&auth_appid=webview_gacha&init_type=301&gacha_id=50eef7be3b7945930041f9f20310e0bbcb8a7a&timestamp=1648597804&lang=zh-cn&device_type=mobile&ext=%7b%22loc%22%3a%7b%22x%22%3a-206.83416748046876%2c%22y%22%3a201.1728973388672%2c%22z%22%3a-993.37060546875%7d%2c%22platform%22%3a%22Android%22%7d&game_version=CNRELAndroid2.6.0_R6626848_S6641322_D6692205&plat_type=android&region=cn_gf01&authkey=wR9769ie0pACSLWz2W3ggPLgAe6jwLyaCX8%2f7qE2ywEWqPDFceqGlSewiU7u0%2fS7osSNc%2fyZKtg2LOrM%2fzAsw5W%2bOEKRrtqMC4CNmARgbGag0PC5q071zjwKbQyBx4LpkPjr7%2f%2bkaBLORncy8NcB6IMiqsXDYYot6DmhbytWKPRGH6XHxZOK6avncs1B7dxJYISjv5OuyqIzfQ8fynDtei7EclBKT8GXpdfaPd3M0Dq4QfD9qd8757g%2f4wSPqbxBHwvl5yFUv0k4VQsmladDiEbeseLPDxHzB6VggTijG3UoMGgVLqSlprUAdLEpYDIsOjqMUTa5oywDBUqXF%2fvhAt7fzyx8Cf2orSUjjpJZUEEtJSltDLCJlNUJllysfbo84lOJnNeUEJdPn91D0%2bLN1UG8J7%2fRgmF9TBtbLGHzFXkMk0TGWSkuF%2f1FNKm6VJtNX7K1V4PoPdk6u%2bLbmH3Wz4SR6ZJXCIgNwor6SKrKa0RxtY6skwPuNHlMPTO9FwSo0c1FN75X8iJaNUO5MuZrPVHbLkRoTZdpjFOiRh1R0P2UY%2b5MIR6wErfS6u7kK0Zxyz8n9IUG%2fc2IfHrys0WogXmmikxXQwB8LR9%2b6N%2bB2xvNzr6tRMVIHdamAetaHMM3cXXt2Iy7FiANW2B8gYbW2ts1zvN38Bf4zC6RNfRk2w9oSvKnEUsNA5po9eaBs1YSeBYf1FCj21jRj11K8x%2ffuwRO4zSLEvdYBHp2PJzM56Ddiyxflo5Im8p3HlR1dd8IguzhYySzw7uy%2bw2F00jx9gW%2fDFR5CGKOXil%2bUfOUFsI7V1qg7GX%2fLi3dzJBu6Nt1YXmJZf6SjKuOmUNU7vEBd14MPsc%2bUen80MXUCN6a28GovAixc1IWbcS%2fBhNsxR3ZkSIaMGRLvSssgY91Ah8QJXkUr6zbFZcTXUz4LGIQUmpolI22Nj5wPX5iiDwexLGqQgwn%2fhS5MYuWDAPxeaSOIlqQuVvmva3S8d083E%2bN6XWZLs%2bSS0SZ%2bCnYQmM59G1sNNoQJUwx8krxFoWQVPSASQy4QM6pLXUYYzP2sY%2bm9TD%2bzdWcm4Z%2fe16%2bClAFbCk5Y9rYXgxIshLDUIMjhSlQJkp7rqalYPQu8IewhX1VjDbQ6fEjD%2b8gg6QeBKGytwivPCZJoSKh%2fO7ebEawvTeSVHBvw1M3iztKHB2nk6QxQiGMfh%2b56qu3Tr8KxxqI9Dq1CePwbfIRhHHP7oujqBoJ2xuAY0VCgzRFfUViPdT8gTrHE7QEE6hNi4ZpZvqkQYTTXUW%2fgqvNFYYvmGUDs19g%2b8M0gELlcmNN%2bBUFl4TUUH0n%2fjiAy1CXyMf00wUtuNHfw8smHW9f8ELlLIgQd%2bWsZA%3d%3d&game_biz=hk4e_cn#/log";
        MiHaYouSoup miHaYouSoup = new MiHaYouSoup();
        //System.out.println(miHaYouSoup.getURLDiffType(url));
        Map<String, List<ItemEntity>> ItemEntityMap = miHaYouSoup.getAllItem(url);
        //System.out.println(ItemEntityMap);
        miHaYouSoup.postprocess(ItemEntityMap);
    }

    // 对查询到的结果进行后处理操作
    private void postprocess(Map<String, List<ItemEntity>> ItemEntityMap){
        // 首先取出四个卡池的信息
        // 卡池分别是 新手卡池,常驻卡池,活动up卡池和武器卡池
        List<ItemEntity> kachi1 = ItemEntityMap.get("100");
        List<ItemEntity> kachi2 = ItemEntityMap.get("200");
        List<ItemEntity> kachi3 = ItemEntityMap.get("301");
        List<ItemEntity> kachi4 = ItemEntityMap.get("302");
        // 统计所有的数量
        int total_all = kachi1.size() + kachi2.size() + kachi3.size() + kachi4.size();
        // 统计武器的数量
        int total_wuqi = 0;
        // 统计角色的数量
        int total_juese = 0;
        // 统计3星数量
        int total_3star = 0;
        // 统计4星的数量
        int total_4star = 0;
        // 统计5星的数量
        int total_5star = 0;

        // 遍历四个卡池得到数据
        for(int i=0; i<kachi1.size(); i++){
            ItemEntity item = kachi1.get(i);
            if(item.getItem_type().equals("武器")){
                total_wuqi++;
            }
            if(item.getItem_type().equals("角色")){
                total_juese++;
            }
            if(item.getRank_type().equals("3")){
                total_3star++;
            }
            if(item.getRank_type().equals("4")){
                total_4star++;
            }
            if(item.getRank_type().equals("5")){
                total_5star++;
            }
        }
        for(int i=0; i<kachi2.size(); i++){
            ItemEntity item = kachi2.get(i);
            if(item.getItem_type().equals("武器")){
                total_wuqi++;
            }
            if(item.getItem_type().equals("角色")){
                total_juese++;
            }
            if(item.getRank_type().equals("3")){
                total_3star++;
            }
            if(item.getRank_type().equals("4")){
                total_4star++;
            }
            if(item.getRank_type().equals("5")){
                total_5star++;
            }
        }
        for(int i=0; i<kachi3.size(); i++){
            ItemEntity item = kachi3.get(i);
            if(item.getItem_type().equals("武器")){
                total_wuqi++;
            }
            if(item.getItem_type().equals("角色")){
                total_juese++;
            }
            if(item.getRank_type().equals("3")){
                total_3star++;
            }
            if(item.getRank_type().equals("4")){
                total_4star++;
            }
            if(item.getRank_type().equals("5")){
                total_5star++;
            }
        }
        for(int i=0; i<kachi4.size(); i++){
            ItemEntity item = kachi4.get(i);
            if(item.getItem_type().equals("武器")){
                total_wuqi++;
            }
            if(item.getItem_type().equals("角色")){
                total_juese++;
            }
            if(item.getRank_type().equals("3")){
                total_3star++;
            }
            if(item.getRank_type().equals("4")){
                total_4star++;
            }
            if(item.getRank_type().equals("5")){
                total_5star++;
            }
        }

        // 打印数据
        System.out.println("所有卡池的总物品数是:" + total_all);
        System.out.println(String.format("新手卡池抽卡总数为:%d \n常驻卡池抽卡总数为:%d \n活动up卡池抽卡总数为:%d \n武器卡池抽卡总数为:%d",
                kachi1.size(), kachi2.size(), kachi3.size(), kachi4.size()));
        System.out.println("总角色数是:" + total_juese + " 抽到角色的概率为:" + String.format("%.4f", (double)total_juese/total_all));
        System.out.println("总武器数是:" + total_wuqi + " 抽到武器的概率为:" + String.format("%.4f", (double)total_wuqi/total_all));
        System.out.println("3星的数量是:" + total_3star + " 抽到3星的概率为:" + String.format("%.4f", (double)total_3star/total_all));
        System.out.println("4星的数量是:" + total_4star + " 抽到4星的概率为:" + String.format("%.4f", (double)total_4star/total_all));
        System.out.println("3星的数量是:" + total_5star + " 抽到3星的概率为:" + String.format("%.4f", (double)total_5star/total_all));
    }

    // 主启动函数,遍历卡池,得到所有数据
    private Map<String, List<ItemEntity>> getAllItem(String url){
        String[] gachatypes = new String[]{"100", "200", "301", "302"};
        // 定义一个Map用来存放四个卡池的数据
        Map<String, List<ItemEntity>> map = new HashMap<>();
        map.put("100", new ArrayList<>());
        map.put("200", new ArrayList<>());
        map.put("301", new ArrayList<>());
        map.put("302", new ArrayList<>());
        String baseurl = getURLDiffType(url);
        for(int i=0; i<gachatypes.length; i++){
            StringBuilder sb = new StringBuilder();
            sb.append(baseurl);
            sb.append("gacha_type=" + gachatypes[i] + "&");
            sb.append("page=1&");
            sb.append("size=20&");
            String baseurlplus = sb.toString();
            String end_id = "0";
            while(true){
                StringBuilder str = new StringBuilder();
                str.append(baseurlplus);
                str.append("end_id=" + end_id);
                System.out.println("查询卡池:" + gachatypes[i] + " end_id=" + end_id);
                List<ItemEntity> itemEntityList = parsejson(str.toString());
                // 如果查询到的数据为空表示查完了
                if(itemEntityList.size() == 0){
                    System.out.println("卡池(" + gachatypes[i] + ")查询完毕");
                    break;
                }
                // 将数据装到map里
                for(int j=0; j<itemEntityList.size(); j++){
                    map.get(gachatypes[i]).add(itemEntityList.get(j));
                }
                // 得到最后一个元素的end_id
                end_id = itemEntityList.get(itemEntityList.size()-1).getId();
                try {
                    // 每个查询需要间隔一段时间
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return map;
    }


    // 处理url,将传入的url修改为接收json的url
    private String getURLDiffType(String url){
        StringBuilder sb = new StringBuilder();
        String base = "https://hk4e-api.mihoyo.com/event/gacha_info/api/getGachaLog?";
        sb.append(base);
        String attr = url.split("\\?")[1];
        String[] attrs = attr.split("#")[0].split("&");
        // 将参数拼装
        for(int i=0; i<attrs.length; i++){
            sb.append(attrs[i]);
            sb.append("&");
        }
        return sb.toString();
    }



    // 接收url,并且将数据解析成实体类列表返回
    private List<ItemEntity> parsejson(String url){
        try {
            // 这里填你自己的url
            Document document = Jsoup.connect(url).header("Accept", "*/*").header("Accept-Encoding", "gzip, deflate")
                    .header("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")
                    .header("Content-Type", "application/json;charset=UTF-8")
                    .header("Content-Type", "application/x-javascript;charset=UTF-8")
                    .header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0")
                    .timeout(10000).ignoreContentType(true).get();
            //System.out.println(document.body().text());
            String jsonstr = document.body().text();
            // 将json字符串转为map
            Map<String, Object> map1 = (Map<String, Object>) JSON.parse(jsonstr);
            //System.out.println(map1.get("data"));
            // 获取data中的list信息
            Map<String, Object> map2 = (Map<String, Object>) JSON.parse(map1.get("data").toString());
            List<ItemEntity> itemEntityList = JSON.parseArray(map2.get("list").toString(), ItemEntity.class);
            /*System.out.println(itemEntityList);
            for (int i = 0; i < itemEntityList.size(); i++) {
                System.out.println(itemEntityList.get(i));
            }*/
            return itemEntityList;
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

}

 运行结果

查询卡池:100 end_id=0
查询卡池:100 end_id=1646471160001247058
卡池(100)查询完毕
查询卡池:200 end_id=0
查询卡池:200 end_id=1648631160003490058
查询卡池:200 end_id=1646481960001005358
查询卡池:200 end_id=1646481960001004558
卡池(200)查询完毕
查询卡池:301 end_id=0
查询卡池:301 end_id=1649070360000868658
查询卡池:301 end_id=1648609560028066458
查询卡池:301 end_id=1647774360000571358
查询卡池:301 end_id=1646834760003882458
卡池(301)查询完毕
查询卡池:302 end_id=0
卡池(302)查询完毕
所有卡池的总物品数是:132
新手卡池抽卡总数为:10 
常驻卡池抽卡总数为:48 
活动up卡池抽卡总数为:74 
武器卡池抽卡总数为:0
总角色数是:10 抽到角色的概率为:0.0758
总武器数是:122 抽到武器的概率为:0.9242
3星的数量是:117 抽到3星的概率为:0.8864
4星的数量是:15 抽到4星的概率为:0.1136
3星的数量是:0 抽到3星的概率为:0.0000

可以看到我抽卡还是挺非的哈哈。当然,在数据处理的部分,我只是做了一个简单的案例,你可以根据你的需求进行其他的分析。如果把这个功能集成到服务器里,就可以做一个提供查询接口的网站或者微信公众号啦,然后可以对所有进行查询的数据进行排名,看看谁是欧皇,谁是非酋。

总结

本文是java爬虫的一个小案例,相信可以给你提供帮助。

最后,就用蒙德最强战神派蒙来结束本文吧

  • 24
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值