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.
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:
| Option | Return type | Description |
|---|---|---|
"text" | str | Plain text with line breaks. |
"blocks" | list of tuples | Each block is a tuple with coordinates, text, block type, and block number. |
"dict" | dict | A nested dictionary with page-level structure. |
"words" | list of tuples | Each 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:
page.get_images(full=True)returns all images referenced on the page, including those nested inside Form XObjects.- For each image, we get its
xrefand create aPixmapfrom the document and that xref. - If the image uses a color space that is not RGB (like CMYK), we convert it to RGB for standard PNG output.
pix.save()writes the image to a file. The format is inferred from the file extension; you can also pass a format string.- Setting
pix = Nonefrees 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.