Free tools Windows power users keep installed
One-click scans. No signup required.
For ordinary sibling elements, select the range with $(startSelector).nextUntil(endSelector). Cheerio includes the siblings after the start node, stops before the element matching the end selector, and excludes that end element. Map the resulting selection for separate values, or call .text() when one concatenated string is enough.
import * as cheerio from 'cheerio';
const $ = cheerio.load(`
<section>
<h2 class="start">Values</h2>
<p>First</p>
<p>Second</p>
<h2 class="end">Next section</h2>
</section>
`);
const values = $('.start').nextUntil('.end');
console.log(values.map((_, element) => $(element).text()).get());
// [ 'First', 'Second' ]
This works when both boundary elements are siblings under the same parent. If the values are text nodes, the boundaries are nested in different parents, or the content is added by browser JavaScript, use a different approach described below.
Install Cheerio and load the markup
Install the package in your Node.js project:
npm install cheerio
Cheerio’s introduction documents both ECMAScript modules and CommonJS. The current introduction says the package runs on Node.js 22.19 or later; check the requirements of the exact Cheerio release you install before pinning a runtime in production. With ESM, use:
import * as cheerio from 'cheerio';
const $ = cheerio.load(html);
In a CommonJS project, the equivalent is:
const cheerio = require('cheerio');
const $ = cheerio.load(html);
Cheerio’s introduction covers loading documents and the distinction between parsing HTML and browser rendering.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#1 Best Overall
Select the bounded range with nextUntil
nextUntil(stopSelector) walks forward through following siblings. It returns every sibling before the first match for stopSelector; the stop element itself is not included.
const html = `
<article>
<h2 class="start">Products</h2>
<div class="product">One</div>
<aside>Related links</aside>
<div class="product">Two</div>
<h2 class="end">Reviews</h2>
<p>Do not select this paragraph.</p>
</article>
`;
const $ = cheerio.load(html);
const between = $('.start').nextUntil('.end');
console.log(between.length); // 3
console.log(between.map((_, el) => $(el).text().trim()).get());
// [ 'One', 'Related links', 'Two' ]
The selection deliberately contains different element types. If you only want products, filter the bounded result rather than replacing the range with a general-sibling selector:
const products = $('.start')
.nextUntil('.end')
.filter('.product')
.map((_, el) => $(el).text().trim())
.get();
console.log(products); // [ 'One', 'Two' ]
Cheerio traversal creates a new selection, so the original $('.start') selection remains usable. See the traversing guide and API reference for the method signatures.
Choose the selector that matches your relationship
| Need | Use | What it returns |
|---|---|---|
| Only the immediately following matching sibling | $('.start + p') |
The next sibling only, if it is a p |
| All later siblings of one type | $('.start ~ p') |
Every later p sibling; there is no end boundary |
| Every sibling between two boundaries | $('.start').nextUntil('.end') |
All following siblings before .end, excluding the endpoint |
| Reverse range | $('.end').prevUntil('.start') |
Previous siblings back to, but not including, the start match |
The CSS adjacent-sibling (+) and general-sibling (~) combinators select by relationship and element type. They do not express “walk until this second boundary.” Use nextUntil for a bounded range even when the range contains headings, paragraphs, lists, or other mixed elements.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Extract text, attributes, and individual values
Keep one result per element
Use map followed by get() when the caller needs an array. Calling text() on the whole selection concatenates descendant text and loses the element boundaries.
const values = $('.start')
.nextUntil('.end')
.map((_, element) => $(element).text().trim())
.get();
Get one concatenated string
const combined = $('.start').nextUntil('.end').text().trim();
Use this for a paragraph-like result, not when downstream code must distinguish separate nodes.
Read an attribute
const links = $('.start')
.nextUntil('.end')
.filter('a')
.map((_, element) => ({
label: $(element).text().trim(),
href: $(element).attr('href')
}))
.get();
For property-backed values, Cheerio also supports APIs such as prop; its extraction documentation demonstrates properties including innerText. innerText is computed from the parsed tree, not from a browser layout engine.
Rank #2
Reverse traversal with prevUntil
When the stop or current node is known and the range lies before it, start at the end and walk backward:
const reverse = $('.end')
.prevUntil('.start')
.map((_, element) => $(element).text().trim())
.get();
Confirm ordering in your own output when order matters. Traversal APIs define the returned selection, but a reverse walk may not match the document order you want for presentation. If necessary, reverse the array explicitly:
const documentOrder = reverse.reverse();
As with nextUntil, the boundary matched by .start is excluded.
When the boundaries are not siblings
Sibling traversal only describes nodes sharing the same parent. This markup has two headings in separate sections, so no nextUntil call from one heading can cross the parent boundary:
<section><h2 class="start">Start</h2></section>
<section><p>Value</p></section>
<section><h2 class="end">End</h2></section>
First inspect the parsed tree and identify a common container. If the logical range is represented by document order rather than siblings, select that container’s children and maintain a state flag while iterating, or redesign the source markup so both markers are siblings. Do not assume visual proximity means DOM sibling proximity.
Text nodes and whitespace
nextUntil is intended for element siblings. If the meaningful values are raw text nodes between markers, parse and inspect the parent’s child nodes, or wrap the values in elements before extraction. Whitespace and parser-repaired markup can change what the tree contains.
Parser choices affect what “between” means
Cheerio uses parse5 by default for HTML and htmlparser2 by default for XML. Malformed HTML can be repaired into a different tree, and XML has different parsing requirements. Because sibling traversal follows that tree, inspect representative input and choose the parser deliberately. The configuration guide explains parser options.
Rank #3
import * as cheerio from 'cheerio';
// HTML (default behavior)
const $html = cheerio.load(markup);
// XML mode, when the input is XML rather than HTML
const $xml = cheerio.load(markup, { xml: true });
Do not use XML mode merely to hide malformed HTML; fix or normalize the source when possible, then test the exact boundary selectors against the resulting tree.
Cheerio does not render or run page JavaScript
Cheerio parses the markup you provide. It does not execute scripts, apply CSS layout, load external resources, or wait for client-side frameworks. If a browser inserts the start, end, or values after page load, those nodes will not exist in the Cheerio document unless you obtain the rendered HTML separately. Use browser automation such as Puppeteer or Playwright for pages whose content requires JavaScript, then pass the resulting HTML to Cheerio for extraction.
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 →This distinction also affects innerText: Cheerio computes it from its parsed tree; it cannot know what a browser hid, expanded, or inserted through layout and scripts.
Build a robust extraction function
For repeatable jobs, validate that the boundaries exist, detect a missing endpoint, and normalize output in one place:
import * as cheerio from 'cheerio';
export function valuesBetween(markup, startSelector, endSelector) {
const $ = cheerio.load(markup);
const start = $(startSelector).first();
if (start.length === 0) {
throw new Error(`Start node not found: ${startSelector}`);
}
const range = start.nextUntil(endSelector);
const end = range.last().nextAll(endSelector).first();
if (end.length === 0) {
throw new Error(`End node not found after start: ${endSelector}`);
}
return range.map((_, element) => ({
tag: element.tagName,
text: $(element).text().trim()
})).get();
}
The endpoint check above is useful when a missing end marker should fail the job rather than silently returning “everything to the end.” If an end marker is optional by design, omit that check and document the fallback.
Multiple start or end matches
A selector can match multiple starts or stops. Decide whether the document format permits that. Use .first() or .eq(index) when the position is defined; otherwise iterate over each start and pair it with the correct endpoint. Avoid accidentally combining ranges from unrelated sections.
Security, limits, and reliability
- Do not interpolate untrusted selector text. A user-supplied value inserted directly into a CSS selector can alter what is selected or trigger selector parsing problems. Prefer a fixed selector and compare untrusted values as data, following Cheerio’s security guidance.
- Limit input size. Parsing consumes memory and CPU in proportion to markup size. Put an explicit byte or character limit on uploaded or fetched HTML, and reject unexpected documents before parsing.
- Check empty selections.
.text()on an empty selection returns an empty string, which can look like valid data. Test.lengthand report a missing boundary or value. - Normalize deliberately. Use
trim()only when surrounding whitespace is not meaningful. Preserve raw text when whitespace carries semantics. - Test malformed fixtures. Include missing end markers, nested sections, duplicate headings, comments, and unexpected element types in automated tests.
For very large documents, narrow the container before traversing and avoid repeatedly reparsing the same markup. Cheerio selections are in-memory; they are not streaming iterators.
Rank #4
Troubleshooting common failures
“I get an empty selection”
Verify the start selector, confirm that it matches the loaded document, and inspect the parent’s children. The endpoint must occur after the start as a sibling. A typo, different parent, or client-side-only node will produce no range.
The end node is included
nextUntil excludes its stop selector. If your output contains the endpoint, you probably selected it separately or used a broader selector such as nextAll(). Filter the bounded result and keep endpoint handling explicit.
Only some expected elements appear
Check whether the omitted content is nested inside a sibling rather than being a direct sibling. nextUntil returns the sibling element; it does not flatten descendants. Traverse or extract descendants from that element as a second step.
Recommended Free Tools
The result includes unrelated elements
The range includes every sibling, regardless of tag. Apply .filter('.desired-class') or test each element during mapping.
The page source contains no values
Cheerio cannot execute the JavaScript that creates them. Capture rendered HTML with browser automation, or use a service that performs the browser capture first, then parse the returned markup.
Selectors behave differently on broken markup
Compare HTML and XML parser modes, inspect the repaired tree, and fix the source if possible. Parser configuration changes parent and sibling relationships.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If the only missing step is obtaining a rendered page before Cheerio extraction, ScreenshotNeo can return a screenshot or PDF through one HTTP request. It is not a replacement for Cheerio’s DOM parser, but it can remove the browser-capture setup when you need a visual artifact or a rendered-page check. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallSee the ScreenshotNeo documentation for all options, including full-page capture, lazy-image loading, CSS-selector element capture, device and viewport settings, retina scale, PDF page controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage data, and the OpenAPI specification.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', buffer));
ScreenshotNeo’s Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to get an API key.
Frequently asked questions
Does nextUntil include the start element?
No. It starts with following siblings, so neither the start nor the end boundary is part of the returned selection.
Can I select between IDs instead of classes?
Yes. For example, $('#start').nextUntil('#end') uses the same traversal rules; ensure both IDs identify siblings in the intended container.
Should I use Cheerio or a browser for this task?
Use Cheerio when the needed nodes are already in supplied HTML. Use a browser first when scripts, network requests, interaction, or rendered state creates the nodes.
Frequently Asked Questions
Does nextUntil include the start element?
No. It returns following siblings only, excluding both the start selection and the stop match.
Can I use IDs as the boundaries?
Yes. Replace the class selectors with #start and #end, provided the nodes are siblings in the intended parent.
When is a browser required instead of Cheerio?
Use browser automation when JavaScript, network requests, or interaction creates the nodes you need; Cheerio parses only the markup it receives.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsQuick 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.

