Cheerio获取HTML锚点对应标题层级时prev()返回空文本问题排查
Let's break down why your code is returning empty text for those previous headers, and fix it up:
The Root Problem
Your current code uses prev("h" + level) to look for the previous header, but Cheerio's prev() method only checks the immediately preceding sibling element. In your HTML structure:
<h1>Title</h1> <p>Some content...</p> <h2><a id="123"></a>Foo</h2>
The <h2>'s immediate previous sibling is a <p>, not the <h1>. So prev("h1") returns an empty Cheerio collection, and text() gives you an empty string.
The Fix: Use prevAll() Instead
To find all preceding sibling headers (not just the immediate one) and grab the closest matching one for each level, use prevAll("h" + level).first(). Here's how to adjust your code:
const linkModel: IDictionary<ILinkModelEntry> = {}; const $ = cheerio.load(html); let count = 0; // Assuming count is defined in your scope $("a").each((_i, elt) => { const anchor = $(elt); const id = anchor.attr("id"); // Simplified attribute access if (id) { const parent = anchor.parent(); const parentTag = parent.prop("tagName"); let headerHierarchy: any[] = []; if (["H1", "H2", "H3", "H4", "H5", "H6"].includes(parentTag)) { let currentLevel = parseInt(parentTag[1]); // Start with the current header headerHierarchy = [{ level: currentLevel, text: parent.text().trim() }]; currentLevel--; while (currentLevel > 0) { // Find ALL preceding siblings with the current level, then take the closest one const prevHeader = parent.prevAll(`h${currentLevel}`).first(); if (prevHeader.length) { // Only add if we found a valid matching header headerHierarchy.unshift({ level: currentLevel, text: prevHeader.text().trim() }); } currentLevel--; } } linkModel[`#${id}`] = { originalId: id, count: count++, headerHierarchy }; } });
Key Improvements:
prevAll()instead ofprev(): This searches all sibling elements that come before the current parent header, not just the immediate one..first(): Ensures we grab the closest matching header (the one most recent before the current title), which is what you need for a proper hierarchy..trim(): Cleans up any extra whitespace in the title text (optional but helpful for consistency).- Check
prevHeader.length: Avoids adding empty entries if a lower-level header doesn't exist, making the code more robust for edge cases.
Testing with Your Example HTML:
- For
id="123"(parent<h2>), the code will:- Add
{ level: 2, text: "Foo" }to the hierarchy - Find the
<h1>Title</h1>viaprevAll("h1"), add it to the start of the array - Result:
[{level: 1, title: "Title"}, {level: 2, title: "Foo"}](matches your expected output)
- Add
- For
id="456"(parent<h3>), it will:- Add
{ level: 3, text: "Bar" } - Find the
<h2>Foo</h2>viaprevAll("h2"), add to start - Find the
<h1>Title</h1>viaprevAll("h1"), add to start - Result:
[{level: 1, title: "Title"}, {level: 2, title: "Foo"}, {level: 3, title: "Bar"}](perfect match)
- Add
内容的提问来源于stack exchange,提问作者omg_me
相关产品推荐
相关产品推荐

