← ALL FIELD NOTES

How to Chunk Extracted Web Content for Retrieval Without Breaking Its Meaning

A structure-first method for turning extracted web pages into retrievable RAG chunks while preserving headings, lists, tables, context, and citations.

Retrieval quality begins before embeddings. If a pipeline turns a web page into one long string and slices every 800 tokens, it may produce technically valid chunks that are semantically broken: a heading is separated from its rule, the first half of a procedure is detached from its final step, or table values lose the headers that explain them.

A better mental model is structure-preserving compilation. First convert a page into ordered, typed elements. Then assemble those elements into chunks that can answer a question independently. Token limits still matter, but they are constraints on a structurally meaningful unit—not the first rule used to create one.

This article shows how to chunk web content for retrieval without losing the structure that gives it meaning.

Start with typed elements, not cleaned text

A web page is more than text. It contains a hierarchy and several kinds of evidence:

  • headings and nested sections;
  • paragraphs, callouts, and quotations;
  • ordered, unordered, and description lists;
  • tables with captions, headers, and cells;
  • code blocks and captions;
  • links, source locations, and document-level metadata.

Flattening all of that into plain text is sometimes necessary for an embedding model, but it should happen late in the pipeline. Preserve the intermediate representation first.

A useful extracted element model might look like this:

{
  "element_id": "el_0042",
  "type": "list_item",
  "text": "Rotate the signing key before deploying the new version.",
  "path": ["Security guide", "Deployment procedure"],
  "list_id": "list_07",
  "ordinal": 3,
  "source_url": "https://docs.example.com/security/deployments",
  "source_locator": { "selector": "main > section:nth-of-type(4) > ol > li:nth-child(3)" }
}

The exact locator will vary by extractor. It might be a DOM selector, page coordinate, character range, or a stable internal element identifier. The important part is that chunking receives both content and structure.

This approach matches how document-oriented systems model retrieval units: nodes can retain source-document metadata and relationships instead of becoming disconnected strings. LlamaIndex’s document and node model is one example of source-linked chunks with metadata and node relationships.

Use semantic boundaries first, then enforce size limits

The central algorithm is straightforward:

  1. Begin a candidate chunk at a meaningful boundary, usually a heading or a document start.
  2. Add compatible adjacent elements in document order.
  3. Stop before crossing into a new semantic section or incompatible block.
  4. Apply the target size as a soft constraint.
  5. If one indivisible element exceeds the hard limit, split only that element with a specialized strategy.

Why not simply maximize chunk size? Large chunks can dilute a match with unrelated material; tiny chunks can fail to express a complete claim. Research on document segmentation identifies this tension between irrelevant context in oversized units and lost coherence in undersized ones, motivating theme-aware grouping rather than purely fixed windows. Document Segmentation Matters for Retrieval-Augmented Generation explores that trade-off directly.

A practical policy uses two limits:

  • Target size: the preferred upper range for a normal chunk.
  • Hard maximum: the point at which an oversized atomic element must be split.

For example, a chunker may prefer sections that fit in its target range, but keep a three-paragraph explanation intact if it is slightly larger. In contrast, a 3,000-token paragraph or a 100-row table needs internal splitting. This is a policy decision, not a universal number: the right limits depend on the documents, retriever, and answer task.

A section-aware assembly loop

Here is pseudocode for the core pass:

for element in ordered_elements:
  if element.type is heading:
    flush(current_chunk)
    current_path = update_heading_path(element)
    continue

  if starts_incompatible_block(element, current_chunk):
    flush(current_chunk)

  if exceeds_hard_limit(element):
    flush(current_chunk)
    emit(split_atomic_element(element, current_path))
    continue

  if exceeds_target_limit(current_chunk + element):
    flush(current_chunk)

  add(element, current_chunk)

flush(current_chunk)

starts_incompatible_block is where product-specific rules belong. A table should not silently merge into prose. A code example may be best paired with its immediately preceding explanation. A new heading should normally close the prior section.

Title-aware chunking follows a similar principle: headings act as boundaries that prevent chunks from spanning unrelated sections. This is consistent with both the machine-readable structural role of headings and documented title-based chunking behavior in Unstructured’s chunking guidance.

Preserve the meaning of lists and tables

Lists and tables are the most common casualties of text-only chunking.

Lists are not a sequence of unrelated sentences

An ordered list encodes order. A description list encodes a term-definition relationship. An unordered list still expresses membership in a shared set. MDN’s semantic HTML curriculum distinguishes these structures because their meaning differs.

Therefore, a chunker should:

  • retain the parent heading with the list;
  • keep all list items together when feasible;
  • preserve numbering, nesting, and item order;
  • avoid combining the end of one list with the beginning of another;
  • repeat the list title and continuation marker when a large list must split.

Suppose a section is titled Release checklist and contains 18 numbered steps. If it must become three chunks, do not emit bare items 7–12. Emit a representation such as:

Release checklist — steps 7–12 of 18
7. Verify the migration completed.
8. Enable the feature flag.
...

That repeated context makes a retrieved middle segment understandable without requiring the retriever to fetch chunk one.

Tables are structured evidence, not prose

A table cell often means nothing without its row label, column label, unit, and caption. HTML table accessibility semantics explicitly connect data cells to headers through constructs such as th, scope, id, and headers. See MDN’s table accessibility guide.

Treat a table as an atomic block where possible. Store its structured form separately, then create retrieval text that repeats essential context.

Pricing limits
Columns: Plan | Requests per day | Retention
Row: Team | 50,000 | 30 days

For a table that exceeds the hard limit, split by coherent row groups, repeating the caption and column headers in every resulting chunk. Keep the original table representation or source HTML in metadata for rendering, auditing, or more exact downstream handling. Unstructured’s chunking documentation likewise treats tables separately and only divides them when size constraints require it.

Add context that survives retrieval

A chunk may be structurally clean yet still be ambiguous. Consider this sentence:

It applies only after the 2024 migration.

Without the entity, policy, or section name, it is hard to retrieve correctly and risky to quote in an answer.

Build a compact contextual prefix from facts already available in the source:

Document: Platform migration guide
Section: Database cutover > Rollback rules
Applies to: 2024 migration

Then keep the retrieval payload separate from the visible source text:

{
  "retrieval_text": "Document: Platform migration guide\nSection: Database cutover > Rollback rules\nApplies to: 2024 migration\n\nA rollback is permitted only when...",
  "display_text": "A rollback is permitted only when...",
  "heading_path": ["Database cutover", "Rollback rules"]
}

This avoids hiding synthetic context in a user-facing quotation while giving the retriever the disambiguating terms it needs. Anthropic’s discussion of Contextual Retrieval describes the same core failure mode: chunking can remove the context needed to identify a passage, and a concise document-specific prefix can restore it.

Want to test a structure-first extraction workflow against real source pages? Sign up for PagePith.

Make provenance a first-class field

Chunks are derived artifacts. You need to be able to answer: Which exact source material produced this text?

At minimum, store:

{
  "chunk_id": "sha256:...",
  "source_url": "https://docs.example.com/security/deployments",
  "source_title": "Deployment procedure",
  "heading_path": ["Security guide", "Deployment procedure"],
  "element_ids": ["el_0039", "el_0040", "el_0041", "el_0042"],
  "previous_chunk_id": "chunk_18",
  "next_chunk_id": "chunk_20",
  "retrieval_text": "...",
  "display_text": "..."
}

The element_ids field matters because a chunk often consolidates multiple source elements. Once consolidated, a single page number, coordinate, or source location can become ambiguous. Unstructured documents this provenance issue and preserves original elements to retain their metadata.

Neighbor links are also useful. They let a retrieval layer expand one highly relevant chunk with immediate context instead of embedding large, repetitive windows everywhere. This is related to sentence-window and hierarchical approaches, where the matching unit can be small while the generation context is expanded through relationships. LlamaIndex’s node parser documentation describes both sentence windows and hierarchical parent-child nodes.

Apply overlap only when a unit must be split

Fixed overlap is often used as insurance against bad boundaries. But once the pipeline respects headings, paragraphs, and lists, copying the final tokens of every chunk into the next can create duplicate evidence and retrieval noise.

Use overlap selectively:

  • No overlap between intact sections and whole elements.
  • Small overlap when splitting an oversized paragraph, so a sentence transition remains interpretable.
  • Header repetition rather than token overlap for table fragments.
  • Heading and list-context repetition for split lists.

This distinction is supported by Unstructured’s workflow configuration guidance, which notes that overlap added to otherwise complete semantic units can introduce noise. Structural context is usually a cleaner solution than blindly duplicated trailing text.

Evaluate chunking before blaming embeddings

A weak retrieval result does not automatically mean you need a new embedding model. Inspect the chunk itself first.

Create a small evaluation set of realistic questions, expected source passages, and expected citations. Then measure at least these properties:

  1. Reference completeness: Does the chunk contain the evidence needed to answer?
  2. Intrachunk cohesion: Do its elements discuss one topic?
  3. Contextual coherence: Does it state who, what, and when clearly enough to stand alone?
  4. Block integrity: Are lists, code blocks, and tables preserved appropriately?
  5. Size compliance: Does it respect the retrieval and generation budget?
  6. Citation resolvability: Can every returned claim be traced to source elements?

These dimensions align with evaluation criteria discussed in AutoChunker and the document-level metrics proposed in Adaptive Chunking. They also provide useful debugging categories: if a correct passage is not retrieved, determine whether it was absent, ambiguous, diluted by noise, or untraceable.

A limited PagePith demonstration from the supplied proof

The supplied PagePith proof shows one concrete extraction result. A fetch request for https://aclanthology.org/2025.findings-acl.422/ returned the title Document Segmentation Matters for Retrieval-Augmented Generation, a reported content length of 17576, and Markdown beginning with the linked paper title followed by author information.

That is enough to illustrate the first handoff in this workflow: extracted Markdown can retain recognizable document content and can be passed into a structural parsing stage rather than treated immediately as arbitrary character data.

It does not prove that this particular fetch produced heading paths, list identities, table schemas, stable element IDs, or complete source locators. Those fields should be verified in the extraction output you actually receive before relying on them for chunk provenance. The chunking design above deliberately separates what an extractor returns from the additional structure a parser and chunker may need to create.

Build chunks that can explain themselves

A strong retrieval chunk is not merely within a token budget. It carries the local section meaning, keeps structured blocks intact, identifies its source, and retains links to neighboring context.

Use this order of operations:

  1. extract ordered, typed elements;
  2. retain document hierarchy and source identity;
  3. group adjacent compatible elements by section;
  4. enforce hard limits only after semantic grouping;
  5. split oversized paragraphs, lists, and tables with type-aware rules;
  6. repeat minimal contextual labels in each retrievable fragment;
  7. preserve element-level provenance and neighboring relationships;
  8. evaluate chunk quality independently from embeddings.

The result is a retrieval corpus that is easier to debug, easier to cite, and less likely to turn well-written web documentation into disconnected fragments.

Ready to put a source-first extraction step in front of your retrieval pipeline? Sign up for PagePith.

Sources

  1. Document Segmentation Matters for Retrieval-Augmented GenerationAssociation for Computational Linguistics
  2. Adaptive Chunking: Optimizing Chunking-Method Selection for RAGAssociation for Computational Linguistics
  3. AutoChunker: Structured Text Chunking and its EvaluationAssociation for Computational Linguistics
  4. ChunkingUnstructured documentation
  5. Workflow chunking configurationUnstructured documentation
  6. Documents / NodesLlamaIndex documentation
  7. Node Parser ModulesLlamaIndex documentation
  8. Contextual Retrieval in AI SystemsAnthropic
Chunk Web Content for Retrieval Without Meaning Loss · PagePith