Playwright是微软2020年开源的一款自动化测试工具,支持多语言(Node、Python、Java、.Net)和多种浏览器内核(Chromium, WebKit, Firefox),像Seleium和Pyppeteer可以驱动浏览器执行操作,并且无需独立安装驱动,功能丰富,API简洁易用,在GitHub已经40k star了。
本篇文章简单应用Playwright爬取王者荣耀的所有英雄的壁纸并保存到本地,代码是基于Node.js。
代码细节
梳理一下爬取壁纸的过程:
- 首先我们可以从英雄资料页中获取所有英雄的页面链接;
- 记录了所有英雄的页面之后,页面跳转到详情页,从下面点击切换背景可以看出每张图片已经在页面元素中了,保存所有壁纸的链接;
- 将壁纸的页面链接保存到本地;
- 将数据以JSON形式保存下来;
观察资料页的页面元素,可以发现类名是
herolist
的ul下保存每个英雄的信息,其中详情页的地址是herodetail/xxx.shtml
,它们共同的前缀地址都是https://pvp.qq.com/web201605/
在英雄详情页中,类名是
pic-pf-list pic-pf-list3
的ul标签保存每个皮肤的图片链接,其中有bigskin的data-imgname属性就是大图的链接。 在写代码之前初始化环境和安装依赖:
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();
执行之后 其中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
总结
本篇是playwright的简单应用,它的功能十分强大比如选择器支持文本、xpath和css选择器,支持网络数据的拦截、截图、下载、运行JS脚本等,等深入应用之后还会有所补充。