# Node爬虫
# 依赖
分享两个比较不错的Node爬虫库
cheerio: 用于爬取静态的资源,功能面较小,但速度快
puppeteer: 用于爬取静态/动态/异步资源,功能强大,但速度满
# cheerio
可以对cheerio库进行二次封装
可以写一个通用函数用于专门爬取静态的HTML
// methods/getHTML.js
const https = require('https')
/**
* 获取爬虫html
* @param {*} url 爬虫地址
* @returns {string} html
*/
async function getHTML(url) {
return new Promise((resolve) => {
https.get(url, (res) => {
let html = ''
res.on('data', (chunk) => {
html += chunk
})
res.on('end', () => {
resolve(html)
})
})
})
}
module.exports = getHTML
接下来需要借助cheerio库进行dom的操作,cheerio能够让我们像使用jquery一样使用cheerio
// utils/cheerio.js
const cheerio = require('cheerio')
/**
* 爬取href属性
* @param {*} html html字符串
* @param {*} classTag 类名
* @returns hrefs
*/
function getHrefsByTagClass(html, classTag) {
console.log(`开始爬取${classTag}中...`)
const $ = cheerio.load(html)
const resHrefs = []
$(classTag).each(function() {
const href = $(this).attr('href')
if (href) {
resHrefs.push(href)
}
})
console.log(`结束爬取${classTag}...`)
return resHrefs
}
/**
* 爬取文本内容
* @param {*} html html字符串
* @param {*} classTag 类名
* @returns
*/
function getContentByTagClass(html, classTag) {
console.log(`开始爬取${classTag}中...`)
const $ = cheerio.load(html)
const resContent = []
$(classTag).each(function() {
const text = $(this).text()
if (text) {
resContent.push(text)
}
})
console.log(`结束爬取${classTag}...`)
return resContent
}
module.exports = {
getHrefsByTagClass,
getContentByTagClass
}
# puppeteer
puppeteer能够让我们像使用浏览器一样,比如滚动行为、点击行为等等
我们同样可以对puppeteer进行二次封装
// utils/puppeteer.js
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
}
这里我们使用了generic-pool对性能进行优化,它能够开启一个连接池减少开关浏览器的行为。
// utils/browser-pool.js
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;
# 定时爬虫
借助node-schedule我们能够实现定时进行爬虫,当然前提是在开启了服务器之后
// utils/schedule.js
const schedule = require('node-schedule')
const SETTING = require('../setting')
const reptile = require('../reptile')
function startReptileSchedule() {
const rule = new schedule.RecurrenceRule()
rule.hour = SETTING.repiteTime.hour
rule.minute = SETTING.repiteTime.min
const job = schedule.scheduleJob("reptile" ,rule, async () => {
try {
await reptile()
console.log('爬取任务已完成')
} catch (error) {
console.error('爬取任务出错:', error)
}
})
}
module.exports = {
startReptileSchedule
}
# 一个小案例
亚运会数据统计[Python/Nodejs]
统计亚运会关注人数最多的比赛
实验环境
- 操作系统:MacOS
- 编程语言:Nodejs 18.17.0
代码仓库
← 介绍