在本篇文章中,我们将通过一个具体的项目实例,探索如何利用Node.js结合Cheerio库爬取豆瓣电影Top250排行榜的数据。本文将分步骤讲解从环境准备、代码实现到数据存储的全过程,适合对Web爬虫感兴趣的初学者及有一定基础的开发者参考。
一、前言
随着大数据时代的到来,网络爬虫技术因其能够自动收集网页信息的能力而变得日益重要。本文将介绍如何使用Node.js这一流行的服务器端JavaScript运行环境,配合Cheerio——一个轻量级的、类jQuery的DOM操作库,来抓取并解析网页数据。我们的目标是获取豆瓣电影Top250榜单上的所有电影信息,包括电影名、评分、简介等,并将这些数据保存为JSON文件。
爬虫技术是自动抓取网页信息的关键手段,其本质在于模拟浏览器行为,向服务器发送HTTP请求,获取响应数据(通常是HTML),并通过解析提取所需信息。这一过程形象地比喻为“他有我拿”,即用户通过爬虫间接访问网站内容。以豆瓣电影排行榜为例,通过向特定URL发送GET请求,获得页面源码后,利用类似CSS选择器的解析工具(如cheerio库在Node.js中的应用),精准提取电影列表信息,并整理成JSON格式输出。
正文
二、技术栈与环境准备
- Node.js: 作为后端JavaScript运行环境,用于执行JavaScript代码。
- Request-Promise: 一个基于Request的Promise封装库,用于发送HTTP请求(已废弃,推荐使用Axios替代)。
- Cheerio: 用于服务器端的HTML解析,模仿jQuery的API,方便地选择和操作DOM元素。
- File System (fs) : Node.js内置模块,用于读写文件系统。
- 三、代码解析
1. 引入依赖
首先,我们需要安装必要的npm包,包括已经弃用的request-promise(推荐使用axios)、cheerio以及使用Node.js自带的fs模块来操作文件。
let request = require('request-promise');
let cheerio = require('cheerio');
let fs = require('fs');
2. 基本设置与辅助函数
let movies = []; // 初始化一个空数组用来存放爬取的电影信息
2let basicUrl = 'https://movie.douban.com/top250'; // 豆瓣电影Top250的基础URL
3let once = function (cb) { // 防止并发执行的辅助函数
4 let active = false;
5 if (!active) {
6 cb();
7 active = true;
8 }
9};
10function log(item) { // 简化的日志打印函数,使用once防止并发打印
11 once(() => {
12 console.log(item);
13 });
定义基本URL、空数组movies来存放爬取的电影信息,并创建一个防并发请求的辅助函数once以及日志输出函数log。
3. 提取电影信息
function getMovieInfo(node) { // 接收一个包含电影信息的DOM节点
2 let $ = cheerio.load(node); // 使用cheerio加载节点内容
3 let titles = $('.info .hd span').map((i, el) => $(el).text()).get(); // 获取并映射标题元素的文本
4 let bd = $('.info .bd'); // 选择包含额外信息的元素
5 let info = bd.find('p').text(); // 提取电影简介
6 let score = bd.find('.star .rating_num').text(); // 提取评分
7 return { titles, info, score }; // 返回包含电影信息的对象
8}
定义getMovieInfo函数,它接收一个包含单部电影信息的DOM节点,使用Cheerio解析出电影的标题、简介和评分。
4. 抓取单页数据
async function getPage(url, num) { // 异步函数,接受URL和页码
2 let html = await request({ url }); // 发送GET请求并等待响应
3 log('连接成功!' + `正在爬取第${num + 1}页数据`); // 打印日志
4 let $ = cheerio.load(html); // 加载响应的HTML
5 let movieNodes = $('#content .article .grid_view .item'); // 选择所有电影项
6 let movieList = movieNodes.map((i, node) => getMovieInfo(node)).get(); // 对每个节点应用getMovieInfo并获取结果
7 return movieList; // 返回该页所有电影的信息列表
8}
getPage函数负责发送HTTP请求到指定URL(根据页码计算),获取页面内容后,使用Cheerio解析出每部电影的信息,并将结果以对象数组形式返回。
5. 主函数执行
async function main() {
2 let count = 25; // 总共爬取的页数
3 let list = []; // 初始化空数组存储所有电影信息
4 for (let i = 0; i < count; i++) { // 循环请求每一页
5 let url = basicUrl + '?start=' + (25 * i); // 计算当前页的URL
6 list = list.concat(await getPage(url, i)); // 合并当前页的电影信息到总列表
7 }
8 console.log(list.length); // 打印总电影数量
9 fs.writeFile('./output.json', JSON.stringify(list, null, 2), 'utf-8', () => { // 将数据保存为JSON文件
10 console.log('生成json文件成功!');
11 });
12}
13main(); // 调用主函数开始执行爬虫任务
main函数是程序的入口,它循环请求每一页的数据,将所有电影信息汇总到一个列表中,并最终将这个列表写入到本地的output.json文件中。
四、执行流程
- 初始化:设置基础URL,定义空数组存储电影信息。
- 循环请求:基于起始页数计算每页URL,调用
getPage函数获取数据。 - 信息提取:对每个电影节点调用
getMovieInfo,提取详细信息。 - 数据汇总:将所有页的数据合并到一个大列表中。
- 文件输出:使用
fs.writeFile将汇总后的电影数据写入JSON文件。
最后我们来看看我们完整的一份代码吧
let request = require('request-promise') // 需要安装
let cheerio = require('cheerio') // 需要安装
let fs = require('fs')
const util = require('util')
let movies = []
let basicUrl = 'https://movie.douban.com/top250'
let once = function (cb) {
let active = false
if (!active) {
cb()
active = true
}
}
function log(item) {
once(() => {
console.log(item)
})
}
function getMovieInfo(node) {
let $ = cheerio.load(node)
let titles = $('.info .hd span')
titles = ([]).map.call(titles, t => {
return $(t).text()
})
let bd = $('.info .bd')
let info = bd.find('p').text()
let score = bd.find('.star .rating_num').text()
return { titles, info, score }
}
async function getPage(url, num) {
let html = await request({
url
})
console.log('连接成功!', `正在爬取第${num + 1}页数据`)
let $ = cheerio.load(html)
let movieNodes = $('#content .article .grid_view').find('.item')
let movieList = ([]).map.call(movieNodes, node => {
return getMovieInfo(node)
})
return movieList
}
async function main() {
let count = 25
let list = []
for (let i = 0; i < count; i++) {
let url = basicUrl + `?start=${25 * i}`
list.push(... await getPage(url, i))
}
console.log(list.length)
fs.writeFile('./output.json', JSON.stringify(list), 'utf-8', () => {
console.log('生成json文件成功!')
})
}
main()
五、总结
通过上述步骤,我们成功实现了从豆瓣电影Top250页面抓取数据并保存为JSON文件的小型爬虫项目。此过程不仅加深了对Node.js、Cheerio等工具的理解,也展示了爬虫技术在数据采集方面的强大能力。未来,可以进一步考虑增加异常处理、数据清洗、多线程爬取等高级功能,以提升程序的稳定性和效率。