zoukankan      html  css  js  c++  java
  • 数据采集实战(三)-- 王者荣耀2021世冠数据

    1. 概述

    王者荣耀是一直都挺喜欢的一个手游,玩了好几年,最近一段开始喜欢看比赛,所以想着采集点数据看看各个战队或者选手的情况。
    顺便也练习练习 puppeteer 的使用。

    数据来源于:尚牛电竞

    2. 采集流程

    王者荣耀最近正在进行的最大比赛就是 2021世冠杯,所以就选择采集这个赛事的数据。
    尚牛电竞 网站上,已经按照战队,选手和英雄分好类了,并且网站不需要登录就能看到数据。

    image.png

    三组数据直接对应不同的URL进行采集即可,没有复杂的流程,唯一需要注意的地方是对Logo头像的小图片的保存。

    2.1 各个数据的采集

    image.png

    积分榜的数据是空的,战队榜选手榜英雄榜的数据可以获取。

    3种数据的URL分别为:

    const urls = [
      {
        url: "https://www.shangniu.cn/gdall/kog?tab=0&pid=40008&tid=45",
        name: "战队榜",
      },
      {
        url: "https://www.shangniu.cn/gdall/kog?tab=1&pid=40008&tid=45",
        name: "选手榜",
      },
      {
        url: "https://www.shangniu.cn/gdall/kog?tab=2&pid=40008&tid=45",
        name: "英雄榜",
      },
    ];
    

    2.1.1 战队数据的采集和解析

    // 战队数据
    const teamData = async (browser, page, url) => {
      await page.goto(url);
    
      // 解析页面
      /*
       * 0. logo: 战队logo
       * 1. name: 战队名称
       * 2. matchCount: 比赛场次
       * 3. matchBoxCount: 比赛局数
       * 4. averageTime: 场均时长
       * 5. winRate: 总胜率
       * 6. blueWinRate: 蓝方胜率
       * 7. redWinRate: 红方胜率
       * 8. KDA: KDA
       * 9. averageKill: 场均击杀
       * 10. averageDie: 场均死亡
       * 11. averageAssit: 场均助攻
       * 12. averageOutput: 分均输出
       * 13. averageEconomic: 分均经济
       * 14. liveRate: 生存率
       * 15. firstBloodRate: 一血率
       * 16. firstTowerRate: 一塔率
       * 17. averageTower: 场均推塔
       * 18. averageCoverTower: 场均被推塔
       * 19. averageTyrants: 场均暴君
       * 20. tyrantsControlRate: 暴君控制率
       * 21. averageDominates: 场均主宰
       * 22. dominatesControlRate: 主宰控制率
       */
    
      const data = [];
      const rows = await page.$$("#scroll-table > .tbody > .row");
    
      for (const row of rows) {
        let line = [];
        const cols = await row.$$(".td");
    
        line[0] = await cols[1].$eval("a > img", (node) =>
          node.getAttribute("src")
        );
        line[1] = await cols[1].$eval(
          ".right-name > .item-name",
          (node) => node.innerText
        );
        line[2] = await page.evaluate((node) => node.innerText, cols[2]);
        line[3] = await page.evaluate((node) => node.innerText, cols[3]);
        line[4] = await page.evaluate((node) => node.innerText, cols[4]);
        line[5] = await page.evaluate((node) => node.innerText, cols[5]);
        line[6] = await page.evaluate((node) => node.innerText, cols[6]);
        line[7] = await page.evaluate((node) => node.innerText, cols[7]);
        line[8] = await cols[8].$eval("div > .kda", (node) => node.innerText);
        line[9] = await page.evaluate((node) => node.innerText, cols[9]);
        line[10] = await page.evaluate((node) => node.innerText, cols[10]);
        line[11] = await page.evaluate((node) => node.innerText, cols[11]);
        line[12] = await page.evaluate((node) => node.innerText, cols[12]);
        line[13] = await page.evaluate((node) => node.innerText, cols[13]);
        line[14] = await page.evaluate((node) => node.innerText, cols[14]);
        line[15] = await page.evaluate((node) => node.innerText, cols[15]);
        line[16] = await page.evaluate((node) => node.innerText, cols[16]);
        line[17] = await page.evaluate((node) => node.innerText, cols[17]);
        line[18] = await page.evaluate((node) => node.innerText, cols[18]);
        line[19] = await page.evaluate((node) => node.innerText, cols[19]);
        line[20] = await page.evaluate((node) => node.innerText, cols[20]);
        line[21] = await page.evaluate((node) => node.innerText, cols[21]);
        line[22] = await page.evaluate((node) => node.innerText, cols[22]);
        data.push(line.join(","));
        await downloadImage(
          browser,
          "./output/wzry/team-logo",
          `${line[1]}.png`,
          line[0]
        );
      }
    
      await saveContent(
        `./output/wzry`,
        `world_cup_2021_team.csv`,
        data.join("
    ")
      );
    };
    

    2.1.2 选手数据的采集和解析

    // 选手数据
    const memberData = async (browser, page, url) => {
      await page.goto(url);
    
      // 解析页面
      /*
       * 0. logo: 选手头像
       * 1. name: 选手名称
       * 2. matchCount: 比赛场次
       * 3. matchBoxCount: 比赛局数
       * 4. winRate: 总胜率
       * 5. KDA: KDA
       * 6. participationRate: 参团率
       * 7. averageKill: 场均击杀
       * 8. averageDie: 场均死亡
       * 9. averageAssit: 场均助攻
       * 10. averageOutput: 分均输出
       * 11. averageEconomic: 分均经济
       * 12. averageBear: 分均承伤
       * 13. outputRate: 输出占比
       * 14. economicRate: 经济占比
       * 15. bearRate: 承伤占比
       */
    
      const data = [];
      const rows = await page.$$("#scroll-table > .tbody > .row");
    
      for (const row of rows) {
        let line = [];
        const cols = await row.$$(".td");
    
        line[0] = await cols[1].$eval("a > img", (node) =>
          node.getAttribute("src")
        );
        line[1] = await cols[1].$eval(
          ".right-name > .item-name",
          (node) => node.innerText
        );
        line[2] = await page.evaluate((node) => node.innerText, cols[2]);
        line[3] = await page.evaluate((node) => node.innerText, cols[3]);
        line[4] = await page.evaluate((node) => node.innerText, cols[4]);
        line[5] = await cols[5].$eval("div > .kda", (node) => node.innerText);
        line[6] = await page.evaluate((node) => node.innerText, cols[6]);
        line[7] = await page.evaluate((node) => node.innerText, cols[7]);
        line[8] = await page.evaluate((node) => node.innerText, cols[8]);
        line[9] = await page.evaluate((node) => node.innerText, cols[9]);
        line[10] = await page.evaluate((node) => node.innerText, cols[10]);
        line[11] = await page.evaluate((node) => node.innerText, cols[11]);
        line[12] = await page.evaluate((node) => node.innerText, cols[12]);
        line[13] = await page.evaluate((node) => node.innerText, cols[13]);
        line[14] = await page.evaluate((node) => node.innerText, cols[14]);
        line[15] = await page.evaluate((node) => node.innerText, cols[15]);
        data.push(line.join(","));
        await downloadImage(
          browser,
          "./output/wzry/member-logo",
          `${line[1]}.png`,
          line[0]
        );
      }
    
      await saveContent(
        `./output/wzry`,
        `world_cup_2021_member.csv`,
        data.join("
    ")
      );
    };
    

    2.1.3 英雄数据的采集和解析

    // 英雄数据
    const heroData = async (browser, page, url) => {
      await page.goto(url);
    
      // 解析页面
      /*
       * 0. logo: 英雄头像
       * 1. name: 英雄名称
       * 2. appearCount: 出场次数
       * 3. appearRate: 出场率
       * 4. winCount: 胜场
       * 5. winRate: 胜率
       * 6. banCount: 禁用次数
       * 7. banRate: 禁用率
       * 8. KDA: KDA
       */
    
      const data = [];
      const rows = await page.$$("#scroll-table > .tbody > .row");
    
      for (const row of rows) {
        let line = [];
        const cols = await row.$$(".td");
    
        line[0] = await cols[1].$eval("a > img", (node) =>
          node.getAttribute("src")
        );
        line[1] = await cols[1].$eval(
          ".right-name > .item-name",
          (node) => node.innerText
        );
        line[2] = await page.evaluate((node) => node.innerText, cols[2]);
        line[3] = await page.evaluate((node) => node.innerText, cols[3]);
        line[4] = await page.evaluate((node) => node.innerText, cols[4]);
        line[5] = await cols[5].$eval(".winRate > span", (node) => node.innerText);
        line[6] = await page.evaluate((node) => node.innerText, cols[6]);
        line[7] = await page.evaluate((node) => node.innerText, cols[7]);
        line[8] = await page.evaluate((node) => node.innerText, cols[9]);
        data.push(line.join(","));
        await downloadImage(
          browser,
          "./output/wzry/hero-logo",
          `${line[1]}.png`,
          line[0]
        );
      }
    
      await saveContent(
        `./output/wzry`,
        `world_cup_2021_hero.csv`,
        data.join("
    ")
      );
    };
    

    2.2 logo和头像的保存

    在html页面中,logo和头像都是图片的url,为了下载实际的图片,封装了个小函数 downloadImage

    // 下载图片
    const downloadImage = async (browser, dirname, filename, imgSrc) => {
      console.log("image src: ", imgSrc);
      const page = await browser.newPage();
    
      try {
        const imgResp = await page.goto(imgSrc);
        const buffer = await imgResp.buffer();
        const imgBase64 = buffer.toString("base64");
    
        if (!mkdirsSync(dirname)) {
          console.error("mkdir save page dir ERROR!");
          return;
        }
        fs.writeFileSync(path.join(dirname, filename), imgBase64, "base64");
      } catch (e) {
        console.error("download image error: ", e);
      } finally {
        await page.close();
      }
    };
    

    3. 总结

    以上通过 puppeteer 采集2021世冠比赛数据的实战中,技术要点主要有:

    1. 解析页面元素中的值
    2. 循环获取html table 中 tr/td 中的内容
    3. 下载网页中图片

    4. 注意事项

    爬取数据只是为了研究学习使用,本文中的代码遵守:

    1. 如果网站有 robots.txt,遵循其中的约定
    2. 爬取速度模拟正常访问的速率,不增加服务器的负担
    3. 只获取完全公开的数据,有可能涉及隐私的数据绝对不碰
  • 相关阅读:
    模拟赛总结
    2018.04.06学习总结
    2018.04.06学习总结
    Java实现 LeetCode 672 灯泡开关 Ⅱ(数学思路问题)
    Java实现 LeetCode 671 二叉树中第二小的节点(遍历树)
    Java实现 LeetCode 671 二叉树中第二小的节点(遍历树)
    Java实现 LeetCode 671 二叉树中第二小的节点(遍历树)
    Java实现 LeetCode 670 最大交换(暴力)
    Java实现 LeetCode 670 最大交换(暴力)
    Java实现 LeetCode 670 最大交换(暴力)
  • 原文地址:https://www.cnblogs.com/wang_yb/p/15168674.html
Copyright © 2011-2022 走看看