← ALL FIELD NOTES

How to Extract Sections from Long Documents Without Returning the Entire File

Build a section-aware document extraction pipeline that finds the relevant passage, retains provenance, and enforces bounded API responses.

Developers rarely need every character in a 200-page document. A workflow may need the eligibility criteria from a policy PDF, the methods section from a paper, an appendix from a filing, or the requirements under one heading in a technical specification.

Returning the entire file’s text is the easy implementation, but it creates avoidable work: larger payloads, more downstream parsing, ambiguous source context, and a greater chance that a consumer uses the wrong passage. A better approach is to treat section extraction as a bounded retrieval problem.

The core pipeline is:

  1. Locate the likely section by page range, heading, or both.
  2. Extract only the relevant pages or layout elements.
  3. Determine where the section ends.
  4. Preserve heading context and page provenance.
  5. Enforce a response-size budget and report truncation explicitly.

This design supports both simple born-digital PDFs and more difficult scanned or layout-heavy documents.

Start with the right extraction mode

There are two related but distinct problems. Choosing between them up front makes the API predictable.

Deterministic page filtering

Use page filtering when the caller already knows where the content lives:

  • “Return pages 18 through 21.”
  • “Extract appendix B from pages 94–103.”
  • “Read the revised requirements pages recorded in our database.”

This is the most economical path because the system can skip unrelated pages. Azure Document Intelligence, for example, documents a pages parameter for selecting individual pages or page ranges in multipage documents. Its layout documentation is a useful reference for this pattern.

Page ranges are deterministic, but they are not semantic. A revised edition can shift a section by several pages. Therefore, store a document version or content hash alongside page-based rules, and verify the heading you find on the selected pages.

Semantic section discovery

Use semantic discovery when the request is expressed as a document concept:

  • “Find the Eligibility section.”
  • “Return Methods, including its subsections.”
  • “Get the limitations after the security requirements.”

Here, page numbers may be an optimization, but headings and document structure define the result. A layout-aware parser can identify titles, narrative text, and list items as typed elements; Unstructured documents these element categories and PDF partitioning approaches in its partitioning guide. Azure’s layout model also identifies paragraph roles such as title and section heading and can represent sections and subsections. See the layout model documentation.

In practice, use a hybrid strategy: narrow the candidate pages when possible, then use heading-aware logic to establish precise start and end boundaries.

Define a bounded response contract first

A section endpoint should not return an unstructured text field alone. Consumers need to know what was found, where it came from, and whether it was cut short to meet an output budget.

A practical response shape might look like this:

{
  "query": {
    "heading": "Eligibility",
    "match": "exact"
  },
  "section": {
    "headingPath": ["Program Rules", "Eligibility"],
    "elementType": "section",
    "pageStart": 12,
    "pageEnd": 14,
    "text": "...",
    "truncated": false,
    "nextCursor": null
  },
  "source": {
    "documentId": "policy-2026-04",
    "documentVersion": "sha256:..."
  }
}

The important fields are:

FieldWhy it belongs in the response
headingPathKeeps a subsection understandable when separated from its parent heading.
pageStart / pageEndGives users and systems a source location to verify.
elementTypeDistinguishes prose from a list, table, or other structural content.
truncatedPrevents a bounded response from looking complete when it is not.
nextCursorLets the client continue safely instead of increasing the default payload size.
document versionMakes page references and cached output auditable across revisions.

Google Document AI’s document model includes page span information and hierarchical layout constructs, making it a useful schema reference for page-backed, structure-aware output. Its layout parser can also attach ancestor headings to chunks. Google’s quickstart and layout parser documentation describe those concepts.

A hard response ceiling should be a contract, not a UI preference. For example, set maxChars to 20,000 or maxElements to 100, stop at the closest safe element boundary, and set truncated: true. Do not silently slice in the middle of a table cell or heading if your parser gives you structural elements.

A reliable section-boundary algorithm

For a semantic query, the selection logic can be simple and still robust:

  1. Normalize the requested heading and every candidate heading.
  2. Find exact matches first; use controlled fuzzy matching only when your product needs it.
  3. Select the first matching heading at the requested hierarchy level.
  4. Include all following elements until the next heading of the same or higher level.
  5. Retain nested headings and their content.
  6. Stop early when the response budget is reached, preserving whole elements.

In pseudocode:

def select_section(elements, target, max_chars):
    start = find_heading(elements, normalize(target))
    if start is None:
        return None

    selected = []
    used = 0
    root_level = start.level

    for element in elements[start.index:]:
        if element.is_heading and element.index != start.index:
            if element.level <= root_level:
                break

        cost = len(element.text)
        if used + cost > max_chars:
            return build_result(selected, truncated=True)

        selected.append(element)
        used += cost

    return build_result(selected, truncated=False)

This is preferable to searching for a heading and returning “the next N characters.” Character windows lose the structural rule that tells you where a section ends. They can also separate a heading from its list, table, or qualifying note.

Title-aware chunking follows the same idea. Unstructured’s by_title strategy starts a new chunk at title elements so chunks do not cross section boundaries; it also provides size limits and optional page-boundary behavior. Its chunking documentation describes these controls.

Build bounded document endpoints, not full-text dumps. Create a PagePith account to explore a workflow that starts from the specific source content your application needs.

Implement the fast path for known PDF pages

For born-digital PDFs with known relevant pages, local extraction can be a good deterministic baseline. PyMuPDF exposes document and page APIs, including page-level text extraction; its documentation covers opening documents and working page by page in The Basics.

Here is an intentionally small example that reads only a requested 1-based page range and returns page-tagged output:

import pymupdf


def extract_page_range(pdf_path: str, first_page: int, last_page: int) -> list[dict]:
    if first_page < 1 or last_page < first_page:
        raise ValueError("Invalid page range")

    results = []
    with pymupdf.open(pdf_path) as document:
        if last_page > document.page_count:
            raise ValueError("Page range exceeds document length")

        for page_number in range(first_page, last_page + 1):
            page = document.load_page(page_number - 1)
            text = page.get_text("text")
            results.append({
                "page": page_number,
                "text": text,
            })

    return results

This code deliberately does not concatenate the document before filtering. It loads only the specified pages and keeps the page number beside each extracted segment.

That said, plain text is not a complete section extractor. PDF text order reflects how the file encodes text and may differ from normal reading order. PyMuPDF explicitly notes this caveat and offers alternatives such as blocks, words, dictionaries, HTML, and XML in its text extraction recipes. For a two-column page, a table, or headers repeated on each page, get_text("text") may be a poor input to heading detection.

For a modest upgrade, use block-level extraction, sort or group blocks according to their coordinates, and detect headings from typography or parser-provided roles. Keep the original page and bounding-box metadata through the pipeline so a client can render a source citation later.

Know when local text extraction is not enough

Use local page filtering when documents are consistent, searchable, and structurally simple. Escalate to a layout-aware or OCR-capable parser when any of the following apply:

  • The PDF is scanned and its text is embedded in images.
  • The target includes tables, figures, multi-column prose, or dense lists.
  • A heading continues across page breaks.
  • The document has inconsistent reading order or repeated boilerplate.
  • You need typed elements and heading hierarchy rather than plain strings.

OCR is required when visible text is raster content rather than native PDF text. PyMuPDF documents OCR-oriented extraction for pages containing raster images or vector graphics in its page API documentation. This should be a fallback or a document-classification decision, not an afterthought after an empty result.

For complex layouts, cloud parsers can reduce implementation work by returning recognized headings, tables, paragraphs, lists, and page-linked structures. Google’s layout parser documentation lists those layout elements and discusses configurable chunking, as well as processing constraints and the fact that multipage tables can be split. Review those limitations before adopting it. A section API still needs its own boundary and truncation rules even when the underlying parser produces chunks.

Preserve provenance through every transformation

Extraction bugs are often verification bugs. A response may contain correct words but omit the parent heading, merge two neighboring sections, or drop a footnote that changes the meaning.

Carry this metadata on every intermediate element:

{
  "text": "Applicants must provide...",
  "type": "NarrativeText",
  "page": 13,
  "bbox": [72, 221, 530, 298],
  "headingPath": ["Program Rules", "Eligibility"],
  "sourceElementId": "p13-b7"
}

Not every extractor supplies every field, but the API model should leave room for them. Preserve page information at minimum. Add bounding boxes when a reviewer may need to highlight the original location. Keep heading paths even if the visible response only displays the final heading.

Also separate not found, found but empty, and truncated outcomes. They represent different operational conditions:

  • 404 or a domain-specific SECTION_NOT_FOUND: no confident heading match.
  • Successful empty result: a valid selected page range had no extractable native text; OCR may be needed.
  • 200 with truncated: true: a matching section exists but exceeds the caller’s response budget.

An honest PagePith demonstration

The supplied PagePith proof shows a fetch of the PyMuPDF documentation page at The Basics. The retrieved result was titled “The Basics - PyMuPDF documentation,” identified as the fetch tier, and reported a content length of 25,689. Its Markdown excerpt began with the “Opening a File” section and included a Python example that imports pymupdf and opens a PDF.

That is a useful, limited demonstration: PagePith retrieved a specific documentation page and produced Markdown content with recognizable section structure rather than requiring the entire website. It does not, by itself, demonstrate PDF OCR, table recognition, semantic heading matching, or a bounded section-extraction API. Those capabilities should be validated against the parser and documents you choose.

Test for boundaries, not just text presence

A production test suite should use documents designed to break naïve extraction:

  1. A section beginning at the bottom of one page and continuing on the next.
  2. A repeated heading such as Requirements in two different chapters.
  3. Two-column text with headers and footers.
  4. A scanned page followed by a native-text page.
  5. A section containing a table and a footnote.
  6. A revised document where the target heading moved pages.
  7. An oversized section that must return truncated: true and a continuation cursor.

For each fixture, assert the heading path, inclusive page range, element sequence, response-size limit, and truncation behavior. Text equality alone is too brittle for OCR and layout processing; structural assertions are usually more valuable.

Build for narrow, verifiable answers

The goal is not merely to extract less text. It is to return the smallest answer that still carries enough context to be trusted.

Use page ranges for known locations. Use heading-aware elements for semantic requests. Add OCR for image-based content. Preserve page and hierarchy metadata. Finally, enforce a hard payload budget with transparent truncation semantics.

That combination gives downstream systems a focused passage instead of a full-file dump—and gives reviewers a clear path back to the source.

Sign up for PagePith to begin working from targeted source content in your own document workflows.

Sources

  1. The Basics — PyMuPDF documentationPyMuPDF
  2. Text recipes — PyMuPDF documentationPyMuPDF
  3. Page — PyMuPDF documentationPyMuPDF
  4. Partitioning — Unstructured documentationUnstructured
  5. Chunking — Unstructured documentationUnstructured
  6. Document layout analysis — Azure Document IntelligenceMicrosoft Learn
  7. Layout Parser Quickstart — Google Document AIGoogle Cloud
  8. Process documents with Gemini layout parser — Google Document AIGoogle Cloud
Extract Sections From Long Documents · PagePith