Node爬虫--统计亚运会关注人数最多的比赛

实验要求

(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+页面耗时在六七分钟左右.

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值