Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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 .length and 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.