Recommended Free Tools
Use a CSS class selector with Cheerio: load the HTML with cheerio.load(), then call the returned $ function with a selector such as $('.intro'). That returns every element carrying the intro class, regardless of its tag name. Add a tag, another class, or a parent scope when the class-only result is too broad.
Load the markup before selecting anything
Cheerio queries a parsed HTML tree. The normal sequence is:
- Import Cheerio.
- Pass an HTML string to
cheerio.load(). - Use the returned
$function with a CSS selector.
Install it in a Node.js project with:
npm install cheerio
This complete example selects every element with the intro class:
import * as cheerio from 'cheerio';
const $ = cheerio.load(`
<article>
<p class="intro">Welcome</p>
<p class="intro featured">Read this</p>
</article>
`);
const intros = $('.intro');
console.log(intros.length); // 2
console.log(intros.first().text()); // Welcome
intros is a Cheerio selection: a wrapper around all matched elements. You can count it, read text or attributes, select one item, iterate over it, and traverse to related elements.
#1 Best Overall
Select a class with the dot syntax
A class selector starts with a period and contains the class token exactly as it appears in the class attribute:
$('.intro')
This matches both paragraphs in the example, including the second paragraph because it has two classes. The selector does not require a particular HTML tag.
Do not put a space between a tag and its class. p.intro means “a paragraph with the intro class”; p .intro means “an element with intro somewhere inside a paragraph,” which is a different relationship.
Choose the right selector when a class is too broad
Cheerio uses CSS-selector syntax for ordinary selection. These patterns cover the most useful ways to find classed elements:
| Selector | What it matches | When to use it |
|---|---|---|
.intro |
Every element carrying intro |
Use when the class itself uniquely identifies the content. |
p.intro |
Only paragraphs carrying intro |
Use when other tags may reuse the class. |
.intro.featured |
Elements carrying both classes | Use when one class is not specific enough. |
h1, h2 |
All h1 and h2 elements |
Use a comma to combine alternatives. |
article .intro |
intro descendants at any depth inside an article |
Use for a broad ancestor-to-descendant scope. |
article > .intro |
intro elements that are direct children of an article |
Use when nested descendants must be excluded. |
Match more than one class
Adjacent class selectors are an AND condition:
const featuredIntros = $('.intro.featured');
console.log(featuredIntros.length); // 1
An element must have both class tokens. A selector such as .intro, .featured is different: it is an OR condition and returns elements having either class.
Scope a selector to a container
Use .find() when you already selected a container and want matching descendants only:
const article = $('article').first();
const subtitles = article.find('.subtitle');
subtitles.each((index, element) => {
console.log(index, $(element).text().trim());
});
.find('.subtitle') searches within the current article selection; it does not restart at the whole document. This is useful when several cards contain elements with the same class.
Filter or exclude an existing selection
When you have already selected a set, .filter() narrows it and .not() removes matches:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsconst paragraphs = $('p');
const intros = paragraphs.filter('.intro');
const nonIntros = paragraphs.not('.intro');
Read text, attributes, and every match
Selection and extraction are separate operations. Use .length to verify how many elements matched, .text() for combined text, .attr() for an attribute, and .first() when only the first match matters.
const links = $('.intro a');
console.log('links:', links.length);
console.log('first link text:', links.first().text().trim());
console.log('first link URL:', links.first().attr('href'));
For records from every match, iterate with .each():
const cards = $('.card');
const results = [];
cards.each((index, element) => {
const card = $(element);
results.push({
position: index,
title: card.find('.card-title').text().trim(),
href: card.find('a').attr('href') || null
});
});
console.log(results);
Calling .text() on a collection returns the text from all of its matched elements. Iterate when you need one object per element or need to combine several fields from each container.
A complete class-based extraction script
The following runnable script parses a string, selects product cards, scopes each lookup to its current card, and handles a missing link without throwing an error:
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #3
import * as cheerio from 'cheerio';
const html = `
<main>
<article class="product-card featured">
<h2 class="product-title">Alpha</h2>
<a class="product-link" href="/alpha">Details</a>
</article>
<article class="product-card">
<h2 class="product-title">Beta</h2>
<a class="product-link" href="/beta">Details</a>
</article>
</main>
`;
const $ = cheerio.load(html);
const products = [];
$('.product-card').each((index, element) => {
const card = $(element);
products.push({
index,
title: card.find('.product-title').text().trim(),
url: card.find('.product-link').attr('href') || null,
featured: card.is('.featured')
});
});
console.log(products);
The important detail is the scope: card.find() prevents a title or link from another card being paired with the current one. A class-only global query is simpler, but it can associate unrelated elements when a page repeats the same class in multiple components.
Cheerio selectors are not browser rendering
Cheerio works on the tree it parses; it does not apply CSS. An element hidden by a stylesheet can still be present and selectable. Conversely, content that a browser adds or changes after loading will not be available unless that final markup is supplied to Cheerio.
If a class query returns zero elements, first inspect the exact HTML string given to cheerio.load(). A class visible in a browser may come from a later client-side operation rather than the original response. Cheerio itself is the right tool for already-available markup, not a replacement for a browser renderer.
Use stable anchors for maintainable scrapers
Presentation classes are often renamed during a redesign. When the page offers a stable data attribute or structural anchor, prefer it or combine it with the class:
$('.product-card[data-product-id]')
$('main .product-card > .product-title')
Text matching with :contains() can be useful when the text is a deliberate identifier, but text is also vulnerable to editorial changes. Keep selectors as narrow as the page structure requires without depending on fragile, generated class names.
Cheerio-specific selector boundaries
Cheerio supports most standard pseudo-classes and also documents extensions such as :contains() and positional selectors including :first, :last, and :eq(n). Those positional extensions are Cheerio features, not valid CSS selectors for a browser.
const firstIntro = $('.intro:first');
const thirdIntro = $('.intro:eq(2)');
If a selector raises an “Unknown pseudo-class” error, the pseudo-class is unsupported in the selector implementation you are using. That differs from a valid selector that simply matches nothing. Test a simpler selector, then add conditions one at a time to identify the unsupported part.
Troubleshoot an empty or incorrect selection
The result has length zero
- Confirm the class spelling, capitalization, hyphens, and underscores.
- Check that the HTML passed to
cheerio.load()actually contains the element. - Remove extra conditions temporarily: test
$('.class-name')before testing a long descendant selector. - Check whether the content is generated after the original markup was loaded.
The selector returns too many elements
- Add the element name, such as
p.intro. - Require a second class with
.intro.featured. - Scope the query with an ancestor or
.find(). - Use a direct-child combinator (
>) when nested descendants should not count.
.find() misses an element
Verify that the element is actually inside the current selection. .find() only searches descendants; it does not include the selected container itself and does not search elsewhere in the document. Select the container broadly enough before calling it.
Text or attributes look wrong
- Call
.trim()on text when indentation and line breaks are not meaningful. - Use
.attr('name')for one attribute and handle an undefined result when the attribute is absent. - Iterate per container instead of reading one large collection when values must stay associated with their element.
A browser inspector shows a class that Cheerio cannot find
The inspector may show a post-load DOM, while Cheerio sees only the markup you supplied. Save or obtain the relevant final HTML, then parse that string. Do not expect CSS visibility or browser layout to change the parsed tree.
Performance and reliability considerations
For ordinary documents, keep one loaded Cheerio instance and reuse it for related selectors rather than reparsing the same string for every field. Scope repeated lookups to each component with .find(); this makes the association explicit and avoids scanning unrelated branches. Check .length at boundaries where a missing element would invalidate a record, and treat optional attributes as nullable rather than assuming they exist.
Selector behavior can depend on the Cheerio version installed in your project. If a pseudo-class behaves differently from this article, check the selector support for that installed version and reduce the query to a standard CSS form.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your goal is a visual snapshot of a page containing the class you are inspecting—not extraction of its HTML—ScreenshotNeo can capture the page through one request. It is not a Cheerio replacement and does not return a list of matching nodes; it returns a PNG, JPEG, WebP, or PDF. Its cleanup steps accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture, with each step switchable.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
Here is a one-call cURL example (see the ScreenshotNeo API documentation for all options):
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
The same request in Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
And in Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));
Every plan includes every feature. The Free plan provides 1,000 shots per month with no card; paid plans are Starter ($5 for 3,000), Growth ($15 for 15,000), Pro ($39 for 60,000), Scale ($99 for 250,000), and Business ($249 for 1,000,000). Yearly billing gives two months free.
If you need screenshots rather than Cheerio extraction, create a free ScreenshotNeo account and start with the 1,000 monthly shots at no card required.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Frequently Asked Questions
Can one element match several Cheerio class selectors?
Yes. Adjacent classes such as .intro.featured require the same element to contain both class tokens.
Why does a valid selector sometimes return no elements?
A valid selector can still match nothing when the supplied HTML lacks that element, the class differs, or the content was added after the markup was parsed.
Should I use a class or a data attribute as my scraper anchor?
Use the most stable identifier available. A dedicated data attribute or durable structure is usually less likely to change than a class used primarily for presentation.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

