Web pages often place article text beside navigation, scripts, and repeated footer copy. HTMLNodeParser lets a LlamaIndex ingestion path select the element types that become retrieval nodes before indexing.

The parser reads raw HTML through BeautifulSoup and accepts a tags list containing element names such as h1, p, and li. Each matching element can become a node. Text from an unselected descendant remains inside its selected parent's node, while a descendant named in tags is omitted from the parent text so it can be parsed separately.

Five nodes should emerge from the sample HTML and all five should appear in VectorStoreIndex. MockEmbedding keeps that handoff local and deterministic, isolating the parser-to-index boundary from API credentials and model availability.

Steps to use HTMLNodeParser in LlamaIndex:

  1. Install LlamaIndex core and BeautifulSoup in the active project environment.
    $ python3 -m pip install llama-index-core beautifulsoup4

    HTMLNodeParser imports the bs4 package at runtime. Omitting beautifulsoup4 causes an import error when HTML parsing begins.

  2. Create html_node_parser_smoke.py from the starter block below.
    html_node_parser_smoke.py
    from llama_index.core import Document, VectorStoreIndex
    from llama_index.core.embeddings import MockEmbedding
    from llama_index.core.node_parser import HTMLNodeParser
     
    html = """<html><body><h1>Support portal runbook</h1>
    <p>Billing tickets route to the finance queue.</p>
    <nav>Skip navigation text</nav><section><h2>Escalation rule</h2>
    <p>Escalate refund requests over $500 to Maya.</p><ul>
    <li>Attach the customer invoice.</li>
    <li>Record the approval code in the ticket.</li></ul></section></body></html>"""
     
    document = Document(text=html, metadata={"source": "support-portal.html"})
  3. Add the tag selection and parser call after the document definition.
    parser = HTMLNodeParser(tags=["h1", "h2", "p", "li"])
    nodes = parser.get_nodes_from_documents([document])
    print(f"parsed nodes: {len(nodes)}")

    The selected tags keep headings, paragraphs, and list items while excluding the nav element. The default HTMLNodeParser tag set is broader, so define tags explicitly when page chrome should stay out of retrieval.

  4. Add the node verification block after the parsed-node count.
    assert len(nodes) == 5
    assert all("Skip navigation text" not in node.get_content() for node in nodes)
    for number, node in enumerate(nodes, start=1):
        tag = node.metadata.get("tag", "unknown")
        text = node.get_content().replace("\n", " ")
        print(f"{number}. tag={tag} text={text}")
  5. Add the mock-backed index construction after the inspection loop.
    index = VectorStoreIndex(nodes, embed_model=MockEmbedding(embed_dim=8))
    assert len(index.index_struct.nodes_dict) == len(nodes)
    print(f"indexed nodes: {len(index.index_struct.nodes_dict)}")

    MockEmbedding keeps the smoke test local; a retrieval application uses its configured embedding model for these nodes.

  6. Confirm that the completed html_node_parser_smoke.py file matches the assembled program.
    html_node_parser_smoke.py
    from llama_index.core import Document, VectorStoreIndex
    from llama_index.core.embeddings import MockEmbedding
    from llama_index.core.node_parser import HTMLNodeParser
     
    html = """<html><body><h1>Support portal runbook</h1>
    <p>Billing tickets route to the finance queue.</p>
    <nav>Skip navigation text</nav><section><h2>Escalation rule</h2>
    <p>Escalate refund requests over $500 to Maya.</p><ul>
    <li>Attach the customer invoice.</li>
    <li>Record the approval code in the ticket.</li></ul></section></body></html>"""
     
    document = Document(text=html, metadata={"source": "support-portal.html"})
     
    parser = HTMLNodeParser(tags=["h1", "h2", "p", "li"])
    nodes = parser.get_nodes_from_documents([document])
    print(f"parsed nodes: {len(nodes)}")
     
    assert len(nodes) == 5
    assert all("Skip navigation text" not in node.get_content() for node in nodes)
    for number, node in enumerate(nodes, start=1):
        tag = node.metadata.get("tag", "unknown")
        text = node.get_content().replace("\n", " ")
        print(f"{number}. tag={tag} text={text}")
     
    index = VectorStoreIndex(nodes, embed_model=MockEmbedding(embed_dim=8))
    assert len(index.index_struct.nodes_dict) == len(nodes)
    print(f"indexed nodes: {len(index.index_struct.nodes_dict)}")
  7. Run the completed HTML parser smoke test.
    $ python3 html_node_parser_smoke.py
    parsed nodes: 5
    1. tag=h1 text=Support portal runbook
    2. tag=p text=Billing tickets route to the finance queue.
    3. tag=h2 text=Escalation rule
    4. tag=p text=Escalate refund requests over $500 to Maya.
    5. tag=li text=Attach the customer invoice. Record the approval code in the ticket.
    indexed nodes: 5

    The missing navigation text confirms the tag filter worked. Consecutive list items are collected under one li node by the parser's HTML grouping logic, and the indexed count confirms every parsed node reached VectorStoreIndex.