实验要求
(1)环境:Linux/MacOS
(2)语言:Python/Node.js
(3)爬虫数据来源:百度体育直播间
代码仓库:
https://github.com/PoliZyh/WebServerStudy
Experiment1/code异步数据没有爬取到
Experiment1/code1.1.1为测试通过版本
安装 npm i
执行 npm run dev
实验环境
使用语言 Node.js 18.17.0
环境 MacOS 11.3.1
爬虫思路
(1)首先爬取2023-09-23界面下所有的a标签,该标签的href属性指向了直播间界面
(2)爬取该界面下所有的日期以及每日的场数,用于对爬取的所有href进行切片
(3)进行切片 --> 以日期为一片
(4)重写所有href,即前缀增加https,后缀改为/聊天厅,这样能够保证获取到直播间人数
(5)整理数据,导出JSON
依赖
"express": "^4.18.2",
"generic-pool": "^3.9.0",
"puppeteer": "^21.3.6"
核心爬虫部分
封装puppeteer库,用于爬虫
const puppeteer = require('puppeteer');
const browserPool = require('./browser-pool');
/**
* 滚动到底部
* @param {} page
*/
async function autoScroll(page) {
console.log('正在滚动页面,时间较长请稍等...')
await page.evaluate(async () => {
await new Promise((resolve) => {
let totalHeight = 0;
const distance = 100;
const scrollInterval = setInterval(() => {
const scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
if (totalHeight >= scrollHeight) {
clearInterval(scrollInterval);
console.log('滚动页面结束...')
resolve();
}
}, 100);
});
});
}
/**
* 优化
* 使用链接池
*/
async function runPuppeteerScriptForTexts(urls, classTag) {
// console.log(`开始爬取${url}的Text`)
return new Promise(async (resolve) => {
const browser = await browserPool.acquire(); // 从连接池获取浏览器实例
const page = await browser.newPage();
await page.setDefaultNavigationTimeout(60000);
const dynamicContents = []
for (const urlItem in urls) {
console.log(`开始爬取${urls[urlItem]}的Text`)
await page.goto(urls[urlItem]);
await page.waitForSelector(classTag);
const dynamicContent = await page.evaluate((classTag) => {
const element = document.querySelector(classTag);
return element ? element.textContent : null;
}, classTag);
dynamicContents.push(dynamicContent)
console.log(`结束爬取${urls[urlItem]}的Text`)
}
// 关闭页面
await page.close();
// 将浏览器实例返回连接池
browserPool.release(browser);
resolve(
dynamicContents
)
})
}
async function runPuppeteerScript(url, classTag, classTagText, classTagDate, classTagName) {
console.log(`开始爬取${url}的Text和href`)
return new Promise(async (resolve) => {
const browser = await browserPool.acquire(); // 从连接池获取浏览器实例
const page = await browser.newPage();
await page.goto(url);
await page.click('.btn-load.m-c-bg-color-white.m-c-color-gray.btn-middle');
// 模拟滚动到底部
await autoScroll(page);
await page.waitForSelector(classTag);
// 执行其他 Puppeteer 操作...
const dynamicHrefs = await page.evaluate((classTag) => {
const elements = document.querySelectorAll(classTag);
const data = []
elements.forEach(element => {
data.push(element.getAttribute('href'))
})
return data
}, classTag);
const dynamicContents = await page.evaluate((classTagText) => {
const elements = document.querySelectorAll(classTagText);
const data = []
elements.forEach(element => {
data.push(element.textContent)
})
return data
}, classTagText)
const dynamicDates = await page.evaluate((classTagDate) => {
const elements = document.querySelectorAll(classTagDate);
console.log(elements)
const data = []
elements.forEach(element => {
data.push(element.textContent)
})
return data
}, classTagDate)
const dynamicNames = await page.evaluate((classTagName) => {
const elements = document.querySelectorAll(classTagName);
console.log(elements)
const data = []
elements.forEach(element => {
data.push(element.textContent)
})
return data
}, classTagName)
// 关闭页面
await page.close();
console.log(`结束爬取${url}的Text和Href`)
// 将浏览器实例返回连接池
browserPool.release(browser);
resolve({
dynamicHrefs,
dynamicContents,
dynamicDates,
dynamicNames
})
})
}
module.exports = {
runPuppeteerScript,
runPuppeteerScriptForTexts
}
性能优化部分
本次实验需要爬取400+页面,相当的耗时,需要进行性能优化
我的思路有以下几个
(1)使用Promise.all进行处理,但需要对其进行二次封装避免过度请求使CPU占率99%+
(2)对Puppeteer进行优化
我选择了第二个方法
(1)首先对puppeteer的launch部分进行优化处理(修改headless等),效果不显著
(2)使用连接池generic-pool进行优化处理,代码如下,效果提升较为显著
const puppeteer = require('puppeteer');
const genericPool = require('generic-pool')
// 创建一个浏览器工厂
const browserFactory = {
create: async () => {
const browser = await puppeteer.launch();
return browser;
},
destroy: (browser) => {
browser.close();
},
};
// 创建浏览器连接池
const browserPool = genericPool.createPool(browserFactory, { min: 1, max: 10 }); // 可根据需求调整最小和最大连接数
module.exports = browserPool;
(3)对puppeteer传入Array<url>而不是url,进行但页面的多次跳转,避免在开关浏览器上产生大量的时间浪费,效果非常显著
目前爬取400+页面耗时在六七分钟左右.