Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz

加入好友
加入社群
Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

這份教學以一份可重現的示範 PDF 為基礎:

– 示範 PDF:`D:\Temp\fitz_demo_tutorial.pdf`

這份 PDF 已先建立好,內容包含:

– 一般文字

– 不同 font 的 heading / body

– 同一行內混用不同 font 的 spans

– 一個用線條畫出的簡單表格,可用 `page.find_tables()` 示範

Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

目標是把幾個最重要的觀念講清楚:

– `fitz.open()` 怎麼讀 PDF

– `page.get_text(“blocks”)`、`page.get_text(“dict”)` 差在哪裡

– 文字層級中的 `block -> line -> span`

– 怎麼抓 `font / size / flags / color`

– 怎麼用 `find_tables()` 抓表格

– 怎麼把結果整理成 DataFrame,方便在 Jupyter 驗證

先記住一個很重要的點:

– 安裝時的套件名稱是 `PyMuPDF`

– import 時的模組名稱是 `fitz`

也就是:

“`powershell

pip install PyMuPDF

“`

但程式裡要寫:

“`python

import fitz

“`

這兩個名稱不一樣是正常的,不是你裝錯套件。

## 1. 先用 fitz 把內容寫進 PDF

PyMuPDF 不只是拿來讀 PDF,也可以直接把文字和線條畫進 PDF。

它的思路比較像「在指定座標作畫」,不是像 Word 那樣自動流式排版。

最常用的幾個 API 是:

– `doc = fitz.open()`:建立新 PDF

– `page = doc.new_page()`:新增頁面

– `page.insert_text((x, y), text, …)`:把文字畫到指定座標

– `page.draw_line((x0, y0), (x1, y1))`:畫線

– `fitz.get_text_length(text, fontname=…, fontsize=…)`:計算一段文字大概會佔多寬

– `doc.save(path)`:存檔

### 1.0 先理解:同一 line 有多個 spans 時,通常要自己排

如果同一 line 裡要混用不同 font / size,通常不是一次 `insert_text()` 就全部搞定,而是:

– 先決定這一行共用的 `y` baseline

– 每個 span 各自呼叫一次 `insert_text()`

– 每寫完一段,就把 `x` 往右推到下一段的起點

也就是:

– 同一 `line`:通常共用同一個 `y`

– 不同 `span`:主要改 `x`

– 新的一行:再把 `y` 往下加固定行距

### 1.0.1 `fitz.get_text_length()` 會幫你算寬度,但不會自動排版

`fitz.get_text_length()` 的角色是:

– 幫你估算某一段文字在指定 font / size 下大概有多寬

它不會自動幫你做:

– 下一個 span 的定位

– 自動換行

– 自動 paragraph 排版

– 自動計算整段 line height

它只是幫你回答:

這段字如果畫上去,大概會有多寬?

所以常見用法是:

page.insert_text((x, y), span_text, fontsize=size, fontname=font)
x += fitz.get_text_length(span_text, fontname=font, fontsize=size)

### 1.0.2 同一 line 插入多個 spans 的最小示範

這段 code 很適合直接在 Jupyter 驗證:

import os
import fitz

output_path = r"D:\Temp\fitz_multi_span_line.pdf"
os.makedirs(os.path.dirname(output_path), exist_ok=True)

doc = fitz.open()
page = doc.new_page(width=595, height=842)

y = 100
x = 50

spans = [
    {"text": "Mixed font: ", "font": "Helvetica", "size": 12},
    {"text": "BOLD_PART", "font": "Helvetica-Bold", "size": 12},
    {"text": " normal tail", "font": "Helvetica", "size": 12},
]

for span in spans:
    page.insert_text(
        (x, y),
        span["text"],
        fontsize=span["size"],
        fontname=span["font"],
    )

    x += fitz.get_text_length(
        span["text"],
        fontname=span["font"],
        fontsize=span["size"],
    )

doc.save(output_path)
doc.close()

print(output_path)

fitz_multi_span_line.pdf:

Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

這段最重要的觀念是:

– `y` 固定,表示它們在同一個 `line`,不是 table 的 `row`

– `x` 每次都往右累加

– 你是靠 `get_text_length()` 手動把下一個 `span` 接上去

### 1.1 寫入最小文字 PDF

這段 code 可以直接在 Jupyter 驗證:

import os
import fitz

output_path = r"D:\Temp\fitz_write_demo.pdf"
os.makedirs(os.path.dirname(output_path), exist_ok=True)

doc = fitz.open()
page = doc.new_page(width=595, height=842)

page.insert_text(
    (50, 60),
    "PyMuPDF Demo Title",
    fontsize=18,
    fontname="Helvetica-Bold",
)

page.insert_text(
    (50, 95),
    "This is a normal line.",
    fontsize=11,
    fontname="Times-Roman",
)

page.insert_text(
    (50, 125),
    "Mixed font: ",
    fontsize=12,
    fontname="Helvetica",
)
page.insert_text(
    (120, 125),
    "BOLD_PART",
    fontsize=12,
    fontname="Helvetica-Bold",
)
page.insert_text(
    (195, 125),
    " normal tail",
    fontsize=12,
    fontname="Helvetica",
)

doc.save(output_path)
doc.close()

print(output_path)

fitz_write_demo.pdf

Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

### 1.2 再讀回來驗證

import fitz

pdf_path = r"D:\Temp\fitz_write_demo.pdf"
doc = fitz.open(pdf_path)
page = doc[0]

print(page.get_text())
doc.close()
Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

### 1.3 用線條加文字做簡單表格

如果你想做一個可以拿來測 `find_tables()` 的簡單 PDF,可以畫格線再放文字:

import os
import fitz

output_path = r"D:\Temp\fitz_write_table_demo.pdf"
os.makedirs(os.path.dirname(output_path), exist_ok=True)

doc = fitz.open()
page = doc.new_page(width=595, height=842)

left = 50
right = 430
top = 120
row_h = 28
col_x = [left, 180, 300, right]
rows = [top + i * row_h for i in range(5)]

for x in col_x:
    page.draw_line((x, rows[0]), (x, rows[-1]), width=1)

for y in rows:
    page.draw_line((left, y), (right, y), width=1)

headers = ["Item", "Class", "Capacity"]
for idx, text in enumerate(headers):
    page.insert_text(
        (col_x[idx] + 8, top + 18),
        text,
        fontsize=11,
        fontname="Helvetica-Bold",
    )

data = [
    ("M", "Module", "16GB"),
    ("R", "RDIMM", "32GB"),
    ("U", "UDIMM", "64GB"),
]

for row_idx, row in enumerate(data, start=1):
    y = top + row_idx * row_h + 18
    for col_idx, value in enumerate(row):
        page.insert_text(
            (col_x[col_idx] + 8, y),
            value,
            fontsize=11,
            fontname="Helvetica",
        )

doc.save(output_path)
doc.close()

print(output_path)

fitz_write_table_demo.pdf

Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

### 1.4 這份教學的示範 PDF也是這樣做出來的

本教學用的 `D:\Temp\fitz_demo_tutorial.pdf`,本質上也是用:

– `insert_text()` 寫文字

– `draw_line()` 畫表格線

先把頁面內容造出來,再回頭示範怎麼用 fitz 把它讀出來。

## 2. 先讀 PDF

最基本的起點:

import fitz

pdf_path = r"D:\Temp\fitz_demo_tutorial.pdf"
doc = fitz.open(pdf_path)

print(len(doc))
doc.close()

這份示範 PDF 的實際結果是:

Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

## 3. 最重要的文字層級觀念

PyMuPDF 在 `dict` 模式下,最常用的層級是:

– `block`

– `line`

– `span`(文字片段)

可以先這樣理解:

– `block`:頁面上的一大塊文字區域

– `line`:block 裡的一行文字

– `span`:同一行裡樣式一致的一小段文字,也可先把它理解成一個文字片段

如果你熟 `python-docx`,可以先用這個類比來記:

– `fitz` 的 `span`,大致相當於 `python-docx` 的 `run`

– 它們都不是整段 paragraph,而是段內一小段、可帶自己樣式的文字

也就是:

page
  -> blocks
    -> lines
      -> spans

這些名字的確不太能直接望文生義,
原因通常不是概念太玄,
而是它們是各自工具鏈裡沿用很久的技術名詞:

– `python-docx` 的 `run`,比較接近「一段連續套用同一格式的文字」

– `fitz` 的 `span`,比較接近「一小段連續、樣式一致的文字範圍」

所以實務上不要硬背字面意思,直接記它們在文件裡扮演的角色會比較快:

– `paragraph` / `line`:比較像你肉眼看到的一整段或一整行

– `run` / `span`:比較像那一段裡面、樣式沒有變的一小截文字

其中 `span` 最重要;如果要補一個中文對照,
這份教學統一把它叫做「文字片段」,
而 font 資訊通常就在這一層。

## 4. `blocks` 模式:快速,但不夠細

如果你只是想先看大致座標與文字內容,可以用:

import fitz
from typing import TypeAlias

BlockTuple: TypeAlias = tuple[float, float, float, float, str, int, int]

pdf_path = r"D:\Temp\fitz_demo_tutorial.pdf"
doc = fitz.open(pdf_path)
page = doc[0]

blocks: list[BlockTuple] = page.get_text("blocks")
for block in blocks[:5]:
    print(block)

doc.close()

這裡不是 `blocks: tuple`,而是:

– `blocks: list[BlockTuple]`

– 每一個 `block` 才是一個 `tuple`

`blocks` 通常會長這樣:
(x0, y0, x1, y1, text, block_no, block_type)

Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

如果把型別攤開來看,就是:

tuple[float, float, float, float, str, int, int]

依序可以讀成:

– 第 1 個 `float`:`x0`,左上角 x

– 第 2 個 `float`:`y0`,左上角 y

– 第 3 個 `float`:`x1`,右下角 x

– 第 4 個 `float`:`y1`,右下角 y

– 第 5 個 `str`:`text`,這個 block 的文字內容

– 第 6 個 `int`:`block_no`,這個 block 在頁面中的編號

– 第 7 個 `int`:`block_type`,block 類型;
常見好用值是 `0 = text`、`1 = image`、`3 = vector`

這個欄位最實用的地方,是你可以很快先做分流:

– `block_type == 0`:保留成文字分析主流程

– `block_type == 1`:代表這個 block 是圖片,
不要拿去做 font / line / span 分析

– `block_type == 3`:代表這個 block 是向量圖形,例如線段或矩形

小提醒:

– 如果你的目標是抽正文、抓 `font`、做 `line / span` 分析,主力幾乎都是 `block_type == 0`

– 如果你的目標是偵測表格結構,就不能只看 `0`,因為表格格線常和 `vector`(`3`)有關

– 不過如果你是用 `page.find_tables()`,通常不需要自己手動處理 `block_type == 3`

用途:

– 快速看頁面有哪些文字區塊

– 先抓大致座標

限制:

– 不容易直接拿到每個細碎文字片段的 `font`

– 不適合做 `is_bold` 判斷

– 不適合做 line 級 / span 級分析

所以,如果你要抓 font,主力通常要換成 `dict`。

## 5. `dict` 模式:抓文字與 fonts 的主力

最常用的做法:

import fitz

pdf_path = r"D:\Temp\fitz_demo_tutorial.pdf"
doc = fitz.open(pdf_path)
page = doc[0]

text_dict = page.get_text("dict")
print(text_dict.keys())

doc.close()
Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

這裡真正重要的是
text_dict[“blocks”]: list[dict]。

Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

這表示 `text_dict[“blocks”]` 裡的每個元素,
本身是一個 block dictionary。
以文字 block 來說,這幾個 key 很重要:

– `number`:這個 block 的編號;
在你直接遍歷原始 `text_dict[“blocks”]` 時,
通常會和 list index 一樣,
但如果你先過濾、重排或只取部分 blocks,
就不要把它當成新的 list index

– `type`:block 類型,文字通常是 `0`

– `bbox`:這個 block 的座標範圍

– `lines`:這個 block 底下的 line dictionaries

Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

你可以這樣數一頁裡有多少 `text block / line / span`:

import fitz

pdf_path = r"D:\Temp\fitz_demo_tutorial.pdf"
doc = fitz.open(pdf_path)

for i, page in enumerate(doc, start=1):
    text_dict = page.get_text("dict")
    text_blocks = [b for b in text_dict.get("blocks", []) if b.get("type") == 0]
    line_count = sum(len(b.get("lines", [])) for b in text_blocks)
    span_count = sum(len(line.get("spans", [])) for b in text_blocks for line in b.get("lines", []))
    print(f"page={i} text_blocks={len(text_blocks)} lines={line_count} spans={span_count}")

doc.close()

這份示範 PDF 的實際結果:

Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

加入好友
加入社群
Python PyMuPDF fitz 教學:從pdf中抓文字、抓 fonts、抓表格; pip install PyMuPDF ; import fitz - 儲蓄保險王

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡

You may also like...

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *