數據采集實戰(三)-- 王者榮耀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("\n")
  );
};

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("\n")
  );
};

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("\n")
  );
};

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. 只獲取完全公開的數據,有可能涉及隱私的數據絕對不碰


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM