Playwright爬取王者荣耀壁纸

Playwright是微软2020年开源的一款自动化测试工具,支持多语言(Node、Python、Java、.Net)和多种浏览器内核(Chromium, WebKit, Firefox),像Seleium和Pyppeteer可以驱动浏览器执行操作,并且无需独立安装驱动,功能丰富,API简洁易用,在GitHub已经40k star了。

本篇文章简单应用Playwright爬取王者荣耀的所有英雄的壁纸并保存到本地,代码是基于Node.js。

代码细节

梳理一下爬取壁纸的过程:

  1. 首先我们可以从英雄资料页中获取所有英雄的页面链接;
  2. 记录了所有英雄的页面之后,页面跳转到详情页,从下面点击切换背景可以看出每张图片已经在页面元素中了,保存所有壁纸的链接;
  3. 将壁纸的页面链接保存到本地;
  4. 将数据以JSON形式保存下来;

wangzhespider.gif 观察资料页的页面元素,可以发现类名是herolist的ul下保存每个英雄的信息,其中详情页的地址是herodetail/xxx.shtml,它们共同的前缀地址都是https://pvp.qq.com/web201605/

Snipaste_2022-07-31_15-41-13.png 在英雄详情页中,类名是pic-pf-list pic-pf-list3的ul标签保存每个皮肤的图片链接,其中有bigskin的data-imgname属性就是大图的链接。 Snipaste_2022-07-31_15-43-32.png 在写代码之前初始化环境和安装依赖:

mkdir wangzhe-spider
cd wangzhe-spider
mkdir hero
npm init -y
npm install playwright axios

代码如下:

const { chromium } = require("playwright");
const path = require("path");
const fs = require("fs");
const axios = require("axios");
const BASE_URL = "https://pvp.qq.com/web201605/";
const HOME_PAGE = "https://pvp.qq.com/web201605/herolist.shtml";
const HERO_LIST = []; // 保存英雄详情页
const ALL_DATA = []; // 保存到JSON的数据
let browser = null;
let page = null;

// 程序入口函数
async function main() {
  browser = await chromium.launch({ headless: false });
  page = await browser.newPage();
  await page.goto(HOME_PAGE);
  await page.waitForTimeout(3000);
  const liElements = page.locator("//ul[@class='herolist clearfix']/li/a");
  const count = await liElements.count();
  console.log("英雄总数:", count);
  for (let i = 0; i < count; i++) {
    const url = await liElements.nth(i).getAttribute("href");
    console.log(url);
    HERO_LIST.push(url);
  }
  for (let item of HERO_LIST) {
    try {
      await parseDetailPage(BASE_URL + item);
    } catch (error) {
      console.log(error);
    }
  }
  await browser.close();
  ALL_DATA.sort((a, b) => b.skinCount - a.skinCount);
  saveJSON(ALL_DATA);
}

// 收集详情页的链接信息
async function parseDetailPage(heroUrl) {
  await page.goto(heroUrl);
  await page.waitForTimeout(2000);
  const elements = page.locator("//ul[@class='pic-pf-list pic-pf-list3']/li");
  const count = await elements.count();
  const heroName = await page
    .locator("//h2[@class='cover-name']")
    .textContent();
  const heroItem = {
    name: heroName,
    skinCount: count,
    skinList: [],
  };
  for (let i = 0; i < count; i++) {
    const img_xpath = `//ul[@class='pic-pf-list pic-pf-list3']/li[${
      i + 1
    }]/i/img`;
    const p_xpath = `//ul[@class='pic-pf-list pic-pf-list3']/li[${i + 1}]/p`;
    const el = page.locator(img_xpath);
    const url = "https:" + (await el.getAttribute("data-imgname"));
    const skinName = await page.locator(p_xpath).textContent();
    heroItem.skinList.push({
      skinName,
      url,
    });
    downloadFile(url, heroName, skinName);
    console.log(heroName, skinName, url);
  }
  ALL_DATA.push(heroItem);
}

// 下载文件到本地
async function downloadFile(url, name, skin) {
  const fileName = path.resolve("./hero", `${name}-${skin}.jpg`);
  if (!fs.existsSync(fileName)) {
    try {
      const resp = await axios.get(url, { responseType: "arraybuffer" });
      fs.writeFileSync(fileName, new Buffer.from(resp.data), "binary");
      console.log("save file:", skin);
    } catch (error) {
      console.log("文件保存失败:", fileName);
    }
  }
}

// 保存JSON数据
async function saveJSON(rawData) {
  const data = JSON.stringify(rawData);
  try {
    fs.writeFileSync("hero.json", data);
    console.log("JSON data is saved.");
  } catch (error) {
    console.error(error);
  }
}

main();

执行之后 Snipaste_2022-07-31_15-58-53.png 其中JSON的数据如下:

{
    "skinList": [
        {
            "skinName": "齐天大圣",
            "url": "https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/167/167-bigskin-1.jpg"
        },
        {
            "skinName": "地狱火",
            "url": "https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/167/167-bigskin-2.jpg"
        },
        {
            "skinName": "西部大镖客",
            "url": "https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/167/167-bigskin-3.jpg"
        },
        {
            "skinName": "美猴王",
            "url": "https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/167/167-bigskin-4.jpg"
        },
        {
            "skinName": "至尊宝",
            "url": "https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/167/167-bigskin-5.jpg"
        },
        {
            "skinName": "全息碎影",
            "url": "https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/167/167-bigskin-6.jpg"
        },
        {
            "skinName": "大圣娶亲",
            "url": "https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/167/167-bigskin-7.jpg"
        },
        {
            "skinName": "零号·赤焰",
            "url": "https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/167/167-bigskin-8.jpg"
        },
        {
            "skinName": "孙行者",
            "url": "https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/167/167-bigskin-9.jpg"
        }
    ]
}

经过排序后,我们110个英雄中,看看皮肤数量前三名的数据吧~ 数量最多的居然是赵云!!

排序 英雄 皮肤数量
1 赵云 10
2 孙悟空 9
2 花木兰 9
2 鲁班七号 9
2 孙尚香 9
3 后羿 8
3 程咬金 8
3 貂蝉 8
3 狄仁杰 8
3 妲己 8
3 小乔 8

其实,在英雄资料页所有数据来自一份JSON文件,链接的数据完全可以直接请求这份文件实现: pvp.qq.com/web201605/j…

大杀器

playwright最让人惊讶的功能就是它可以录制浏览器的行为录制代码,这也太方便了吧。

npx playwright install
npx playwright codegen -b webkit https://book.douban.com/top250?icn=index-book250-all

codegen.gif

总结

本篇是playwright的简单应用,它的功能十分强大比如选择器支持文本、xpath和css选择器,支持网络数据的拦截、截图、下载、运行JS脚本等,等深入应用之后还会有所补充。

猜你喜欢

转载自juejin.im/post/7126474216509014023