使用 Node.js 编写一个简单的网络爬虫
使用 Node.js 编写一个简单的网络爬虫
引言/概述
随着互联网数据的爆炸式增长,如何高效地从网页中提取结构化信息成为开发者和数据分析师的重要技能。网络爬虫(Web Crawler)或网络蜘蛛(Web Spider)是一种自动抓取网页内容并从中提取所需数据的程序。在众多编程语言中,JavaScript 因其在 Web 领域的天然优势而备受青睐,而 Node.js 作为 JavaScript 的服务端运行环境,提供了强大的异步 I/O 能力,非常适合构建高性能的网络爬虫。
本文将带你从零开始,使用 Node.js 编写一个功能完整的简单爬虫。我们将介绍爬虫的基本原理、所需的核心模块、如何解析 HTML 内容、处理分页以及遵守爬虫道德规范。通过本文的学习,你将掌握构建基础爬虫的关键技术,并能在此基础上进行扩展。
核心内容
准备工作:安装必要依赖
在开始编码之前,我们需要安装几个关键的 Node.js 模块:
axios:用于发送 HTTP 请求,替代原生的http模块,使用更简洁。cheerio:服务器端的 jQuery 实现,用于解析和操作 HTML 文档。fs(内置):用于将抓取的数据保存到本地文件。
首先初始化项目并安装依赖:
mkdir simple-crawler
cd simple-crawler
npm init -y
npm install axios cheerio
基础爬虫结构
一个最基本的爬虫通常包含以下步骤:
- 发送 HTTP 请求获取网页内容;
- 解析 HTML 提取目标数据;
- 存储或处理提取的数据。
下面是一个最简示例,抓取某个页面的标题:
// crawler.js
const axios = require('axios');
const cheerio = require('cheerio');
async function crawl(url) {
try {
const { data } = await axios.get(url);
const $ = cheerio.load(data);
const title = $('title').text().trim();
console.log('页面标题:', title);
} catch (error) {
console.error('请求失败:', error.message);
}
}
crawl('https://example.com');
这段代码展示了爬虫的核心流程:用 axios.get() 获取网页 HTML,用 cheerio.load() 加载内容,然后像使用 jQuery 一样选择元素并提取文本。
提取结构化数据
实际应用中,我们往往需要提取多个字段,例如新闻网站的文章标题、作者、发布时间等。假设我们要爬取一个假想的博客列表页(如 https://example-blog.com/posts),其 HTML 结构如下:
<div class="post">
<h2 class="title">文章标题1</h2>
<span class="author">作者A</span>
<time class="date">2024-05-01</time>
</div>
<div class="post">
<h2 class="title">文章标题2</h2>
<span class="author">作者B</span>
<time class="date">2024-05-02</time>
</div>
我们可以编写如下代码提取所有文章信息:
// advanced-crawler.js
const axios = require('axios');
const cheerio = require('cheerio');
async function crawlBlogList(url) {
try {
const { data } = await axios.get(url);
const $ = cheerio.load(data);
const posts = [];
$('.post').each((index, element) => {
const title = $(element).find('.title').text().trim();
const author = $(element).find('.author').text().trim();
const date = $(element).find('.date').text().trim();
posts.push({ title, author, date });
});
console.log('抓取到的文章:', posts);
return posts;
} catch (error) {
console.error('抓取失败:', error.message);
return [];
}
}
crawlBlogList('https://example-blog.com/posts');
这里使用了 Cheerio 的 .each() 方法遍历所有匹配的 .post 元素,并逐个提取子元素内容,最终返回一个结构化的对象数组。
处理分页
许多网站的内容分布在多个页面上。常见的分页形式包括“下一页”链接或页码导航。我们可以通过递归或循环方式依次访问每一页。
假设分页链接为 /posts?page=1、/posts?page=2……直到没有更多内容。我们可以这样实现:
// paginated-crawler.js
const axios = require('axios');
const cheerio = require('cheerio');
async function crawlAllPages(baseUrl, maxPages = 5) {
let allPosts = [];
for (let page = 1; page <= maxPages; page++) {
const url = `${baseUrl}?page=${page}`;
console.log(`正在抓取第 ${page} 页: ${url}`);
try {
const { data } = await axios.get(url);
const $ = cheerio.load(data);
// 如果当前页没有文章,提前退出
if ($('.post').length === 0) {
console.log('已无更多内容,停止抓取');
break;
}
const posts = [];
$('.post').each((_, el) => {
posts.push({
title: $(el).find('.title').text().trim(),
author: $(el).find('.author').text().trim(),
date: $(el).find('.date').text().trim()
});
});
allPosts = allPosts.concat(posts);
// 可选:添加延迟避免请求过于频繁
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
console.error(`第 ${page} 页抓取失败:`, error.message);
break;
}
}
return allPosts;
}
crawlAllPages('https://example-blog.com/posts').then(posts => {
console.log(`共抓取 ${posts.length} 篇文章`);
});
注意:我们在每次请求后加入了 1 秒的延迟(setTimeout),这是为了避免对目标服务器造成过大压力,体现良好的爬虫礼仪。
数据持久化
抓取到的数据通常需要保存下来供后续分析。我们可以将其写入 JSON 文件:
const fs = require('fs').promises;
async function saveData

评论 0