数据采集实战(三)

1. 概述
王者荣耀是一直都挺喜欢的一个手游,玩了好几年,最近一段开始喜欢看比赛,所以想着采集点数据看看各个战队或者选手的情况。
顺便也练习练习 puppeteer 的使用。
数据来源于:尚牛电竞 。
2. 采集流程
王者荣耀最近正在进行的最大比赛就是 2021世冠杯,所以就选择采集这个赛事的数据。
在 尚牛电竞 网站上,已经按照战队,选手和英雄分好类了,并且网站不需要登录就能看到数据。
三组数据直接对应不同的URL进行采集即可,没有复杂的流程,唯一需要注意的地方是对Logo和头像的小图片的保存。
2.1 各个数据的采集
积分榜的数据是空的,战队榜,选手榜和英雄榜的数据可以获取。
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世冠比赛数据的实战中,技术要点主要有:
- 解析页面元素中的值
 - 循环获取html table 中 tr/td 中的内容
 - 下载网页中图片
 
4. 注意事项
爬取数据只是为了研究学习使用,本文中的代码遵守:
- 如果网站有 robots.txt,遵循其中的约定
 - 爬取速度模拟正常访问的速率,不增加服务器的负担
 - 只获取完全公开的数据,有可能涉及隐私的数据绝对不碰
 
以上是 数据采集实战(三) 的全部内容, 来源链接: utcz.com/z/535868.html




