Cheerio
Cheerio是一个基于Node.js的快速、灵活、功能强大的HTML解析器和DOM操作库,类似于jQuery,但主要用于服务器端的Web应用程序。Cheerio可以像jQuery一样使用CSS选择器、DOM遍历、事件处理等功能,可以方便地从HTML文档中提取数据、修改内容、操纵DOM等。Cheerio的核心代码非常小,只有几百行代码,因此它非常快速、轻量级、易于使用。Cheerio还支持多种插件和扩展,如cheerio-tableparser、cheerio-eq等,可以扩展其功能以满足各种需求。由于Cheerio的性能和易用性,它已经成为Node.js中最受欢迎的HTML解析和DOM操纵库之一,并被广泛用于Web爬虫、数据挖掘、数据抓取等应用程序的开发。

查看更多相关内容
Cheerio 和 jsdom 有什么区别?如何选择使用?Cheerio 和 jsdom 都是 Node.js 中处理 HTML/XML 的工具,但它们的设计理念和实现方式有显著差异。以下是详细的对比分析:
## 1. 核心架构对比
### Cheerio
- **类型**:HTML 解析器
- **底层实现**:基于 htmlparser2
- **DOM 实现**:自定义的轻量级 DOM 实现
- **JavaScript 执行**:不支持
- **浏览器环境模拟**:不模拟
### jsdom
- **类型**:完整的 DOM 和浏览器环境模拟器
- **底层实现**:基于 WHATWG DOM 标准
- **DOM 实现**:完整的 W3C DOM 规范实现
- **JavaScript 执行**:完全支持
- **浏览器环境模拟**:完整模拟
## 2. 功能对比表
| 特性 | Cheerio | jsdom |
|------|---------|-------|
| **HTML 解析** | ✅ 快速 | ✅ 标准 |
| **CSS 选择器** | ✅ jQuery 风格 | ✅ 标准 |
| **DOM 操作** | ✅ 基础操作 | ✅ 完整 API |
| **JavaScript 执行** | ❌ 不支持 | ✅ 完全支持 |
| **事件处理** | ❌ 不支持 | ✅ 完全支持 |
| **性能** | ⚡ 极快 | 🐢 较慢 |
| **内存占用** | 📉 低 | 📈 高 |
| **浏览器 API** | ❌ 无 | ✅ 完整 |
| **网络请求** | ❌ 无 | ✅ 支持 |
| **Canvas** | ❌ 无 | ✅ 支持 |
| **LocalStorage** | ❌ 无 | ✅ 支持 |
## 3. 使用示例对比
### Cheerio 使用示例
```javascript
const cheerio = require('cheerio');
const html = `
<div id="container">
<p class="text">Hello World</p>
<button onclick="alert('Clicked')">Click</button>
</div>
`;
const $ = cheerio.load(html);
// 基本操作
console.log($('#container').text()); // "Hello World"
console.log($('.text').text()); // "Hello World"
// DOM 操作
$('.text').addClass('highlight');
console.log($('.text').attr('class')); // "text highlight"
// 无法执行 JavaScript
$('button').click(); // 无效,Cheerio 不支持事件
```
### jsdom 使用示例
```javascript
const { JSDOM } = require('jsdom');
const html = `
<div id="container">
<p class="text">Hello World</p>
<button onclick="alert('Clicked')">Click</button>
</div>
`;
const dom = new JSDOM(html);
const document = dom.window.document;
// 基本操作
console.log(document.getElementById('container').textContent); // "Hello World"
console.log(document.querySelector('.text').textContent); // "Hello World"
// DOM 操作
document.querySelector('.text').classList.add('highlight');
console.log(document.querySelector('.text').className); // "text highlight"
// 可以执行 JavaScript
const button = document.querySelector('button');
button.click(); // 有效,会触发 onclick 事件
// 可以使用浏览器 API
console.log(dom.window.innerWidth); // 窗口宽度
console.log(dom.window.location.href); // 当前 URL
```
## 4. 性能对比
### 解析速度测试
```javascript
const cheerio = require('cheerio');
const { JSDOM } = require('jsdom');
const largeHtml = '<div>' + '<p>Test</p>'.repeat(10000) + '</div>';
// Cheerio 性能测试
const start1 = Date.now();
const $ = cheerio.load(largeHtml);
const cheerioTime = Date.now() - start1;
console.log(`Cheerio: ${cheerioTime}ms`);
// jsdom 性能测试
const start2 = Date.now();
const dom = new JSDOM(largeHtml);
const jsdomTime = Date.now() - start2;
console.log(`jsdom: ${jsdomTime}ms`);
// 典型结果:
// Cheerio: 5-10ms
// jsdom: 100-500ms
```
### 内存占用对比
```javascript
// Cheerio - 内存占用低
function cheerioMemoryTest() {
const $ = cheerio.load(largeHtml);
const elements = $('p');
return elements.length;
}
// jsdom - 内存占用高
function jsdomMemoryTest() {
const dom = new JSDOM(largeHtml);
const elements = dom.window.document.querySelectorAll('p');
return elements.length;
}
```
## 5. 适用场景对比
### 使用 Cheerio 的场景
```javascript
// 1. 网页爬虫和数据提取
async function scrapeWebsite() {
const axios = require('axios');
const response = await axios.get('https://example.com');
const $ = cheerio.load(response.data);
return {
title: $('title').text(),
links: $('a').map((i, el) => $(el).attr('href')).get()
};
}
// 2. HTML 内容处理
function processHtml(html) {
const $ = cheerio.load(html);
$('script').remove(); // 移除脚本
$('style').remove(); // 移除样式
return $.html();
}
// 3. 批量处理大量文档
function batchProcess(htmlList) {
return htmlList.map(html => {
const $ = cheerio.load(html);
return $('title').text();
});
}
```
### 使用 jsdom 的场景
```javascript
// 1. 测试前端代码
const { JSDOM } = require('jsdom');
function testFrontendCode() {
const dom = new JSDOM(`
<div id="app"></div>
<script>
document.getElementById('app').textContent = 'Hello';
</script>
`, { runScripts: 'dangerously' });
console.log(dom.window.document.getElementById('app').textContent);
}
// 2. 服务端渲染 (SSR)
function renderComponent(component) {
const dom = new JSDOM('<div id="root"></div>');
const root = dom.window.document.getElementById('root');
// 执行组件代码
component(root);
return dom.serialize();
}
// 3. 处理需要 JavaScript 的内容
function processDynamicContent(html) {
const dom = new JSDOM(html, {
runScripts: 'dangerously',
resources: 'usable'
});
// 等待 JavaScript 执行完成
return new Promise(resolve => {
dom.window.onload = () => {
resolve(dom.serialize());
};
});
}
```
## 6. API 对比
### Cheerio API 特点
```javascript
const $ = cheerio.load(html);
// jQuery 风格的 API
$('.class').text();
$('.class').html();
$('.class').attr('href');
$('.class').addClass('active');
$('.class').find('a');
// 链式调用
$('.container')
.find('.item')
.addClass('highlight')
.text();
// 不支持的浏览器 API
$.window; // undefined
$.document; // undefined
$.localStorage; // undefined
```
### jsdom API 特点
```javascript
const dom = new JSDOM(html);
const document = dom.window.document;
// 标准 DOM API
document.querySelector('.class').textContent;
document.querySelector('.class').innerHTML;
document.querySelector('.class').getAttribute('href');
document.querySelector('.class').classList.add('active');
document.querySelector('.class').querySelector('a');
// 支持浏览器 API
dom.window.innerWidth;
dom.window.location.href;
dom.window.localStorage;
dom.window.fetch;
dom.window.console;
```
## 7. 选择建议
### 选择 Cheerio 的情况
1. **只需要解析和提取数据**
- 网页爬虫
- 数据抓取
- HTML 内容处理
2. **性能要求高**
- 处理大量文档
- 批量操作
- 实时处理
3. **资源受限**
- 内存有限
- CPU 有限
- 无服务器环境
4. **不需要浏览器功能**
- 不需要执行 JavaScript
- 不需要事件处理
- 不需要浏览器 API
### 选择 jsdom 的情况
1. **需要完整的浏览器环境**
- 前端代码测试
- 服务端渲染
- 组件测试
2. **需要执行 JavaScript**
- 动态内容处理
- 客户端代码执行
- 框架渲染
3. **需要浏览器 API**
- LocalStorage
- Fetch API
- Canvas
- Web Workers
4. **需要标准 DOM 行为**
- 事件冒泡
- DOM 事件
- 浏览器兼容性测试
## 8. 混合使用场景
```javascript
// 先用 jsdom 执行 JavaScript,再用 Cheerio 解析
const { JSDOM } = require('jsdom');
const cheerio = require('cheerio');
async function hybridProcess(html) {
// 1. 使用 jsdom 执行 JavaScript
const dom = new JSDOM(html, { runScripts: 'dangerously' });
// 等待 JavaScript 执行
await new Promise(resolve => {
dom.window.onload = resolve;
});
// 2. 获取执行后的 HTML
const processedHtml = dom.serialize();
// 3. 使用 Cheerio 快速解析
const $ = cheerio.load(processedHtml);
return {
title: $('title').text(),
content: $('.content').text()
};
}
```
## 总结
- **Cheerio**:轻量、快速、专注数据提取,适合爬虫和静态 HTML 处理
- **jsdom**:完整、标准、模拟浏览器,适合测试和动态内容处理
- **选择原则**:根据需求选择,需要性能用 Cheerio,需要完整功能用 jsdom
- **混合使用**:可以结合两者优势,先用 jsdom 执行 JS,再用 Cheerio 解析
服务端 · 2月22日 14:31
Cheerio 和 Puppeteer 有什么区别?如何选择使用?Cheerio 和 Puppeteer 都是 Node.js 中用于处理网页的工具,但它们的设计目标和使用场景有显著差异:
## 1. 核心区别
| 特性 | Cheerio | Puppeteer |
|------|---------|-----------|
| **类型** | HTML 解析器 | 浏览器自动化工具 |
| **JavaScript 执行** | 不支持 | 完全支持 |
| **动态内容** | 无法处理 | 完全支持 |
| **性能** | 极快 | 较慢 |
| **资源消耗** | 低 | 高 |
| **API** | jQuery 风格 | 浏览器 DevTools 协议 |
| **使用场景** | 静态 HTML 解析 | 动态网页、截图、PDF |
## 2. Cheerio 的特点
### 优势
- **轻量快速**:核心代码只有几百行,解析速度极快
- **简单易用**:jQuery 风格的 API,学习成本低
- **低资源消耗**:不需要启动浏览器,内存占用少
- **适合批量处理**:可以快速处理大量静态页面
### 局限性
- **无法执行 JavaScript**:只能解析静态 HTML
- **无法处理动态内容**:无法获取通过 JS 动态加载的数据
- **无法处理复杂交互**:不支持点击、滚动等用户操作
- **无法截图或生成 PDF**:没有可视化能力
### 适用场景
```javascript
// 适合:静态网页数据提取
const cheerio = require('cheerio');
const axios = require('axios');
async function scrapeStaticSite() {
const response = await axios.get('https://example.com');
const $ = cheerio.load(response.data);
return {
title: $('title').text(),
links: $('a').map((i, el) => $(el).attr('href')).get()
};
}
```
## 3. Puppeteer 的特点
### 优势
- **完整浏览器环境**:使用真实的 Chrome/Chromium
- **JavaScript 执行**:可以执行页面中的所有 JavaScript
- **动态内容支持**:可以获取 AJAX 加载的数据
- **交互能力**:支持点击、输入、滚动等操作
- **可视化功能**:支持截图、生成 PDF
- **网络拦截**:可以监控和修改网络请求
### 局限性
- **资源消耗大**:需要启动完整的浏览器实例
- **速度较慢**:相比 Cheerio 慢很多
- **复杂度高**:API 相对复杂,学习成本高
- **部署困难**:在某些服务器环境部署较复杂
### 适用场景
```javascript
// 适合:动态网页、需要交互的场景
const puppeteer = require('puppeteer');
async function scrapeDynamicSite() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
// 等待动态内容加载
await page.waitForSelector('.dynamic-content');
const data = await page.evaluate(() => {
return {
title: document.title,
content: document.querySelector('.dynamic-content').textContent
};
});
await browser.close();
return data;
}
```
## 4. 性能对比
```javascript
// Cheerio - 快速解析
const cheerio = require('cheerio');
async function cheerioBenchmark() {
const start = Date.now();
const $ = cheerio.load(htmlString);
const items = $('.item').map((i, el) => $(el).text()).get();
const time = Date.now() - start;
console.log(`Cheerio: ${time}ms, ${items.length} items`);
// 结果:通常 < 10ms
}
// Puppeteer - 完整浏览器
const puppeteer = require('puppeteer');
async function puppeteerBenchmark() {
const start = Date.now();
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(htmlString);
const items = await page.$$eval('.item', elements =>
elements.map(el => el.textContent)
);
await browser.close();
const time = Date.now() - start;
console.log(`Puppeteer: ${time}ms, ${items.length} items`);
// 结果:通常 500-2000ms
}
```
## 5. 选择建议
### 使用 Cheerio 的场景
- 网站内容是静态 HTML
- 需要处理大量页面
- 对性能要求高
- 只需要提取数据,不需要交互
- 服务器资源有限
### 使用 Puppeteer 的场景
- 网站使用 JavaScript 动态加载内容
- 需要模拟用户操作(点击、滚动等)
- 需要截图或生成 PDF
- 需要处理复杂的 SPA 应用
- 需要监控网络请求
### 混合使用场景
```javascript
// 先用 Puppeteer 获取动态内容,再用 Cheerio 解析
const puppeteer = require('puppeteer');
const cheerio = require('cheerio');
async function hybridScrape() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 使用 Puppeteer 加载动态页面
await page.goto('https://example.com/dynamic');
await page.waitForSelector('.content');
// 获取 HTML
const html = await page.content();
await browser.close();
// 使用 Cheerio 快速解析
const $ = cheerio.load(html);
const data = $('.item').map((i, el) => ({
title: $(el).find('.title').text(),
content: $(el).find('.content').text()
})).get();
return data;
}
```
## 6. 实际应用示例
### Cheerio - 抓取静态博客
```javascript
async function scrapeBlog() {
const response = await axios.get('https://blog.example.com');
const $ = cheerio.load(response.data);
return $('.post').map((i, el) => ({
title: $(el).find('h2').text(),
date: $(el).find('.date').text(),
excerpt: $(el).find('.excerpt').text()
})).get();
}
```
### Puppeteer - 抓取动态电商网站
```javascript
async function scrapeShop() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://shop.example.com');
// 滚动加载更多商品
for (let i = 0; i < 5; i++) {
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(1000);
}
const products = await page.$$eval('.product', items =>
items.map(item => ({
name: item.querySelector('.name').textContent,
price: item.querySelector('.price').textContent
}))
);
await browser.close();
return products;
}
```
## 总结
- **Cheerio**:适合静态页面、高性能需求、批量处理
- **Puppeteer**:适合动态页面、需要交互、可视化需求
- **混合使用**:先用 Puppeteer 加载动态内容,再用 Cheerio 解析,可以获得最佳的性能和功能平衡
服务端 · 2月22日 14:30
如何使用 Cheerio 进行网页爬虫和数据抓取?Cheerio 在网页爬虫和数据抓取方面表现出色,因为它轻量、快速且易于使用。以下是使用 Cheerio 进行网页爬虫的完整指南:
## 1. 基本爬虫架构
```javascript
const cheerio = require('cheerio');
const axios = require('axios');
async function scrapePage(url) {
try {
// 1. 发送 HTTP 请求获取 HTML
const response = await axios.get(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
// 2. 使用 Cheerio 加载 HTML
const $ = cheerio.load(response.data);
// 3. 提取数据
const data = {
title: $('title').text(),
description: $('meta[name="description"]').attr('content'),
links: []
};
// 4. 提取所有链接
$('a[href]').each((index, element) => {
data.links.push({
text: $(element).text().trim(),
href: $(element).attr('href')
});
});
return data;
} catch (error) {
console.error('爬取失败:', error.message);
throw error;
}
}
```
## 2. 抓取新闻网站示例
```javascript
async function scrapeNews() {
const url = 'https://example-news.com';
const response = await axios.get(url);
const $ = cheerio.load(response.data);
const articles = [];
$('.news-item').each((index, element) => {
const $item = $(element);
articles.push({
title: $item.find('.title').text().trim(),
link: $item.find('a').attr('href'),
summary: $item.find('.summary').text().trim(),
date: $item.find('.date').text().trim(),
author: $item.find('.author').text().trim()
});
});
return articles;
}
```
## 3. 抓取电商产品信息
```javascript
async function scrapeProducts() {
const url = 'https://example-shop.com/products';
const response = await axios.get(url);
const $ = cheerio.load(response.data);
const products = [];
$('.product-card').each((index, element) => {
const $product = $(element);
const priceText = $product.find('.price').text();
const price = parseFloat(priceText.replace(/[^0-9.]/g, ''));
products.push({
name: $product.find('.product-name').text().trim(),
price: price,
originalPrice: parseFloat($product.find('.original-price').text().replace(/[^0-9.]/g, '')) || null,
discount: $product.find('.discount').text().trim(),
rating: parseFloat($product.find('.rating').attr('data-rating')),
reviews: parseInt($product.find('.review-count').text().replace(/[^0-9]/g, '')),
image: $product.find('img').attr('src'),
link: $product.find('a.product-link').attr('href')
});
});
return products;
}
```
## 4. 分页爬取
```javascript
async function scrapeMultiplePages(baseUrl, maxPages) {
const allData = [];
for (let page = 1; page <= maxPages; page++) {
const url = `${baseUrl}?page=${page}`;
console.log(`正在爬取第 ${page} 页...`);
try {
const response = await axios.get(url);
const $ = cheerio.load(response.data);
$('.item').each((index, element) => {
allData.push({
id: $(element).attr('data-id'),
title: $(element).find('.title').text().trim(),
content: $(element).find('.content').text().trim()
});
});
// 延迟避免被封
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
console.error(`第 ${page} 页爬取失败:`, error.message);
}
}
return allData;
}
```
## 5. 处理相对 URL
```javascript
const URL = require('url');
function resolveUrl(base, relative) {
return URL.resolve(base, relative);
}
// 使用示例
const baseUrl = 'https://example.com';
const relativeLink = '/article/123';
const absoluteUrl = resolveUrl(baseUrl, relativeLink);
// 结果: https://example.com/article/123
```
## 6. 数据清洗和验证
```javascript
function cleanData(rawData) {
return rawData.map(item => ({
title: item.title.replace(/\s+/g, ' ').trim(),
price: parseFloat(item.price) || 0,
description: item.description
.replace(/<[^>]*>/g, '') // 移除 HTML 标签
.replace(/\s+/g, ' ') // 合并空格
.trim(),
date: new Date(item.date),
isValid: item.title.length > 0 && item.price > 0
})).filter(item => item.isValid);
}
```
## 7. 错误处理和重试机制
```javascript
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await axios.get(url, {
timeout: 10000,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
return response.data;
} catch (error) {
console.log(`尝试 ${i + 1}/${maxRetries} 失败:`, error.message);
if (i === maxRetries - 1) {
throw error;
}
// 指数退避
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
}
}
```
## 8. 保存数据到文件
```javascript
const fs = require('fs');
function saveToFile(data, filename) {
const jsonData = JSON.stringify(data, null, 2);
fs.writeFileSync(filename, jsonData, 'utf8');
console.log(`数据已保存到 ${filename}`);
}
// 保存为 CSV
function saveToCSV(data, filename) {
if (data.length === 0) return;
const headers = Object.keys(data[0]).join(',');
const rows = data.map(item =>
Object.values(item).map(value =>
`"${String(value).replace(/"/g, '""')}"`
).join(',')
);
const csv = [headers, ...rows].join('\n');
fs.writeFileSync(filename, csv, 'utf8');
console.log(`CSV 已保存到 ${filename}`);
}
```
## 9. 完整爬虫示例
```javascript
const cheerio = require('cheerio');
const axios = require('axios');
const fs = require('fs');
class WebScraper {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.data = [];
}
async scrape(maxPages = 5) {
for (let page = 1; page <= maxPages; page++) {
await this.scrapePage(page);
await this.delay(1000);
}
this.saveData();
return this.data;
}
async scrapePage(page) {
const url = `${this.baseUrl}?page=${page}`;
console.log(`正在爬取: ${url}`);
try {
const html = await fetchWithRetry(url);
const $ = cheerio.load(html);
$('.article').each((index, element) => {
this.data.push(this.extractData($, element));
});
console.log(`第 ${page} 页完成,共 ${this.data.length} 条数据`);
} catch (error) {
console.error(`第 ${page} 页爬取失败:`, error.message);
}
}
extractData($, element) {
const $el = $(element);
return {
title: $el.find('.title').text().trim(),
author: $el.find('.author').text().trim(),
date: $el.find('.date').text().trim(),
content: $el.find('.content').text().trim(),
link: $el.find('a').attr('href'),
tags: $el.find('.tag').map((i, tag) => $(tag).text()).get()
};
}
saveData() {
const filename = `scraped_data_${Date.now()}.json`;
fs.writeFileSync(filename, JSON.stringify(this.data, null, 2));
console.log(`数据已保存到 ${filename}`);
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用示例
async function main() {
const scraper = new WebScraper('https://example-blog.com/articles');
const data = await scraper.scrape(10);
console.log(`爬取完成,共 ${data.length} 条数据`);
}
main().catch(console.error);
```
## 最佳实践
1. **设置合理的延迟**:避免频繁请求导致被封
2. **使用 User-Agent**:模拟真实浏览器请求
3. **处理异常**:完善的错误处理和重试机制
4. **数据验证**:清洗和验证提取的数据
5. **遵守 robots.txt**:尊重网站的爬虫规则
6. **增量更新**:只抓取新增或变化的数据
7. **并发控制**:使用队列控制并发请求数量
服务端 · 2月22日 14:30
Cheerio 使用中的常见问题有哪些?如何解决这些问题?Cheerio 提供了丰富的 API,但在实际使用中,开发者经常会遇到一些常见问题。以下是 Cheerio 使用中的常见问题及其解决方案:
## 1. 中文乱码问题
### 问题描述
当抓取包含中文的网页时,出现乱码。
### 解决方案
```javascript
const axios = require('axios');
const cheerio = require('cheerio');
const iconv = require('iconv-lite');
async function scrapeWithEncoding(url) {
// 方案1:设置响应类型为 arraybuffer
const response = await axios.get(url, {
responseType: 'arraybuffer',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
// 方案2:检测编码并转换
let html = response.data;
// 检测 Content-Type 中的编码
const contentType = response.headers['content-type'] || '';
const charsetMatch = contentType.match(/charset=([^;]+)/i);
if (charsetMatch) {
const charset = charsetMatch[1].toLowerCase();
if (charset !== 'utf-8') {
html = iconv.decode(Buffer.from(html), charset);
}
}
// 方案3:从 HTML meta 标签获取编码
const $temp = cheerio.load(html);
const metaCharset = $temp('meta[charset]').attr('charset');
if (metaCharset && metaCharset.toLowerCase() !== 'utf-8') {
html = iconv.decode(Buffer.from(html), metaCharset);
}
const $ = cheerio.load(html);
return $('title').text();
}
```
## 2. 选择器找不到元素
### 问题描述
使用选择器查询时返回空结果,但元素确实存在。
### 解决方案
```javascript
const cheerio = require('cheerio');
const html = `
<div class="container">
<p class="text">Hello</p>
</div>
`;
const $ = cheerio.load(html);
// 问题:选择器错误
console.log($('.container p.text').length); // 1
// 解决方案1:检查选择器语法
console.log($('.container > p.text').length); // 1
// 解决方案2:使用更宽松的选择器
console.log($('.container .text').length); // 1
// 解决方案3:逐步调试
console.log($('.container').length); // 1
console.log($('.container p').length); // 1
console.log($('.container p').hasClass('text')); // true
// 解决方案4:使用 contains() 查找包含文本的元素
console.log($('p:contains("Hello")').length); // 1
// 解决方案5:检查 HTML 是否正确加载
console.log($.html()); // 查看完整的 HTML
```
## 3. 动态内容无法获取
### 问题描述
页面中通过 JavaScript 动态加载的内容无法获取。
### 解决方案
```javascript
const cheerio = require('cheerio');
const axios = require('axios');
// 问题:直接使用 Cheerio 无法获取动态内容
async function scrapeStatic() {
const response = await axios.get('https://example.com/dynamic');
const $ = cheerio.load(response.data);
console.log($('.dynamic-content').text()); // 空
}
// 解决方案:结合 Puppeteer
const puppeteer = require('puppeteer');
async function scrapeDynamic() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/dynamic');
// 等待动态内容加载
await page.waitForSelector('.dynamic-content');
const html = await page.content();
await browser.close();
const $ = cheerio.load(html);
console.log($('.dynamic-content').text()); // 有内容
}
// 解决方案:直接调用 API
async function scrapeAPI() {
const response = await axios.get('https://example.com/api/data');
const data = response.data;
console.log(data); // 直接获取 JSON 数据
}
```
## 4. 内存占用过高
### 问题描述
处理大量 HTML 时内存占用过高,导致程序崩溃。
### 解决方案
```javascript
const cheerio = require('cheerio');
// 问题:一次性处理大文件
function processLargeFileBad(html) {
const $ = cheerio.load(html);
const results = [];
// 处理数百万个元素
$('.item').each((i, el) => {
results.push({
title: $(el).find('.title').text(),
content: $(el).find('.content').text()
});
});
return results;
}
// 解决方案1:分批处理
function processLargeFileGood(html) {
const $ = cheerio.load(html);
const batchSize = 1000;
const total = $('.item').length;
const results = [];
for (let i = 0; i < total; i += batchSize) {
const $batch = $('.item').slice(i, i + batchSize);
const batchData = $batch.map((j, el) => ({
title: $(el).find('.title').text(),
content: $(el).find('.content').text()
})).get();
results.push(...batchData);
// 及时清理
$batch = null;
// 强制垃圾回收(开发环境)
if (global.gc) {
global.gc();
}
}
return results;
}
// 解决方案2:使用流式处理
const fs = require('fs');
const { Transform } = require('stream');
function processWithStream(filePath) {
return new Promise((resolve, reject) => {
const results = [];
let buffer = '';
const transformStream = new Transform({
transform(chunk, encoding, callback) {
buffer += chunk.toString();
// 按标签分割处理
const items = buffer.match(/<item[^>]*>[\s\S]*?<\/item>/g) || [];
items.forEach(item => {
const $ = cheerio.load(item);
results.push({
title: $('.title').text(),
content: $('.content').text()
});
});
// 清理已处理的内容
const lastIndex = buffer.lastIndexOf('</item>');
if (lastIndex !== -1) {
buffer = buffer.slice(lastIndex + 7);
}
callback();
},
flush(callback) {
resolve(results);
callback();
}
});
fs.createReadStream(filePath)
.pipe(transformStream)
.on('error', reject);
});
}
```
## 5. 相对路径处理问题
### 问题描述
提取的链接是相对路径,无法直接访问。
### 解决方案
```javascript
const cheerio = require('cheerio');
const { URL } = require('url');
function resolveLinks(html, baseUrl) {
const $ = cheerio.load(html);
const links = [];
$('a[href]').each((i, el) => {
const href = $(el).attr('href');
const absoluteUrl = new URL(href, baseUrl).href;
links.push({
text: $(el).text().trim(),
href: href,
absoluteUrl: absoluteUrl
});
});
return links;
}
// 使用示例
const html = `
<a href="/page1">Page 1</a>
<a href="../page2">Page 2</a>
<a href="https://example.com/page3">Page 3</a>
`;
const links = resolveLinks(html, 'https://example.com/dir/index.html');
console.log(links);
```
## 6. 表单数据提取问题
### 问题描述
提取表单数据时遇到复选框、多选框等复杂情况。
### 解决方案
```javascript
const cheerio = require('cheerio');
function extractFormData(html) {
const $ = cheerio.load(html);
const formData = {};
// 文本输入
$('input[type="text"]').each((i, el) => {
const name = $(el).attr('name');
const value = $(el).val() || '';
formData[name] = value;
});
// 复选框(多选)
$('input[type="checkbox"]:checked').each((i, el) => {
const name = $(el).attr('name');
const value = $(el).val();
if (!formData[name]) {
formData[name] = [];
}
formData[name].push(value);
});
// 单选框
$('input[type="radio"]:checked').each((i, el) => {
const name = $(el).attr('name');
const value = $(el).val();
formData[name] = value;
});
// 下拉选择
$('select').each((i, el) => {
const name = $(el).attr('name');
const selectedOption = $(el).find('option:selected');
formData[name] = selectedOption.val();
});
// 多选下拉
$('select[multiple]').each((i, el) => {
const name = $(el).attr('name');
const selectedOptions = $(el).find('option:selected');
formData[name] = selectedOptions.map((j, opt) => $(opt).val()).get();
});
// 文本域
$('textarea').each((i, el) => {
const name = $(el).attr('name');
const value = $(el).val() || '';
formData[name] = value;
});
return formData;
}
```
## 7. HTML 实体编码问题
### 问题描述
HTML 中的特殊字符被编码,如 ` `、`&` 等。
### 解决方案
```javascript
const cheerio = require('cheerio');
const html = '<div>Hello & World Test</div>';
// 问题:默认会解码实体
const $ = cheerio.load(html);
console.log($('.div').text()); // "Hello & World Test"
// 解决方案1:禁用实体解码
const $2 = cheerio.load(html, { decodeEntities: false });
console.log($2('.div').text()); // "Hello & World Test"
// 解决方案2:手动处理实体
const he = require('he');
const text = he.decode($('.div').text());
console.log(text); // "Hello & World Test"
// 解决方案3:使用 html() 方法获取原始 HTML
const rawHtml = $('.div').html();
console.log(rawHtml); // "Hello & World Test"
```
## 8. 性能问题
### 问题描述
处理大量数据时性能不佳。
### 解决方案
```javascript
const cheerio = require('cheerio');
// 问题:使用复杂选择器
function slowQuery($) {
return $('div div div p span a').text();
}
// 解决方案1:使用更具体的选择器
function fastQuery1($) {
return $('.container .link').text();
}
// 解决方案2:使用 find() 方法
function fastQuery2($) {
return $('.container').find('.link').text();
}
// 解决方案3:缓存选择器结果
function fastQuery3($) {
const $container = $('.container');
return $container.find('.link').text();
}
// 解决方案4:使用原生方法
function fastestQuery($) {
const container = $('.container')[0];
return container.querySelector('.link').textContent;
}
```
## 9. 空白字符处理
### 问题描述
提取的文本包含大量空白字符。
### 解决方案
```javascript
const cheerio = require('cheerio');
const html = `
<div>
<p>
Hello
World
</p>
</div>
`;
const $ = cheerio.load(html);
// 问题:包含大量空白
console.log($('p').text()); // "\n Hello\n World\n "
// 解决方案1:使用 trim()
console.log($('p').text().trim()); // "Hello\n World"
// 解决方案2:使用正则替换
console.log($('p').text().replace(/\s+/g, ' ').trim()); // "Hello World"
// 解决方案3:使用 normalizeWhitespace 选项
const $2 = cheerio.load(html, { normalizeWhitespace: true });
console.log($2('p').text()); // "Hello World"
// 解决方案4:自定义清理函数
function cleanText(text) {
return text
.replace(/[\r\n\t]+/g, ' ') // 替换换行和制表符
.replace(/\s+/g, ' ') // 合并多个空格
.trim(); // 去除首尾空格
}
console.log(cleanText($('p').text())); // "Hello World"
```
## 10. XML 解析问题
### 问题描述
解析 XML 文档时出现问题。
### 解决方案
```javascript
const cheerio = require('cheerio');
const xml = `
<root>
<item id="1">
<name>Item 1</name>
</item>
<item id="2">
<name>Item 2</name>
</item>
</root>
`;
// 解决方案:使用 XML 模式
const $ = cheerio.load(xml, {
xmlMode: true,
decodeEntities: false
});
// 提取数据
const items = [];
$('item').each((i, el) => {
items.push({
id: $(el).attr('id'),
name: $(el).find('name').text()
});
});
console.log(items);
```
通过掌握这些常见问题的解决方案,可以更有效地使用 Cheerio 进行 HTML/XML 解析和数据提取。
服务端 · 2月22日 14:30
Cheerio 的 DOM 操作方法有哪些?如何使用这些方法?Cheerio 提供了丰富的 DOM 操作方法,与 jQuery 的 API 高度兼容。以下是常用的 DOM 操作方法:
## 1. 获取和设置内容
```javascript
// 获取文本内容
$('p').text(); // 获取第一个匹配元素的文本
$('p').text('New text'); // 设置所有匹配元素的文本
// 获取 HTML 内容
$('.container').html(); // 获取第一个匹配元素的 HTML
$('.container').html('<p>New</p>'); // 设置所有匹配元素的 HTML
// 获取表单值
$('input').val(); // 获取输入值
$('input').val('new value'); // 设置输入值
// 获取属性值
$('a').attr('href'); // 获取 href 属性
$('a').attr('href', 'new-url'); // 设置 href 属性
$('a').attr({ // 设置多个属性
href: 'new-url',
target: '_blank'
});
$('a').removeAttr('target'); // 移除属性
// 获取数据属性
$('div').data('id'); // 获取 data-id
$('div').data('id', '123'); // 设置 data-id
```
## 2. CSS 类操作
```javascript
// 添加类
$('div').addClass('active');
$('div').addClass('active highlight');
// 移除类
$('div').removeClass('active');
$('div').removeClass('active highlight');
// 切换类
$('div').toggleClass('active');
// 检查类
$('div').hasClass('active'); // 返回 true/false
// 获取类名
$('div').attr('class'); // 获取所有类名
```
## 3. CSS 样式操作
```javascript
// 获取样式
$('div').css('color'); // 获取 color 样式
// 设置样式
$('div').css('color', 'red');
$('div').css({ // 设置多个样式
color: 'red',
fontSize: '14px',
backgroundColor: '#f0f0f0'
});
```
## 4. DOM 遍历
```javascript
// 查找子元素
$('div').find('p'); // 查找所有后代 p
$('div').children(); // 获取直接子元素
$('div').children('p'); // 获取直接子元素 p
// 父元素
$('p').parent(); // 获取直接父元素
$('p').parents(); // 获取所有祖先元素
$('p').parents('.container'); // 获取匹配的祖先元素
$('p').closest('.container'); // 获取最近的匹配祖先
// 兄弟元素
$('p').next(); // 下一个兄弟元素
$('p').prev(); // 上一个兄弟元素
$('p').nextAll(); // 之后的所有兄弟元素
$('p').prevAll(); // 之前的所有兄弟元素
$('p').siblings(); // 所有兄弟元素
$('p').siblings('.active'); // 匹配的兄弟元素
```
## 5. DOM 插入
```javascript
// 内部插入
$('div').append('<p>New paragraph</p>'); // 在元素末尾插入
$('<p>New</p>').appendTo('div'); // 插入到元素末尾
$('div').prepend('<p>New paragraph</p>'); // 在元素开头插入
$('<p>New</p>').prependTo('div'); // 插入到元素开头
// 外部插入
$('div').after('<p>New paragraph</p>'); // 在元素之后插入
$('<p>New</p>').insertAfter('div'); // 插入到元素之后
$('div').before('<p>New paragraph</p>'); // 在元素之前插入
$('<p>New</p>').insertBefore('div'); // 插入到元素之前
// 替换
$('p').replaceWith('<div>New</div>'); // 替换元素
$('<div>New</div>').replaceAll('p'); // 替换所有匹配元素
```
## 6. DOM 删除
```javascript
// 删除元素
$('p').remove(); // 删除匹配的元素及其子元素
$('p').empty(); // 清空元素内容,保留元素本身
// 分离元素
const $detached = $('p').detach(); // 删除元素但保留数据和事件
```
## 7. DOM 复制
```javascript
// 克隆元素
const $clone = $('div').clone(); // 克隆元素
const $cloneWithEvents = $('div').clone(true); // 克隆元素并复制事件
```
## 8. 元素过滤
```javascript
// 过滤元素
$('li').filter('.active'); // 保留匹配的元素
$('li').not('.active'); // 移除匹配的元素
$('li').first(); // 获取第一个元素
$('li').last(); // 获取最后一个元素
$('li').eq(2); // 获取索引为 2 的元素
$('li').slice(1, 4); // 获取索引 1-3 的元素
// 查找元素
$('div').has('p'); // 包含 p 的 div
$('div').is('.active'); // 检查是否匹配
```
## 9. 集合操作
```javascript
// 获取元素数量
$('li').length; // 或 $('li').size()
// 获取索引
$('li').index(); // 当前元素在集合中的索引
$('li').index($('.active')); // 指定元素的索引
// 转换为数组
const texts = $('li').map(function() {
return $(this).text();
}).get(); // 转换为普通数组
// 遍历元素
$('li').each(function(i, elem) {
console.log(i, $(this).text());
});
// 获取 DOM 元素
const elem = $('div')[0]; // 获取第一个原生 DOM 元素
const elem = $('div').get(0); // 同上
const elems = $('div').get(); // 获取所有原生 DOM 元素
```
## 10. 实用示例
```javascript
// 提取文章标题和链接
const articles = [];
$('.article').each(function() {
articles.push({
title: $(this).find('h2').text(),
link: $(this).find('a').attr('href'),
summary: $(this).find('p').text().trim()
});
});
// 修改表格数据
$('table tr').each(function() {
const $row = $(this);
const price = parseFloat($row.find('.price').text());
if (price > 100) {
$row.addClass('highlight');
}
});
// 生成新的 HTML
const $container = cheerio.load('<div class="container"></div>');
data.items.forEach(item => {
$container('.container').append(`
<div class="item">
<h3>${item.title}</h3>
<p>${item.description}</p>
</div>
`);
});
```
服务端 · 2月22日 14:30
Cheerio 如何加载 HTML 内容?有哪些加载方式?Cheerio 提供了多种加载 HTML 内容的方法,适用于不同的使用场景:
## 1. 从 HTML 字符串加载
最常用的方法,直接传入 HTML 字符串:
```javascript
const cheerio = require('cheerio');
const $ = cheerio.load('<div class="content"><p>Hello</p></div>');
```
## 2. 从文件加载
读取 HTML 文件后加载:
```javascript
const fs = require('fs');
const cheerio = require('cheerio');
const html = fs.readFileSync('index.html', 'utf8');
const $ = cheerio.load(html);
```
## 3. 使用配置选项加载
`cheerio.load()` 方法接受第二个参数作为配置选项:
```javascript
const $ = cheerio.load(html, {
// 是否识别 XML 模式
xmlMode: false,
// 是否解码 HTML 实体
decodeEntities: true,
// 是否包含空白节点
withDomLvl1: false,
// 自�认函数处理 XML 标签
normalizeWhitespace: false,
// 使用 htmlparser2 的选项
xml: {
xmlMode: false,
decodeEntities: true
}
});
```
## 4. XML 模式加载
处理 XML 文档时启用 XML 模式:
```javascript
const xml = '<root><item>Value</item></root>';
const $ = cheerio.load(xml, { xmlMode: true });
```
## 5. 流式处理(结合其他库)
对于大文件,可以使用流式读取:
```javascript
const fs = require('fs');
const cheerio = require('cheerio');
const stream = fs.createReadStream('large.html');
let html = '';
stream.on('data', chunk => {
html += chunk;
});
stream.on('end', () => {
const $ = cheerio.load(html);
// 处理 DOM
});
```
## 最佳实践
- 对于小到中等大小的 HTML,直接使用 `cheerio.load()`
- 对于大文件,考虑分块处理或使用专门的流式解析器
- XML 文档务必设置 `xmlMode: true`
- 根据需求调整 `decodeEntities` 选项
服务端 · 2月22日 14:30
Cheerio 中常用的选择器有哪些?如何高效使用选择器?Cheerio 支持几乎所有 jQuery 的选择器语法,这使得熟悉 jQuery 的开发者可以快速上手。以下是 Cheerio 中常用的选择器类型:
## 1. 基本选择器
```javascript
// 元素选择器
$('div')
$('p')
$('a')
// ID 选择器
$('#header')
$('#main-content')
// 类选择器
$('.container')
$('.active')
// 多重选择器
$('div, p, a')
$('.class1, .class2')
```
## 2. 层级选择器
```javascript
// 后代选择器
$('div p') // div 内的所有 p 元素
$('.container a') // .container 内的所有 a 元素
// 子元素选择器
$('ul > li') // ul 的直接子元素 li
// 相邻兄弟选择器
$('h2 + p') // 紧跟在 h2 后的 p 元素
// 通用兄弟选择器
$('h2 ~ p') // h2 之后的所有兄弟 p 元素
```
## 3. 属性选择器
```javascript
// 存在属性
$('[href]')
$('[data-id]')
// 属性值匹配
$('[class="active"]')
$('[href="https://example.com"]')
// 属性值包含
$('[class*="btn"]') // class 包含 "btn"
$('[href*="/api/"]')
// 属性值开头
$('[class^="col-"]') // class 以 "col-" 开头
$('[href^="https://"]')
// 属性值结尾
$('[src$=".jpg"]') // src 以 ".jpg" 结尾
$('[href$=".html"]')
// 多属性选择器
$('a[href^="http"][target="_blank"]')
```
## 4. 伪类选择器
```javascript
// 位置伪类
$('li:first') // 第一个 li
$('li:last') // 最后一个 li
$('li:even') // 偶数位置的 li
$('li:odd') // 奇数位置的 li
$('li:eq(2)') // 第三个 li(从 0 开始)
$('li:gt(2)') // 索引大于 2 的 li
$('li:lt(5)') // 索引小于 5 的 li
// 内容伪类
$('p:contains("Hello")') // 包含 "Hello" 文本的 p
$('p:empty') // 空的 p 元素
$('div:has(p)') // 包含 p 的 div
// 表单伪类
$('input:checked') // 选中的 checkbox/radio
$('input:disabled') // 禁用的 input
$('input:enabled') // 启用的 input
$('input:focus') // 获得焦点的 input
$('option:selected') // 选中的 option
```
## 5. 表单选择器
```javascript
$(':text') // 文本输入框
$(':password') // 密码输入框
$(':radio') // 单选按钮
$(':checkbox') // 复选框
$(':submit') // 提交按钮
$(':reset') // 重置按钮
$(':button') // 按钮
$(':file') // 文件上传
$(':image') // 图片按钮
```
## 6. 组合选择器示例
```javascript
// 复杂选择器组合
$('.container > .row:first-child .col-md-4 a[href^="http"]')
// 多条件筛选
$('div.active[data-type="article"]:not(.hidden)')
// 嵌套选择
$('.content').find('p').filter('.highlight')
```
## 性能优化建议
1. **使用具体的选择器**:`$('#content p')` 比 `$('p')` 更快
2. **避免过度使用通配符**:`$('*')` 性能较差
3. **缓存选择结果**:重复使用时保存到变量
4. **使用 find() 代替层级选择器**:在某些情况下性能更好
5. **限制搜索范围**:先选择父元素再查找子元素
```javascript
// 好的做法
const $content = $('#content');
const $paragraphs = $content.find('p');
// 避免
$('p').each(function() {
if ($(this).parents('#content').length) {
// 处理
}
});
```
服务端 · 2月22日 14:30
如何开发和使用 Cheerio 插件?有哪些实用的插件示例?Cheerio 支持插件系统,可以通过插件扩展其功能。以下是 Cheerio 插件开发的完整指南:
## 1. Cheerio 插件基础
### 插件结构
Cheerio 插件本质上是一个函数,它接收 Cheerio 实例作为参数,并扩展其原型:
```javascript
// 基本插件结构
module.exports = function(cheerio) {
// 扩展 Cheerio 原型
cheerio.prototype.myMethod = function(selector) {
// 插件逻辑
return this;
};
};
```
### 简单插件示例
```javascript
// my-plugin.js
module.exports = function(cheerio) {
// 添加一个获取所有文本内容的方法
cheerio.prototype.getAllText = function() {
return this.map((i, el) => cheerio(el).text()).get();
};
// 添加一个过滤空元素的方法
cheerio.prototype.filterEmpty = function() {
return this.filter((i, el) => {
return cheerio(el).text().trim().length > 0;
});
};
};
```
## 2. 使用插件
### 安装和加载插件
```javascript
const cheerio = require('cheerio');
const myPlugin = require('./my-plugin');
// 加载插件
cheerio.use(myPlugin);
// 使用插件提供的方法
const $ = cheerio.load('<div><p>Hello</p><p></p></div>');
console.log($('p').getAllText()); // ['Hello']
console.log($('p').filterEmpty().length); // 1
```
### 链式调用
```javascript
// 插件方法支持链式调用
const result = $('p')
.filterEmpty()
.getAllText()
.map(text => text.toUpperCase());
```
## 3. 实用插件示例
### 1. 文本清理插件
```javascript
// text-cleaner.js
module.exports = function(cheerio) {
// 清理文本中的多余空白
cheerio.prototype.cleanText = function() {
return this.each((i, el) => {
const $el = cheerio(el);
const text = $el.text()
.replace(/\s+/g, ' ')
.trim();
$el.text(text);
});
};
// 移除指定标签
cheerio.prototype.removeTags = function(tags) {
const tagArray = Array.isArray(tags) ? tags : [tags];
return this.each((i, el) => {
const $el = cheerio(el);
tagArray.forEach(tag => {
$el.find(tag).remove();
});
});
};
};
```
### 2. 数据提取插件
```javascript
// data-extractor.js
module.exports = function(cheerio) {
// 提取表格数据为二维数组
cheerio.prototype.tableToArray = function() {
const result = [];
this.find('tr').each((i, row) => {
const rowData = [];
cheerio(row).find('td, th').each((j, cell) => {
rowData.push(cheerio(cell).text().trim());
});
result.push(rowData);
});
return result;
};
// 提取表格数据为对象数组
cheerio.prototype.tableToObjects = function() {
const $ = cheerio(this);
const headers = [];
const result = [];
// 提取表头
$.find('thead th, tr:first-child td').each((i, th) => {
headers.push(cheerio(th).text().trim());
});
// 提取数据行
$find('tbody tr, tr:not(:first-child)').each((i, row) => {
const obj = {};
cheerio(row).find('td').each((j, td) => {
const key = headers[j] || `col_${j}`;
obj[key] = cheerio(td).text().trim();
});
result.push(obj);
});
return result;
};
};
```
### 3. URL 处理插件
```javascript
// url-handler.js
const { URL } = require('url');
module.exports = function(cheerio) {
// 将相对 URL 转换为绝对 URL
cheerio.prototype.resolveUrls = function(baseUrl) {
return this.each((i, el) => {
const $el = cheerio(el);
const href = $el.attr('href');
const src = $el.attr('src');
if (href) {
$el.attr('href', new URL(href, baseUrl).href);
}
if (src) {
$el.attr('src', new URL(src, baseUrl).href);
}
});
};
// 提取所有链接
cheerio.prototype.extractLinks = function() {
const links = [];
this.find('a[href]').each((i, el) => {
const $el = cheerio(el);
links.push({
text: $el.text().trim(),
href: $el.attr('href'),
title: $el.attr('title')
});
});
return links;
};
};
```
### 4. 图片处理插件
```javascript
// image-handler.js
module.exports = function(cheerio) {
// 提取所有图片信息
cheerio.prototype.extractImages = function() {
const images = [];
this.find('img').each((i, el) => {
const $el = cheerio(el);
images.push({
src: $el.attr('src'),
alt: $el.attr('alt'),
title: $el.attr('title'),
width: $el.attr('width'),
height: $el.attr('height')
});
});
return images;
};
// 懒加载图片处理
cheerio.prototype.handleLazyImages = function() {
return this.each((i, el) => {
const $el = cheerio(el);
const dataSrc = $el.attr('data-src');
if (dataSrc) {
$el.attr('src', dataSrc);
}
});
};
};
```
## 4. 高级插件开发
### 带配置的插件
```javascript
// configurable-plugin.js
module.exports = function(cheerio, options = {}) {
const defaultOptions = {
trim: true,
removeEmpty: true,
maxLength: 1000
};
const opts = { ...defaultOptions, ...options };
cheerio.prototype.smartExtract = function() {
return this.map((i, el) => {
let text = cheerio(el).text();
if (opts.trim) {
text = text.trim();
}
if (opts.removeEmpty && text.length === 0) {
return null;
}
if (opts.maxLength && text.length > opts.maxLength) {
text = text.substring(0, opts.maxLength) + '...';
}
return text;
}).filter(text => text !== null);
};
};
// 使用
cheerio.use(configurablePlugin, {
trim: true,
removeEmpty: true,
maxLength: 500
});
```
### 异步插件
```javascript
// async-plugin.js
module.exports = function(cheerio) {
cheerio.prototype.fetchContent = async function(url) {
const axios = require('axios');
const response = await axios.get(url);
return cheerio.load(response.data);
};
cheerio.prototype.batchProcess = async function(processor) {
const results = [];
for (let i = 0; i < this.length; i++) {
const result = await processor(cheerio(this[i]));
results.push(result);
}
return results;
};
};
```
## 5. 插件发布
### package.json 配置
```json
{
"name": "cheerio-my-plugin",
"version": "1.0.0",
"description": "My Cheerio plugin",
"main": "index.js",
"keywords": [
"cheerio",
"plugin",
"html",
"parser"
],
"peerDependencies": {
"cheerio": ">=1.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/username/cheerio-my-plugin"
},
"license": "MIT"
}
```
### 插件测试
```javascript
// test.js
const cheerio = require('cheerio');
const myPlugin = require('./my-plugin');
describe('My Plugin', () => {
beforeEach(() => {
cheerio.use(myPlugin);
});
test('should filter empty elements', () => {
const $ = cheerio.load('<div><p>Hello</p><p></p></div>');
const result = $('p').filterEmpty();
expect(result.length).toBe(1);
expect(result.text()).toBe('Hello');
});
test('should get all text', () => {
const $ = cheerio.load('<div><p>Hello</p><p>World</p></div>');
const result = $('p').getAllText();
expect(result).toEqual(['Hello', 'World']);
});
});
```
## 6. 现有流行插件
### cheerio-tableparser
```javascript
const cheerio = require('cheerio');
const tableParser = require('cheerio-tableparser');
cheerio.use(tableParser);
const $ = cheerio.load(html);
const tableData = $('table').parsetable();
```
### cheerio-select
```javascript
const cheerio = require('cheerio');
const select = require('cheerio-select');
cheerio.use(select);
const $ = cheerio.load(html);
const elements = $.select('div > p:first-child');
```
## 7. 最佳实践
### 1. 命名规范
```javascript
// ✅ 好的命名
cheerio.prototype.extractLinks = function() {}
cheerio.prototype.cleanText = function() {}
// ❌ 不好的命名
cheerio.prototype.doSomething = function() {}
cheerio.prototype.method1 = function() {}
```
### 2. 返回值处理
```javascript
// ✅ 支持链式调用
cheerio.prototype.myMethod = function() {
// 处理逻辑
return this;
};
// ✅ 返回新集合
cheerio.prototype.myFilter = function() {
const filtered = this.filter(/* 条件 */);
return filtered;
};
```
### 3. 错误处理
```javascript
cheerio.prototype.safeExtract = function() {
try {
// 提取逻辑
return this.map((i, el) => {
return cheerio(el).text();
}).get();
} catch (error) {
console.error('Extraction failed:', error);
return [];
}
};
```
### 4. 文档和注释
```javascript
/**
* 提取所有链接信息
* @param {Object} options - 配置选项
* @param {boolean} options.resolveAbsolute - 是否转换为绝对 URL
* @param {string} options.baseUrl - 基础 URL
* @returns {Array} 链接信息数组
*/
cheerio.prototype.extractLinks = function(options = {}) {
// 实现
};
```
通过开发和使用 Cheerio 插件,可以大大扩展 Cheerio 的功能,提高开发效率。
服务端 · 2月22日 14:30
Cheerio 如何处理动态加载的内容?有哪些解决方案?Cheerio 本身不支持处理动态加载的内容,因为它只是一个 HTML 解析器,不会执行 JavaScript。但是,我们可以通过多种方式结合其他工具来处理动态内容:
## 1. 使用 Puppeteer + Cheerio 组合
这是最常用的方案,先用 Puppeteer 加载动态页面,然后用 Cheerio 解析:
```javascript
const puppeteer = require('puppeteer');
const cheerio = require('cheerio');
async function scrapeDynamicContent(url) {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// 访问页面并等待动态内容加载
await page.goto(url, { waitUntil: 'networkidle2' });
// 等待特定元素出现
await page.waitForSelector('.dynamic-content', { timeout: 10000 });
// 获取完整的 HTML
const html = await page.content();
// 关闭浏览器
await browser.close();
// 使用 Cheerio 解析
const $ = cheerio.load(html);
// 提取数据
const data = [];
$('.dynamic-item').each((index, element) => {
data.push({
title: $(element).find('.title').text().trim(),
content: $(element).find('.content').text().trim(),
link: $(element).find('a').attr('href')
});
});
return data;
}
```
## 2. 处理无限滚动页面
```javascript
async function scrapeInfiniteScroll(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);
let previousHeight = 0;
let currentHeight = await page.evaluate('document.body.scrollHeight');
// 滚动直到没有新内容加载
while (currentHeight > previousHeight) {
previousHeight = currentHeight;
// 滚动到底部
await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
// 等待新内容加载
await page.waitForTimeout(2000);
currentHeight = await page.evaluate('document.body.scrollHeight');
}
// 获取 HTML 并用 Cheerio 解析
const html = await page.content();
const $ = cheerio.load(html);
const items = $('.item').map((i, el) => ({
title: $(el).find('.title').text(),
link: $(el).find('a').attr('href')
})).get();
await browser.close();
return items;
}
```
## 3. 处理懒加载图片
```javascript
async function scrapeLazyImages(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);
// 滚动页面触发懒加载
const scrollHeight = await page.evaluate('document.body.scrollHeight');
for (let i = 0; i < scrollHeight; i += 500) {
await page.evaluate(`window.scrollTo(0, ${i})`);
await page.waitForTimeout(500);
}
// 等待所有图片加载完成
await page.evaluate(async () => {
const images = Array.from(document.querySelectorAll('img[data-src]'));
await Promise.all(images.map(img => {
if (img.dataset.src) {
img.src = img.dataset.src;
}
}));
});
await page.waitForTimeout(2000);
const html = await page.content();
const $ = cheerio.load(html);
const images = $('img').map((i, el) => ({
src: $(el).attr('src'),
alt: $(el).attr('alt')
})).get();
await browser.close();
return images;
}
```
## 4. 处理点击加载更多
```javascript
async function scrapeClickToLoad(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);
let hasMore = true;
const allItems = [];
while (hasMore) {
// 等待内容加载
await page.waitForSelector('.item');
// 获取当前页面的 HTML
const html = await page.content();
const $ = cheerio.load(html);
// 提取当前页面的数据
$('.item').each((index, element) => {
const title = $(element).find('.title').text();
// 避免重复添加
if (!allItems.some(item => item.title === title)) {
allItems.push({ title });
}
});
// 检查是否有"加载更多"按钮
const loadMoreButton = await page.$('.load-more');
if (loadMoreButton) {
// 点击加载更多
await loadMoreButton.click();
// 等待新内容加载
await page.waitForTimeout(2000);
} else {
hasMore = false;
}
}
await browser.close();
return allItems;
}
```
## 5. 处理 AJAX 请求
```javascript
async function scrapeAJAXContent(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 监听网络请求
const apiData = [];
page.on('response', async response => {
const url = response.url();
if (url.includes('/api/data')) {
const data = await response.json();
apiData.push(...data);
}
});
await page.goto(url);
// 等待 AJAX 请求完成
await page.waitForSelector('.data-loaded', { timeout: 15000 });
await browser.close();
return apiData;
}
```
## 6. 使用 Playwright 替代方案
Playwright 是另一个强大的浏览器自动化工具:
```javascript
const { chromium } = require('playwright');
const cheerio = require('cheerio');
async function scrapeWithPlaywright(url) {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle' });
// 等待动态内容
await page.waitForSelector('.dynamic-content');
const html = await page.content();
await browser.close();
const $ = cheerio.load(html);
return $('.item').map((i, el) => $(el).text()).get();
}
```
## 7. 优化性能的技巧
```javascript
// 1. 禁用不必要的资源加载
async function optimizedScrape(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 阻止图片、字体等资源加载
await page.setRequestInterception(true);
page.on('request', request => {
const resourceType = request.resourceType();
if (['image', 'font', 'stylesheet'].includes(resourceType)) {
request.abort();
} else {
request.continue();
}
});
await page.goto(url);
const html = await page.content();
await browser.close();
return cheerio.load(html);
}
// 2. 复用浏览器实例
class Scraper {
constructor() {
this.browser = null;
}
async init() {
this.browser = await puppeteer.launch();
}
async scrape(url) {
const page = await this.browser.newPage();
await page.goto(url);
const html = await page.content();
await page.close();
return cheerio.load(html);
}
async close() {
await this.browser.close();
}
}
// 使用示例
async function main() {
const scraper = new Scraper();
await scraper.init();
const urls = ['url1', 'url2', 'url3'];
const results = [];
for (const url of urls) {
const $ = await scraper.scrape(url);
results.push($('.title').text());
}
await scraper.close();
return results;
}
```
## 8. 错误处理和重试
```javascript
async function scrapeWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url, { timeout: 30000 });
await page.waitForSelector('.content', { timeout: 10000 });
const html = await page.content();
await browser.close();
return cheerio.load(html);
} catch (error) {
console.log(`尝试 ${i + 1} 失败:`, error.message);
if (i === maxRetries - 1) {
throw error;
}
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
}
```
## 总结
虽然 Cheerio 本身无法处理动态内容,但通过与 Puppeteer、Playwright 等浏览器自动化工具结合,可以有效地处理各种动态加载场景。关键在于:
1. **等待策略**:使用 `waitForSelector`、`waitForTimeout` 等确保内容加载完成
2. **性能优化**:禁用不必要的资源加载,复用浏览器实例
3. **错误处理**:实现重试机制,处理网络异常
4. **混合使用**:先用浏览器工具加载动态内容,再用 Cheerio 快速解析
服务端 · 2月22日 14:30
什么是 Cheerio?它和 jQuery 有什么区别?Cheerio 是一个基于 Node.js 的快速、灵活且功能强大的 HTML 解析器,它实现了 jQuery 的核心 API,但专门用于服务器端环境。与浏览器中的 jQuery 不同,Cheerio 不渲染 DOM,不处理 CSS 样式,也不执行 JavaScript,这使得它非常轻量且高效。
Cheerio 的核心特点包括:
1. **轻量级**:核心代码只有几百行,性能优异
2. **jQuery 语法**:使用熟悉的 jQuery 选择器和操作方法
3. **服务器端使用**:在 Node.js 环境中运行,不依赖浏览器
4. **快速解析**:使用 htmlparser2 作为底层解析器,解析速度快
5. **灵活性**:支持多种 HTML/XML 解析选项
Cheerio 的典型使用场景包括网页爬虫、数据抓取、HTML 内容处理等。它可以从 HTML 字符串或文件中提取数据,修改 DOM 结构,生成新的 HTML 内容等。
基本用法示例:
```javascript
const cheerio = require('cheerio');
const $ = cheerio.load('<div class="container"><p>Hello World</p></div>');
// 使用 jQuery 选择器
$('p').text(); // 'Hello World'
$('.container').html(); // '<p>Hello World</p>'
// 修改 DOM
$('p').addClass('highlight').text('Updated Text');
```
Cheerio 适合处理静态 HTML 内容,如果需要处理动态渲染的页面(需要执行 JavaScript),则需要结合 Puppeteer 或 Playwright 等工具使用。
服务端 · 2月22日 14:30