mrkeyoor.com_
Thu 06 Aug 08:55 UTC
PyPIDataupdated 06 Aug 2026

pdfminer.six

pdfminer.six reads the text out of PDF files by interpreting the drawing instructions inside them, not by looking at pictures of pages. It walks the content stream, decodes the fonts and character maps, and reports every glyph with its exact position on the page, then runs a layout pass that groups those glyphs into words, lines, and text boxes. You can take the one-line answer (extract_text gives you a string) or take the layout tree and decide yourself what counts as a paragraph, a column, or a table cell, using coordinates and font names. It is a community-maintained fork of the original PDFMiner, written entirely in Python, and it is the engine underneath pdfplumber and a lot of document-processing pipelines.

Verdict

The dependable pure-Python way to get text with exact coordinates out of digital PDFs, and the foundation the nicer libraries are built on. It is slow, it does nothing for scanned pages, and it will never hand you a table, so choose it when you want control over layout data rather than a finished answer.

API stability4/5extract_text, extract_pages, and the LAParams and converter classes have kept the same signatures for years, and date-stamped releases are fixes rather than redesigns; the 20251230 security release did force anyone with custom pickled CMaps to convert them to JSON.
Docs4/5pdfminersix.readthedocs.io has task-shaped tutorials (text, images, form data), a topics section that actually explains the coordinate system and every LAParams knob, and generated API pages; the converter classes stay thin and the non-text output modes are barely covered.
Maintenance3/5Last pushed March 2026 with 213 open issues (223 counting PRs) and a README that says maintainer availability is limited and PRs are the fastest route to a fix; security work does still land, such as CVE-2025-64512 replacing pickled CMap storage with JSON in the 20251230 release.
Ecosystem5/5Around 16.9 million weekly downloads and 7,015 stars, and it is the layer other tools sit on: pdfplumber depends on it directly with an exact pin, so answers and worked examples for coordinate-level PDF work almost always assume it.

Use it if

  • You need text plus geometry: every character comes with a bounding box, a font name, and a size, which is what you build invoice and form parsers on
  • You want to write your own grouping logic rather than accept someone's paragraph heuristics, because extract_pages hands you the raw LTPage tree
  • Your PDFs are encrypted with a password (RC4 or AES) or contain CJK and vertical writing, both of which are handled without extra packages
  • You need something that installs into a slim container or a lambda without system PDF libraries, and a pdf2txt.py command line that works immediately
Skip it if

Setup reality

pip install pdfminer.six on Python 3.10 or newer. The library itself is pure Python, but it pulls cryptography for RC4 and AES decryption and charset-normalizer for encodings, and cryptography is a compiled wheel, so an unusual platform without a prebuilt wheel drags in a Rust toolchain. Image extraction is an extra: pip install 'pdfminer.six[image]' adds Pillow. Two naming traps catch people. The distribution is pdfminer.six with a dot but the import is plain pdfminer, and the abandoned original pdfminer package is still sitting on PyPI, so installing the wrong name gives you dead code from years ago. The console scripts keep their suffix too, so the commands are literally pdf2txt.py and dumppdf.py. Versions are dates like 20260107 rather than semver, which means pinning is exact-match and a release number tells you nothing about whether it breaks you; read the changelog instead. The real configuration cost is LAParams, whose defaults (char_margin 2.0, line_margin 0.5, word_margin 0.1, boxes_flow 0.5) decide where paragraphs and columns land, and getting sensible output on your own documents almost always means tuning them.

Patterns

Get the whole document as a stringextract-all-text

from pdfminer.high_level import extract_text

text = extract_text("example.pdf")
print(text)

Accepts a path or an already-open binary file object. An empty or nearly empty result almost always means the pages are scanned images, not that the file is broken.

Read only the pages you needextract-specific-pages

from pdfminer.high_level import extract_text

first_two = extract_text("report.pdf", page_numbers=[0, 1])
cover = extract_text("report.pdf", maxpages=1)

page_numbers is zero-indexed, so page 1 of the document is 0. Passing a set instead of a list is faster on long documents because membership is checked once per page.

Fix wrong paragraph and column groupingtune-layout-analysis

from pdfminer.high_level import extract_text
from pdfminer.layout import LAParams

params = LAParams(line_margin=0.3, char_margin=1.5, boxes_flow=0.5)
text = extract_text("two-column.pdf", laparams=params)

# read strictly in position order, no grouping heuristics:
raw = extract_text("odd.pdf", laparams=LAParams(boxes_flow=None))

All the margins are relative to character or line size, not absolute points. boxes_flow runs from -1.0 (horizontal position only) to +1.0 (vertical only); setting it to None turns off advanced ordering entirely, which is often what you want for a fixed template.

Walk the layout tree instead of taking a stringiterate-layout-objects

from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer

for page_layout in extract_pages("example.pdf"):
    for element in page_layout:
        if isinstance(element, LTTextContainer):
            x0, y0, x1, y1 = element.bbox
            print(round(y0), element.get_text().strip())

extract_pages is a generator, so pages are parsed lazily and memory stays flat. Coordinates start at the bottom-left of the page in PDF points (72 per inch), so y grows upward, the opposite of screen coordinates.

Read font name and size per charactercharacter-font-info

from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer, LTTextLine, LTChar

for page in extract_pages("example.pdf"):
    for element in page:
        if not isinstance(element, LTTextContainer):
            continue
        for line in element:
            if not isinstance(line, LTTextLine):
                continue
            for char in line:
                if isinstance(char, LTChar):
                    print(char.get_text(), char.fontname, round(char.size, 1))

A text line also contains LTAnno objects for spaces and newlines that the layout pass inserted; those have no font or position, which is why the isinstance check on LTChar matters. Font names arrive with the PDF subset prefix, as in ABCDEF+Helvetica.

Pull a value from a fixed spot on the pagefind-text-by-position

from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer

BOX = (350, 680, 560, 720)  # x0, y0, x1, y1 in points

def inside(bbox, box):
    return bbox[0] >= box[0] and bbox[1] >= box[1] and bbox[2] <= box[2] and bbox[3] <= box[3]

for page in extract_pages("invoice.pdf", page_numbers=[0]):
    for el in page:
        if isinstance(el, LTTextContainer) and inside(el.bbox, BOX):
            print(el.get_text().strip())

Check page.mediabox before hardcoding numbers, since A4 and Letter differ and a rotated page swaps width and height. This is how template-based invoice parsing gets built on pdfminer.

Open a password-protected PDFencrypted-pdf

from pdfminer.high_level import extract_text

text = extract_text("protected.pdf", password="hunter2")

This handles user passwords on RC4 and AES encrypted files. An owner-password-only file (no password needed to open, but copying is disallowed) still extracts, because pdfminer reads the content rather than honoring the permission flag.

Save embedded images to diskextract-images

# pip install 'pdfminer.six[image]'
from pdfminer.high_level import extract_pages
from pdfminer.image import ImageWriter
from pdfminer.layout import LTImage, LTFigure

writer = ImageWriter("out_images")
for page in extract_pages("scan.pdf"):
    for element in page:
        if isinstance(element, LTFigure):
            for obj in element:
                if isinstance(obj, LTImage):
                    print(writer.export_image(obj))

Images usually sit inside an LTFigure rather than at the top level of the page, which is why the nested loop is needed. export_image returns the filename it chose and picks the extension from the image encoding, so you may get .jpg, .png, or a raw .bmp.

Read document metadata and the table of contentsmetadata-and-outlines

from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument, PDFNoOutlines

with open("book.pdf", "rb") as fp:
    doc = PDFDocument(PDFParser(fp))
    print(doc.info)  # list of dicts: Title, Author, CreationDate, ...
    try:
        for level, title, dest, a, se in doc.get_outlines():
            print(" " * level, title)
    except PDFNoOutlines:
        print("no bookmarks")

Values in doc.info come back as raw bytes, so decode them yourself and expect encoding surprises. get_outlines raises PDFNoOutlines rather than yielding nothing when the document has no bookmarks.

Tell a scanned PDF from a digital one before you parse itdetect-scanned-pdf

from pdfminer.high_level import extract_text

def needs_ocr(path, sample_pages=3, min_chars=100):
    text = extract_text(path, maxpages=sample_pages)
    return len(text.strip()) < min_chars

if needs_ocr("unknown.pdf"):
    print("run OCR first, pdfminer will return nothing useful")

There is no built-in flag for this, and no exception is raised, so a scan silently produces empty output. Sampling the first few pages keeps the check cheap on long documents.

Extract from the command linecli-extraction

pdf2txt.py example.pdf > out.txt
pdf2txt.py -p 1,2 -o out.txt example.pdf
pdf2txt.py -P hunter2 protected.pdf

# inspect the PDF object structure
dumppdf.py -a example.pdf | head -40

The .py suffix is part of the installed command name, which trips up anyone typing pdf2txt. dumppdf.py is the tool to reach for when extraction returns nothing and you need to see what objects the file actually contains.

Write output through the low-level entry pointstream-output-to-file

from io import StringIO
from pdfminer.high_level import extract_text_to_fp
from pdfminer.layout import LAParams

with open("example.pdf", "rb") as fin:
    out = StringIO()
    extract_text_to_fp(fin, out, laparams=LAParams(), output_type="text")
    print(out.getvalue())

This is what pdf2txt.py calls, and it takes options extract_text does not, including rotation, scale, and output_dir for images. Note that omitting laparams here is not the same as passing LAParams(): None skips layout analysis entirely.

Alternatives

PackageRegistryPick it when
pdfplumberPyPIYou want tables, word-level extraction, cropping, and page images without writing layout code; it wraps this exact library and pins pdfminer.six==20260107.
pypdfium2PyPIYou need speed or page rendering to images and want permissive licensing, since it binds Google's pdfium under BSD and Apache terms.
pymupdfPyPIYou want the fastest and most complete extraction available and your project can accept AGPL, or you are willing to buy the commercial license.
pypdfPyPIYour job is manipulating files (merge, split, rotate, encrypt) with rough text extraction on the side, and you want a pure-Python package with light dependencies.