返回文章列表

文章

关于Cheerio

目录
  1. 📚 一、基本概念
  2. 1. 是什么
  3. 2. 核心特点
  4. 3. 底层架构
  5. 4. 与相关技术的对比
  6. 🛠 二、能做什么
  7. 1. 数据提取(主要用途)
  8. 2. 网页内容分析
  9. 3. HTML 清理与转换
  10. 4. 模板处理
  11. 5. 测试辅助
  12. 🚀 三、怎么使用
  13. 1. 安装与初始化
  14. 2. 选择元素
  15. 3. 遍历与查找
  16. 4. 提取数据
  17. 5. 操作DOM
  18. 6. 集合操作
  19. 🎯 四、使用场景
  20. 1. 网络爬虫/数据抓取
  21. 2. 内容聚合
  22. 3. HTML预处理
  23. 4. 邮件模板处理
  24. 5. SEO优化检查
  25. ⚖️ 五、优缺点分析
  26. ✅ 优点
  27. 1. 性能卓越
  28. 2. 内存友好
  29. 3. API友好
  30. 4. 轻量级
  31. 5. 稳定性强
  32. ❌ 缺点
  33. 1. 无法执行JavaScript
  34. 2. 无CSS计算
  35. 3. 有限的浏览器API
  36. 4. 选择器限制
  37. 5. 无法处理iframe/框架页
  38. 🔧 六、最佳实践
  39. 1. 错误处理
  40. 2. 性能优化
  41. 3. 处理特殊字符
  42. 4. 与动态内容配合
  43. 🎓 七、学习路径建议
  44. 阶段1:基础掌握(1-2天)
  45. 阶段2:中级应用(3-5天)
  46. 阶段3:高级实战(1-2周)
  47. 阶段4:源码理解(可选)
  48. 📈 八、总结
  49. 📎 参考文章

📚 一、基本概念#

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渲染速度用途
CheerioNode.js⚡极快静态HTML解析
jsdomNode.js中等模拟浏览器环境
PuppeteerNode.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天)#

  1. 安装和基本加载
  2. 常用选择器使用
  3. 文本和属性提取

阶段2:中级应用(3-5天)#

  1. DOM遍历方法
  2. 集合操作(each, map, filter)
  3. HTML生成和修改

阶段3:高级实战(1-2周)#

  1. 复杂HTML结构解析
  2. 性能优化技巧
  3. 与其他工具配合(Puppeteer, Request等)

阶段4:源码理解(可选)#

  1. 了解htmlparser2原理
  2. 学习jQuery API设计
  3. 理解虚拟DOM实现

📈 八、总结#

Cheerio是Node.js生态中最优秀的静态HTML解析工具,特别适合:

  1. 需要高性能的网页抓取场景
  2. 处理大量HTML数据的后端应用
  3. 熟悉jQuery的开发者快速上手
  4. 不需要JavaScript执行的解析任务 选择Cheerio当:
  • 网页内容都是静态HTML
  • 需要处理大量页面
  • 对性能要求极高
  • 在服务器端运行 不选Cheerio当:
  • 页面依赖JavaScript渲染
  • 需要与页面交互(点击、滚动)
  • 需要获取计算后的样式
  • 需要处理iframe或跨域内容 掌握Cheerio能让你在Web抓取和HTML处理领域如鱼得水,它是每个Node.js开发者都应该掌握的重要工具之一。

📎 参考文章#