cheerio — Cursor rule: HTML parsing and install
A Cursor rule for cheerio in Node.js, covering scraper structure, selector choices, data cleanup, scaling, and parser tests.
.cursor/rules/cheerio.mdcWhat cheerio does
The rule targets **/*.{js,jsx} and sets a clear shape for Node.js scrapers. It keeps HTTP requests, HTML parsing, and data storage in separate parts, uses async/await for asynchronous work, and favors scoped selectors. Extracted text should be trimmed, attributes read with .attr(), and missing elements checked before values are used.
It also addresses memory use when cheerio.load() handles large HTML documents. For queued requests, retries, concurrency, and rate limits, it points to Crawlee and CheerioCrawler. Parser behavior belongs in Jest tests with mocked HTML. cheerio does not run JavaScript or render CSS, so client-rendered content requires Puppeteer or Playwright. Scrapers must respect robots.txt.
What it helps with
- separate fetching, parsing, and data storage
- scope selectors to the relevant HTML section
- trim extracted text before saving it
- handle missing nodes and attributes safely
- test selectors against mocked HTML
Example prompts for the agent
- Set up a
cheerioproduct scraper with scoped selectors, trimmed text, and output saved toproducts.json. - Refactor this scraper so HTTP fetching, HTML parsing, and persistence are separate functions using
async/await. - Create Jest tests in
parser.test.jsfor present and missing product fields in mocked HTML.
How to install cheerio
Manually: put the files in place
.cursor/rules/cheerio.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/cheerio.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/cheerio.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/cheerio.mdManually: put the files in place
.clinerules/cheerio.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about cheerio
How do I install cheerio in Cursor?
Copy the rule file to .cursor/rules/cheerio.mdc. Cursor will apply it to files matching **/*.{js,jsx}.
What does the cheerio rule do?
It guides Node.js HTML parsing with modular scraper code, focused selectors, trimmed values, missing-element checks, and Jest coverage for parser logic.
Can cheerio handle content rendered by JavaScript?
No. cheerio parses the raw HTML string without running JavaScript or rendering CSS. Use Puppeteer or Playwright for client-rendered content.
Source text
cheerio.mdc · CC0-1.0
---
description: Definitive guidelines for using cheerio effectively in Node.js projects, focusing on robust web scraping, performance, and maintainable code.
globs: **/*.{js,jsx}
---
# cheerio Best Practices
`cheerio` is your lightweight, server-side jQuery for Node.js, ideal for fast HTML parsing and manipulation. Follow these rules to build efficient, maintainable, and robust scraping solutions.
## 1. Code Organization & Structure
**Always modularize your scraping logic.** Separate HTTP requests, HTML parsing, and data persistence. Use `async/await` for all asynchronous operations.
### ✅ GOOD: Modular & Asynchronous Structure
```javascript
// index.js
import * as cheerio from 'cheerio';
import axios from 'axios';
import { writeFile } from 'fs/promises';
async function fetchAndParse(url) {
try {
const { data: html } = await axios.get(url, {
headers: { 'User-Agent': 'YourScraper/1.0' }
});
const $ = cheerio.load(html);
const products = [];
$('.product-item').each((i, el) => {
const $el = $(el);
const name = $el.find('.product-name').text().trim();
const price = $el.find('.product-price').text().trim();
const imageUrl = $el.find('img').attr('src');
// Always check for existence before pushing
if (name) {
products.push({ name, price, imageUrl });
}
});
return products;
} catch (error) {
console.error(`Error scraping ${url}:`, error.message);
throw error;
}
}
async function main() {
const targetUrl = 'https://www.example.com/products';
const scrapedData = await fetchAndParse(targetUrl);
await writeFile('products.json', JSON.stringify(scrapedData, null, 2), 'utf8');
console.log('Scraping complete. Data saved to products.json');
}
main();
```
## 2. Common Patterns & Anti-patterns
### 2.1. Selector Efficiency
**Keep selectors specific and scoped.** Avoid broad selectors that force `cheerio` to traverse large portions of the DOM. Chain `.find()` for nested elements.
```javascript
// ❌ BAD: Overly broad, inefficient
const allLinks = $('a').map((i, el) => $(el).attr('href')).get();
// ✅ GOOD: Scoped to relevant section
const productLinks = $('#product-grid a.product-link').map((i, el) => $(el).attr('href')).get();
// ✅ GOOD: Chain .find() for precision
const productName = $('.product-card').first().find('h2.title').text().trim();
```
### 2.2. Data Extraction & Cleaning
**Use `.text()` for visible text, `.attr()` for attributes, `.html()` for inner HTML.** Always `.trim()` extracted text to remove whitespace.
```javascript
// ❌ BAD: Untrimmed text, can contain leading/trailing whitespace or newlines
const title = $('h1').text(); // " My Product Title \n"
// ✅ GOOD: Clean text extraction
const title = $('h1').text().trim(); // "My Product Title"
// ❌ BAD: Assuming element exists, `undefined.text()` will crash
const price = $('.product-price').text().trim();
// ✅ GOOD: Check element existence before extracting
const $priceEl = $('.product-price');
const price = $priceEl.length ? $priceEl.text().trim() : 'N/A';
```
## 3. Performance Considerations
### 3.1. Large HTML Documents (>10MB)
`cheerio.load()` creates an in-memory DOM. For very large documents, this can consume significant memory.
**Strategies for large documents:**
1. **Selective Loading:** If possible, extract and parse only relevant HTML snippets.
2. **Clear References:** Set `$` to `null` after processing to aid garbage collection.
```javascript
// ✅ GOOD: Aid garbage collection
let $ = cheerio.load(htmlContent);
// ... perform operations ...
$ = null; // Clear reference
if (global.gc) global.gc(); // Force GC (requires --expose-gc)
```
### 3.2. Concurrency & Rate Limiting
For scalable, production scraping, **use `Crawlee`'s `CheerioCrawler`**. It handles request queuing, concurrency, retries, and rate limiting automatically.
```javascript
// ✅ GOOD: Scalable scraping with Crawlee
import { CheerioCrawler, Dataset } from 'crawlee';
const crawler = new CheerioCrawler({
maxRequestsPerMinute: 60, // Respect rate limits
async requestHandler({ request, $, log }) {
log.info(`Processing ${request.url}`);
const title = $('h1').text().trim();
await Dataset.pushData({ url: request.url, title });
},
});
await crawler.run(['https://crawlee.dev']);
```
## 4. Common Pitfalls & Gotchas
### 4.1. Cheerio is NOT a Browser
**`cheerio` does not execute JavaScript, render CSS, or load external resources.** It only parses the raw HTML string.
**Solution:** For dynamic content rendered by client-side JavaScript, use browser automation tools like `Puppeteer` or `Playwright`.
### 4.2. Respect `robots.txt`
**Always check and respect a website's `robots.txt` file.** Ethical scraping is non-negotiable.
## 5. Testing Approaches
**Unit test your parsing logic with mocked HTML.** This ensures your selectors and extraction methods are robust and resilient to minor HTML changes.
```javascript
// parser.test.js (using Jest)
import * as cheerio from 'cheerio';
describe('Product Parser', () => {
const mockHtml = `
<div class="product-item">
<img src="/img/prod1.jpg">
<h2 class="product-name">Product A</h2>
<span class="product-price">$19.99</span>
</div>`;
it('should extract product details correctly', () => {
const $ = cheerio.load(mockHtml);
const $product = $('.product-item');
expect($product.find('.product-name').text().trim()).toBe('Product A');
expect($product.find('.product-price').text().trim()).toBe('$19.99');
expect($product.find('img').attr('src')).toBe('/img/prod1.jpg');
});
it('should handle missing elements gracefully', () => {
const $ = cheerio.load('<div class="product-item"></div>');
const $product = $('.product-item');
expect($product.find('.non-existent').text().trim()).toBe(''); // Cheerio returns empty string
expect($product.find('img').attr('src')).toBeUndefined(); // Cheerio returns undefined for missing attr
});
});
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc