返回文章列表

文章

Cheerio 使用技巧总结

目录
  1. 🎯 核心概念
  2. 1. 加载 HTML
  3. 2. 选择器类型
  4. 🔍 精准查找技巧
  5. 1. 避免文本匹配污染
  6. 2. DOM 遍历方法
  7. 🛠 数据提取技巧
  8. 1. 属性提取
  9. 2. 文本提取
  10. 🎨 操作 DOM
  11. 1. 修改内容
  12. 2. 操作类和属性
  13. 🧩 高级技巧
  14. 1. 处理动态内容(常见问题)
  15. 2. 提取表格数据
  16. 3. 链式操作优化
  17. 🐛 调试技巧
  18. 1. 查看元素结构
  19. 2. 选择性打印
  20. 📦 实用工具函数
  21. 1. 清理和格式化
  22. 2. 安全获取
  23. ⚠️ 常见陷阱与解决方案
  24. 1. 空元素检查
  25. 2. 动态类名处理
  26. 3. Cheerio vs jQuery 差异
  27. 🚀 性能优化
  28. 📎 参考文章

🎯 核心概念#

1. 加载 HTML#

const cheerio = require('cheerio');
const $ = cheerio.load(html, {
  decodeEntities: false,  // 是否解码实体,默认为 true
  xmlMode: false,        // XML 模式,默认为 false
});

2. 选择器类型#

// CSS 选择器
$('div.class')           // 类选择器
$('div#id')              // ID 选择器
$('div[data-value]')     // 属性选择器
$('parent > child')      // 子选择器
$('div:first-child')     // 伪类选择器

// 自定义筛选
$('div').filter((i, el) => $(el).text().includes('文本'))
$('div').find('span')    // 后代查找
$('div').children()      // 直接子元素

🔍 精准查找技巧#

1. 避免文本匹配污染#

// ❌ 错误:可能匹配到父元素
$("div").filter((_, el) => $(el).text().trim() === "目标文本")

// ✅ 正确:确保是直接文本
$("div").filter((_, el) => {
  const $el = $(el);
  // 检查元素是否直接包含文本(没有子元素)
  return $el.children().length === 0 && $el.text().trim() === "目标文本";
})

// ✅ 使用 :contains 配合检查
$("div:contains('目标文本')").filter((_, el) => {
  return $(el).children().length === 0;
})

2. DOM 遍历方法#

const $element = $('.target');

// 向上查找
$element.parent()                    // 直接父元素
$element.parents('.container')       // 所有祖先元素中匹配选择器的
$element.closest('.wrapper')         // 最近的匹配祖先

// 向下查找
$element.children('.child')          // 直接子元素
$element.find('.descendant')         // 所有后代元素

// 同级查找
$element.next()                      // 下一个兄弟
$element.nextAll('.sibling')         // 后面所有兄弟
$element.prev()                      // 上一个兄弟
$element.prevAll('.sibling')         // 前面所有兄弟
$element.siblings()                  // 所有兄弟元素

🛠 数据提取技巧#

1. 属性提取#

// 获取属性
const href = $('a').attr('href');
const data = $('div').data('value');  // data-value 属性

// 获取所有属性
const attrs = $('div').attr();
Object.keys(attrs).forEach(key => {
  if (key.startsWith('data-')) {
    console.log(`数据属性 ${key}: ${attrs[key]}`);
  }
});

// 获取多个元素的属性
const links = $('a').map((i, el) => $(el).attr('href')).get();

2. 文本提取#

// 获取元素文本
const text = $('div').text();                     // 包含子元素文本
const ownText = $('div').contents()               // 只获取直接文本
  .filter((_, node) => node.type === 'text')
  .text();

// 获取多个元素文本
const texts = $('li').map((i, el) => $(el).text().trim()).get();

// 获取 HTML
const html = $('div').html();                     // 内部 HTML
const outerHtml = $.html($('div'));               // 外层 HTML

🎨 操作 DOM#

1. 修改内容#

// 设置内容
$('div').text('新文本');              // 设置文本
$('div').html('<span>新HTML</span>'); // 设置HTML

// 添加内容
$('div').append('<span>追加</span>');  // 内部末尾
$('div').prepend('<span>前插</span>'); // 内部开头
$('div').after('<div>之后</div>');     // 元素之后
$('div').before('<div>之前</div>');    // 元素之前

// 替换元素
$('div').replaceWith('<p>替换</p>');

2. 操作类和属性#

// 类操作
$('div').addClass('new-class');
$('div').removeClass('old-class');
$('div').toggleClass('active');
$('div').hasClass('target');          // 返回布尔值

// 属性操作
$('div').attr('title', '新标题');      // 设置
$('div').removeAttr('data-old');       // 删除

🧩 高级技巧#

1. 处理动态内容(常见问题)#

// 检查是否为空元素(可能动态加载)
const $content = $('.dynamic-content');
if ($content.length > 0 && $content.html().trim() === '') {
  console.log('内容可能是动态加载的');

  // 查找可能的脚本数据
  const scripts = $('script').filter((_, el) => {
    const text = $(el).html();
    return text && text.includes('JSON') || text.includes('data');
  });

  scripts.each((i, el) => {
    const scriptText = $(el).html();
    // 尝试提取 JSON
    const jsonMatch = scriptText.match(/\\{[\\s\\S]*\\}/);
    if (jsonMatch) {
      try {
        const data = JSON.parse(jsonMatch[0]);
        console.log('找到JSON数据:', data);
      } catch(e) {
        console.log('JSON解析失败');
      }
    }
  });
}

2. 提取表格数据#

const tableData = [];
$('table tr').each((rowIndex, row) => {
  const rowData = [];
  $(row).find('td, th').each((cellIndex, cell) => {
    // 处理合并单元格等情况
    const colspan = parseInt($(cell).attr('colspan') || '1');
    const rowspan = parseInt($(cell).attr('rowspan') || '1');
    const text = $(cell).text().trim();

    rowData.push({
      text,
      colspan,
      rowspan,
      row: rowIndex,
      col: cellIndex
    });
  });
  if (rowData.length > 0) {
    tableData.push(rowData);
  }
});

3. 链式操作优化#

// 避免重复查找
const $items = $('.list-item');

// ❌ 错误:多次查找
const first = $('.list-item').first();
const last = $('.list-item').last();

// ✅ 正确:链式操作
const $items = $('.list-item');
const first = $items.first();
const last = $items.last();

// ✅ 高效遍历
const results = $('.item')
  .map((i, el) => {
    const $el = $(el);
    return {
      title: $el.find('.title').text().trim(),
      link: $el.find('a').attr('href'),
      desc: $el.find('.desc').text().trim()
    };
  })
  .get();  // .get() 将 cheerio 对象转为数组

🐛 调试技巧#

1. 查看元素结构#

// 查看元素及子元素
console.log('HTML:', $.html($element));
console.log('Outer HTML:', $element.toString());
console.log('Text content:', $element.text());

// 查看所有属性
console.log('Attributes:', $element.attr());

// 查看父级结构
let $parent = $element;
for (let i = 0; i < 3 && $parent.length; i++) {
  console.log(`Parent ${i}:`, $parent.prop('tagName'), $parent.attr('class'));
  $parent = $parent.parent();
}

2. 选择性打印#

// 打印前几个元素
$('div').slice(0, 5).each((i, el) => {
  console.log(`Element ${i}:`, $(el).html());
});

// 打印特定属性的元素
$('*').each((i, el) => {
  const $el = $(el);
  if ($el.attr('data-src')) {
    console.log(`Found data-src:`, $el.attr('data-src'));
  }
});

📦 实用工具函数#

1. 清理和格式化#

function cleanText(text) {
  return text
    .replace(/\\s+/g, ' ')      // 多个空格合并为一个
    .replace(/\\n\\s*\\n/g, '\\n') // 多个空行合并为一个
    .trim();
}

function extractNumber(text) {
  const match = text.match(/\\d+(\\.\\d+)?/);
  return match ? parseFloat(match[0]) : null;
}

2. 安全获取#

function safeGet($element, selector, defaultValue = '') {
  const $found = $element.find(selector);
  return $found.length ? $found.text().trim() : defaultValue;
}

function safeAttr($element, selector, attr, defaultValue = '') {
  const $found = $element.find(selector);
  return $found.length ? $found.attr(attr) || defaultValue : defaultValue;
}

⚠️ 常见陷阱与解决方案#

1. 空元素检查#

// ❌ 错误:只检查长度
if ($('.item').length) {
  // .item 可能存在但内容为空
}

// ✅ 正确:检查内容和长度
const $items = $('.item');
if ($items.length && $items.text().trim()) {
  // 确实有内容
}

2. 动态类名处理#

// 当类名包含动态哈希时(如 class_zJFy1)
const $elements = $('div[class*="container_"]');  // 部分匹配
const $elements = $('div[class^="container"]');   // 开头匹配
const $elements = $('div[class$="container"]');   // 结尾匹配

3. Cheerio vs jQuery 差异#

  • 无 DOM 环境:Cheerio 没有浏览器环境,不能执行 JavaScript
  • 无事件系统:不能绑定或触发事件
  • 有限的伪类支持:部分 jQuery 伪类不可用
  • 性能更好:比 jQuery 更轻量,适合服务器端

🚀 性能优化#

// 1. 限制查找范围
const $container = $('#main-content');
$container.find('.item');  // 只在容器内查找

// 2. 使用更具体的选择器
$('div.list > ul.items > li.item');  // 比 $('.item') 更快

// 3. 缓存结果
const $links = $('a');  // 缓存后重复使用

// 4. 减少 DOM 操作
// ❌ 不好:频繁操作
$('.item').each((i, el) => {
  $(el).addClass('processed');
});

// ✅ 更好:批量操作
$('.item').addClass('processed');

Cheerio 是一个非常强大的 HTML 解析库,掌握这些技巧可以让你更高效地处理各种网页抓取和解析任务。记住,如果页面内容是通过 JavaScript 动态加载的,可能需要考虑使用 Puppeteer 或 Playwright 等工具。

📎 参考文章#