文章
关于Cheerio
目录
- 📚 一、基本概念
- 1. 是什么
- 2. 核心特点
- 3. 底层架构
- 4. 与相关技术的对比
- 🛠 二、能做什么
- 1. 数据提取(主要用途)
- 2. 网页内容分析
- 3. HTML 清理与转换
- 4. 模板处理
- 5. 测试辅助
- 🚀 三、怎么使用
- 1. 安装与初始化
- 2. 选择元素
- 3. 遍历与查找
- 4. 提取数据
- 5. 操作DOM
- 6. 集合操作
- 🎯 四、使用场景
- 1. 网络爬虫/数据抓取
- 2. 内容聚合
- 3. HTML预处理
- 4. 邮件模板处理
- 5. SEO优化检查
- ⚖️ 五、优缺点分析
- ✅ 优点
- 1. 性能卓越
- 2. 内存友好
- 3. API友好
- 4. 轻量级
- 5. 稳定性强
- ❌ 缺点
- 1. 无法执行JavaScript
- 2. 无CSS计算
- 3. 有限的浏览器API
- 4. 选择器限制
- 5. 无法处理iframe/框架页
- 🔧 六、最佳实践
- 1. 错误处理
- 2. 性能优化
- 3. 处理特殊字符
- 4. 与动态内容配合
- 🎓 七、学习路径建议
- 阶段1:基础掌握(1-2天)
- 阶段2:中级应用(3-5天)
- 阶段3:高级实战(1-2周)
- 阶段4:源码理解(可选)
- 📈 八、总结
- 📎 参考文章
📚 一、基本概念#
1. 是什么#
Cheerio 是一个轻量级的 HTML/XML 解析库,专为 Node.js 环境设计。它采用了类似 jQuery 的 API 语法,但运行在服务器端而非浏览器中。
2. 核心特点#
- 非浏览器环境:没有 DOM、window、document 等浏览器对象
- 无 JavaScript 执行:不能执行页面中的 JavaScript 代码
- 无 CSS 渲染:不会应用样式或布局计算
- 纯解析器:只做 HTML/XML 的解析和操作
3. 底层架构#
Cheerio = htmlparser2(解析器) + jQuery-like API(接口)
└── 将 HTML 字符串 → DOM 树结构 → 提供 jQuery 方法操作
4. 与相关技术的对比#
| 工具 | 环境 | 执行JS | 渲染 | 速度 | 用途 |
|---|---|---|---|---|---|
| Cheerio | Node.js | ❌ | ❌ | ⚡极快 | 静态HTML解析 |
| jsdom | Node.js | ✅ | ✅ | 中等 | 模拟浏览器环境 |
| Puppeteer | Node.js | ✅ | ✅ | 较慢 | 无头浏览器自动化 |
| jQuery | 浏览器 | ✅ | ✅ | 快 | 浏览器DOM操作 |
🛠 二、能做什么#
1. 数据提取(主要用途)#
// 提取文章标题、内容、发布时间等
const title = $('h1.title').text();
const content = $('.article-body').html();
const publishTime = $('.meta time').attr('datetime');
2. 网页内容分析#
// 分析页面结构
const headings = $('h1, h2, h3').map((i, el) => ({
level: el.tagName,
text: $(el).text(),
id: $(el).attr('id')
})).get();
3. HTML 清理与转换#
// 移除不需要的元素
$('script, style, iframe, ads').remove();
// 转换格式
$('img').each((i, img) => {
$(img).attr('src', convertToAbsoluteUrl($(img).attr('src')));
});
4. 模板处理#
// 填充模板
const template = `
<div class="product">
<h2>{{title}}</h2>
<p>{{description}}</p>
</div>
`;
const $template = cheerio.load(template);
$template('h2').text(product.title);
$template('p').text(product.description);
5. 测试辅助#
// 验证生成的HTML结构
test('组件渲染正确', () => {
const html = renderComponent(props);
const $ = cheerio.load(html);
expect($('.button').length).toBe(1);
expect($('.button').text()).toBe('Submit');
});
🚀 三、怎么使用#
1. 安装与初始化#
npm install cheerio
const cheerio = require('cheerio');
// 方式1:从字符串加载
const html = '<div class="container">Hello</div>';
const $ = cheerio.load(html);
// 方式2:带配置项
const $ = cheerio.load(html, {
decodeEntities: false, // 不解码HTML实体
xmlMode: true, // XML模式
lowerCaseTags: false, // 不转为小写标签
});
2. 选择元素#
// CSS选择器(支持大部分CSS3选择器)
$('#id') // ID选择器
$('.class') // 类选择器
$('div') // 标签选择器
$('div.container') // 组合选择器
$('a[href]') // 属性选择器
$('ul > li') // 子选择器
$('h1, h2, h3') // 分组选择器
$('div:first-child') // 伪类选择器(部分支持)
3. 遍历与查找#
const $element = $('.target');
// 向上查找
$element.parent() // 直接父元素
$element.parents('.ancestor') // 所有匹配的祖先
$element.closest('.wrapper') // 最近的匹配祖先
// 向下查找
$element.children() // 直接子元素
$element.find('.descendant') // 所有后代元素
$element.contents() // 所有子节点(包括文本节点)
// 同级查找
$element.next() // 下一个兄弟
$element.nextAll('.sibling') // 后面的所有匹配兄弟
$element.prev() // 上一个兄弟
$element.prevAll('.sibling') // 前面的所有匹配兄弟
$element.siblings() // 所有兄弟元素
4. 提取数据#
// 文本内容
$('div').text() // 合并所有子文本
$('div').contents() // 获取所有子节点
.filter((_, node) => node.type === 'text')
.text() // 只获取直接文本
// HTML内容
$('div').html() // 内部HTML
$.html($('div')) // 外部HTML(字符串化)
// 属性
$('a').attr('href') // 单个属性
$('img').attr() // 所有属性
$('div').data('user-id') // data-* 属性
$('input').prop('checked') // 属性值(包括布尔)
// 样式
$('div').css('color') // 获取样式
$('div').attr('style') // 获取style属性
5. 操作DOM#
// 修改内容
$('div').text('新文本') // 设置文本
$('div').html('<span>内容</span>') // 设置HTML
// 添加元素
$('ul').append('<li>新项</li>') // 内部末尾
$('ul').prepend('<li>首项</li>') // 内部开头
$('div').after('<p>后面</p>') // 元素之后
$('div').before('<p>前面</p>') // 元素之前
$('div').wrap('<div class="wrap"></div>') // 包裹
// 删除元素
$('div').remove() // 删除元素本身
$('div').empty() // 清空子元素
$('div').detach() // 移除但保留数据
// 属性操作
$('div').attr('id', 'new-id') // 设置属性
$('div').removeAttr('class') // 删除属性
$('div').addClass('active') // 添加类
$('div').removeClass('old') // 移除类
$('div').toggleClass('hidden') // 切换类
6. 集合操作#
// 遍历
$('li').each((index, element) => {
console.log($(element).text());
});
// 映射
const texts = $('li').map((index, element) => {
return $(element).text();
}).get(); // .get() 转换为数组
// 筛选
$('div').filter('.important') // 根据选择器筛选
$('div').filter((index, element) => {
return $(element).attr('data-value') > 10;
}); // 根据函数筛选
// 切片
$('li').first() // 第一个
$('li').last() // 最后一个
$('li').eq(2) // 第3个(0-based)
$('li').slice(1, 4) // 第2-4个
🎯 四、使用场景#
1. 网络爬虫/数据抓取#
// 抓取新闻列表
async function scrapeNews() {
const html = await fetch('<https://news.site>');
const $ = cheerio.load(html);
const news = $('.news-item').map((i, el) => ({
title: $(el).find('.title').text(),
url: $(el).find('a').attr('href'),
time: $(el).find('.time').attr('datetime'),
summary: $(el).find('.summary').text()
})).get();
return news;
}
2. 内容聚合#
// 聚合多个RSS源
async function aggregateFeeds(feeds) {
const allArticles = [];
for (const feed of feeds) {
const $ = cheerio.load(feed.xml, { xmlMode: true });
$('item').each((i, item) => {
allArticles.push({
title: $(item).find('title').text(),
link: $(item).find('link').text(),
pubDate: $(item).find('pubDate').text()
});
});
}
return allArticles.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate));
}
3. HTML预处理#
// 清理用户输入的HTML
function sanitizeHtml(userHtml) {
const $ = cheerio.load(userHtml);
// 移除危险标签和属性
$('script, style, iframe, form').remove();
$('*').removeAttr('onclick onload onerror');
// 只保留安全的标签和属性
const allowedTags = ['p', 'br', 'b', 'i', 'strong', 'em', 'a', 'img'];
const allowedAttrs = ['href', 'src', 'alt', 'title'];
$('*').each((i, el) => {
if (!allowedTags.includes(el.tagName)) {
$(el).replaceWith($(el).html());
} else {
// 清理属性
const attrs = $(el).attr();
Object.keys(attrs).forEach(attr => {
if (!allowedAttrs.includes(attr)) {
$(el).removeAttr(attr);
}
});
}
});
return $.html();
}
4. 邮件模板处理#
// 处理邮件模板
function prepareEmailTemplate(template, data) {
const $ = cheerio.load(template);
// 替换占位符
$('[data-field]').each((i, el) => {
const field = $(el).data('field');
if (data[field]) {
$(el).text(data[field]);
}
});
// 处理条件区块
if (!data.isPremium) {
$('.premium-only').remove();
}
// 内联CSS(为了邮件兼容性)
inlineCss($);
return $.html();
}
5. SEO优化检查#
// 检查页面的SEO基础元素
function checkSeo(html) {
const $ = cheerio.load(html);
const issues = [];
// 检查标题
const title = $('title').text();
if (!title || title.length > 60) {
issues.push('标题问题');
}
// 检查meta描述
const description = $('meta[name="description"]').attr('content');
if (!description || description.length < 50) {
issues.push('描述问题');
}
// 检查h1数量
const h1Count = $('h1').length;
if (h1Count !== 1) {
issues.push(`有${h1Count}个h1标签`);
}
// 检查图片alt属性
$('img').each((i, img) => {
if (!$(img).attr('alt')) {
issues.push(`图片${i}缺少alt属性`);
}
});
return issues;
}
⚖️ 五、优缺点分析#
✅ 优点#
1. 性能卓越#
// 比无头浏览器快10-100倍
const start = Date.now();
const $ = cheerio.load(largeHtml);
// 处理百万级HTML字符只需几十毫秒
2. 内存友好#
- 不需要启动浏览器进程
- 没有V8实例开销
- 适合服务器端高并发场景
3. API友好#
// jQuery开发者零学习成本
$('.item').each(() => { ... }); // 熟悉的语法
$('#form').serialize(); // 相同的方法
4. 轻量级#
# 安装包很小
cheerio: ~1.0 MB
puppeteer: ~300 MB (包含Chromium)
5. 稳定性强#
- 纯JavaScript实现
- 无外部依赖(除htmlparser2)
- 无版本兼容问题
❌ 缺点#
1. 无法执行JavaScript#
// ❌ 无法获取动态内容
const html = `
<div id="app"></div>
<script>
document.getElementById('app').innerHTML = '<p>动态内容</p>';
</script>
`;
const $ = cheerio.load(html);
console.log($('#app').html()); // 输出: null
2. 无CSS计算#
// ❌ 无法获取计算样式
const html = '<div style="color: red;">文本</div>';
const $ = cheerio.load(html);
console.log($('div').css('color')); // 输出: "red"(仅是属性值)
// 无法获取继承或计算的样式
3. 有限的浏览器API#
// 这些都不存在:
window
document.cookie
localStorage
XMLHttpRequest
fetch
Event
4. 选择器限制#
// 部分CSS3选择器不支持
$('input:checked') // ❌ 不支持状态伪类
$('div:hover') // ❌ 不支持交互伪类
$('::before') // ❌ 不支持伪元素
5. 无法处理iframe/框架页#
// ❌ 无法访问iframe内容
const html = `
<iframe src="/inner.html"></iframe>
`;
const $ = cheerio.load(html);
console.log($('iframe').contents()); // 无法获取iframe内的DOM
🔧 六、最佳实践#
1. 错误处理#
try {
const $ = cheerio.load(html);
// 操作DOM
} catch (error) {
if (error.message.includes('not well-formed')) {
console.log('HTML格式错误');
}
// 处理其他错误
}
2. 性能优化#
// 1. 限制选择范围
const $container = $('#main');
$container.find('.item'); // 比 $('.item') 快
// 2. 缓存选择结果
const $items = $('.item'); // 缓存起来重复使用
// 3. 使用原生方法
// ❌ 慢
$('div').each(() => { ... });
// ✅ 快
const divs = $('div').get();
divs.forEach(div => { ... });
3. 处理特殊字符#
// 配置decodeEntities正确处理编码
const $ = cheerio.load(html, {
decodeEntities: false // 保留HTML实体
});
// 或者手动处理
function decodeHtmlEntities(text) {
const $ = cheerio.load(`<div>${text}</div>`);
return $('div').text();
}
4. 与动态内容配合#
// 结合Puppeteer处理动态页面
async function scrapeDynamicPage(url) {
// 1. 用Puppeteer获取渲染后的HTML
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);
await page.waitForSelector('.dynamic-content');
const html = await page.content();
await browser.close();
// 2. 用Cheerio高效解析
const $ = cheerio.load(html);
return $('.data-item').map((i, el) => ({
text: $(el).text(),
// ...其他提取
})).get();
}
🎓 七、学习路径建议#
阶段1:基础掌握(1-2天)#
- 安装和基本加载
- 常用选择器使用
- 文本和属性提取
阶段2:中级应用(3-5天)#
- DOM遍历方法
- 集合操作(each, map, filter)
- HTML生成和修改
阶段3:高级实战(1-2周)#
- 复杂HTML结构解析
- 性能优化技巧
- 与其他工具配合(Puppeteer, Request等)
阶段4:源码理解(可选)#
- 了解htmlparser2原理
- 学习jQuery API设计
- 理解虚拟DOM实现
📈 八、总结#
Cheerio是Node.js生态中最优秀的静态HTML解析工具,特别适合:
- 需要高性能的网页抓取场景
- 处理大量HTML数据的后端应用
- 熟悉jQuery的开发者快速上手
- 不需要JavaScript执行的解析任务 选择Cheerio当:
- 网页内容都是静态HTML
- 需要处理大量页面
- 对性能要求极高
- 在服务器端运行 不选Cheerio当:
- 页面依赖JavaScript渲染
- 需要与页面交互(点击、滚动)
- 需要获取计算后的样式
- 需要处理iframe或跨域内容 掌握Cheerio能让你在Web抓取和HTML处理领域如鱼得水,它是每个Node.js开发者都应该掌握的重要工具之一。