Back to Blog
Python

Extract Text and Images from PDF with PyMuPDF

Learn how to extract text and images from PDF files using PyMuPDF in Python. Includes code examples for text blocks, embedded images, and common edge cases.

PyMuPDFPDF extractiontext extractionimage extractionfitz
A stylized PDF document with text lines and image icons being extracted into separate elements, representing PyMuPDF text and image extraction in Python.

When you need to pull text and images out of a PDF in Python, PyMuPDF (imported as fitz) is one of the most direct options. It gives you page-level access to both the text layer and embedded raster images without requiring a separate OCR engine or complex parsing pipeline. The examples below show how to extract text with page.get_text(), extract embedded images with page.get_images() or get_text("blocks"), and combine both extractions in a single pass. The article also covers performance and edge cases to watch out for when working with large or unusual PDFs.

Setting Up PyMuPDF and Loading a PDF

Install PyMuPDF with pip:

pip install PyMuPDF

The import name is fitz, so use that name in Python code. After importing, you open a PDF by passing a file path to fitz.open():

import fitz doc = fitz.open("sample.pdf")

This returns a Document object. You can iterate over its pages with for page in doc:, or access a specific page by index. Always close the document when you are done to release the file handle:

doc.close()

If the PDF is password-protected, you need to authenticate before accessing pages:

if doc.needs_pass: doc.authenticate("password")

Extracting Text from a PDF Page

The core method for text extraction is page.get_text(). Called with no arguments, it returns the plain text on the page as a single string, preserving line breaks and spaces as they appear in the content stream. This is often enough for simple text dumps, but it does not give you positional information or the ability to separate text blocks.

For more structured output, get_text() accepts an option argument as its first parameter. The most useful options are:

OptionReturn typeDescription
"text"strPlain text with line breaks.
"blocks"list of tuplesEach block is a tuple with coordinates, text, block type, and block number.
"dict"dictA nested dictionary with page-level structure.
"words"list of tuplesEach word as a tuple with coordinates and text.

For example, to get text blocks with their bounding boxes:

page = doc[0] blocks = page.get_text("blocks") for block in blocks: x0, y0, x1, y1, text, block_no, block_type = block print(f"Block {block_no} at ({x0:.1f}, {y0:.1f}): {text[:50]}...")

The block_type is 0 for text and 1 for image blocks. This distinction is useful when you want to process text and images together.

When a PDF has multiple columns or complex layouts, the default "text" option may produce a jumbled reading order. Using "blocks" or "dict" lets you reconstruct the intended order by sorting blocks by their vertical and horizontal positions. For most extraction tasks, "blocks" is a good balance between simplicity and control.

Extracting Images from a PDF Page

PyMuPDF can extract the raw image data embedded in a page. The method page.get_images(full=True) returns a list of image references. Each reference is a tuple containing an xref (cross-reference number), which you can use to retrieve the actual image bytes.

page = doc[0] image_list = page.get_images(full=True) for img in image_list: xref = img[0] pix = fitz.Pixmap(doc, xref) if pix.n - pix.alpha >= 4: # CMYK or other color spaces pix = fitz.Pixmap(fitz.csRGB, pix) pix.save(f"image_{xref}.png") pix = None # free memory

This code does the following:

  1. page.get_images(full=True) returns all images referenced on the page, including those nested inside Form XObjects.
  2. For each image, we get its xref and create a Pixmap from the document and that xref.
  3. If the image uses a color space that is not RGB (like CMYK), we convert it to RGB for standard PNG output.
  4. pix.save() writes the image to a file. The format is inferred from the file extension; you can also pass a format string.
  5. Setting pix = None frees the underlying memory, which is important when processing many images.

Note that get_images() returns only images that are embedded in the page's resources. Images that are only referenced via annotations or as page backgrounds may not appear here. To capture those, you would need to inspect the page's /Resources dictionary directly, which is more advanced.

Combining Text and Image Extraction in One Pass

Often you want both the text and the images from a page, and you need to know where each image appears relative to the text. The "blocks" mode gives you this directly because image blocks have block_type == 1 and include the image's bounding box. You can iterate over blocks and handle text and image blocks differently:

page = doc[0] blocks = page.get_text("blocks") for block in blocks: x0, y0, x1, y1, content, block_no, block_type = block if block_type == 0: print(f"Text: {content}") elif block_type == 1: # content is an image xref in this case xref = content pix = fitz.Pixmap(doc, xref) if pix.n - pix.alpha >= 4: pix = fitz.Pixmap(fitz.csRGB, pix) pix.save(f"block_{block_no}.png") pix = None

In the "blocks" tuple, for image blocks, the content field is the image's xref number, not the image bytes. This lets you extract the image without calling get_images() separately. However, get_images() is still useful when you want to list all images on a page regardless of their block association.

If you need to map images to specific text regions, the bounding box coordinates from the block tuple give you the exact placement. You can then use those coordinates to associate an image with nearby text blocks.

Performance and Memory Considerations

PyMuPDF is a C-based library, so it is generally fast for text and image extraction. However, when working with large PDFs or documents with many high-resolution images, memory usage can become a concern if you are not careful.

Each Pixmap holds the decoded image data in memory. For a 300 DPI full-page image, that can be several megabytes or more. If you extract many images without releasing the pixmaps, memory will grow quickly. The pattern of setting pix = None after saving is a simple way to allow garbage collection. Alternatively, you can use del pix and call gc.collect() if you are in a tight loop, but that is rarely necessary.

For text extraction, get_text() returns strings, lists, or dicts, which are also held in memory. If you are processing a document with thousands of pages and you only need a summary, consider processing page by page and discarding the extracted data after you have used it, rather than accumulating everything in a list.

Another factor to keep in mind is the full=True parameter in get_images(). Use it when you want the complete set of image references for a page. If the same image appears more than once, deduplicate by xref before saving to avoid repeated writes.

Common Pitfalls and Edge Cases

Password-Protected PDFs

If you open a PDF that requires a password, doc.needs_pass will be True. You must call doc.authenticate(password) before accessing pages. If the password is incorrect, authenticate() returns False and page access will raise an exception.

Scanned PDFs

PyMuPDF extracts text from the embedded text layer. If the PDF is a scanned document with no OCR text, get_text() will return an empty string. In that case, you need an OCR tool like Tesseract or a dedicated OCR library. PyMuPDF itself does not perform OCR.

Image Formats and Transparency

Pixmap.save() infers the output format from the file extension. For PNG, it preserves alpha transparency if the original image has an alpha channel. If you want to force a format regardless of extension, pass the format string as the second argument, e.g., pix.save("image", "png").

Coordinate System

PyMuPDF uses a coordinate system where the origin is at the top-left corner of the page, with y increasing downward. This is the same as most PDF viewers, but it differs from the bottom-left origin used in some PDF specifications. When you use bounding boxes from get_text("blocks"), they are in this top-left system.

Duplicate Images

The same image may be referenced on multiple pages or multiple times on one page. If you want to avoid saving duplicate files, track seen xrefs in a set and skip any xref you have already processed.

When to Choose PyMuPDF Over Other Libraries

PyMuPDF is a strong choice when you need both text and image extraction in one library, and when you need fine-grained control over layout and image data. It is often faster than pure-Python libraries like PyPDF2 for many operations because it is backed by C code. However, it has a steeper learning curve than simpler libraries like pdfplumber for basic text extraction, and its API is more complex.

If your primary need is simple text extraction from well-structured PDFs, pdfplumber or even PyPDF2 may be sufficient. But if you need to extract images, handle complex layouts, or process large documents efficiently, PyMuPDF is worth the extra complexity. It also supports rendering pages to images, which is useful for thumbnails or previews.

For a one-off script where you need to quickly pull text and images from a few PDFs, PyMuPDF's combined API lets you do both without pulling in multiple dependencies. The ability to get image blocks directly from get_text("blocks") is a practical advantage that simplifies the task of associating images with their surrounding text.

Extract Text and Images from PDFs Using PyMuPDF in Python | RYUSLOG DEV