python の PyMuPDF で PDF から取得したテキスト要素の位置情報を使って、指定範囲のテキスト要素のみを抽出します。
やりたいこと
PDF からテキストを抽出する際、ヘッダ、フッタ、ページ番号などを除外して、本文だけを抽出できるようにします。
ここでは、本文領域は何らかの方法で取得済とします。
PDF の座標
PDF の座標は左下が基点で x軸は右向き、y軸は上向きです。
一方、PyMuPDF では左上が基点で x軸は右向き、y軸は下向きです。
https://pymupdf.readthedocs.io/ja/latest/app3.html#coordinates
プログラム
本体
「python で2つの PDF のテキストの差分を検出」で PyMuPDF を使用してテキストを抽出しました。
ここでは、このプログラムを流用して、PDF の範囲を指定してテキストを抽出します。
コンストラクタの引数に x_min y_min, x_min, x_max が指定されている場合には、get_text_objs_from_page() で指定範囲内のテキストのみを抽出します。
import sys
import re
import fitz
from pdf_text_matcher import PdfTextMatcher
class PdfRangeTextMatcher(PdfTextMatcher):
def __init__(self, pdf1, pdf2,
x_min=None, y_min=None, x_max=None, y_max=None):
# 範囲指定がある場合
self.x_min = x_min
self.y_min = y_min
self.x_max = x_max
self.y_max = y_max
self.range_flag = False
if x_min != None and y_min != None and x_max != None and y_max != None:
self.range_flag = True
super().__init__(pdf1, pdf2)
def get_text_objs_from_page(self, pdf, page_id):
page = pdf[page_id]
blocks = page.get_text('blocks')
text_objs = []
for i, obj in enumerate(blocks):
if self.range_flag:
# PDF は左下が基点だが PyMuPDF では左上が基点
left = obj[0]
top = obj[1]
right = obj[2]
bottom = obj[3]
if not ((self.x_min <= left and right <= self.x_max)
and (self.y_min <= top and bottom <= self.y_max)):
continue
text = obj[4]
text = self.re_strip.sub('', text)
if text == '': continue
id = f"id_{page_id}_{i}"
text_obj = {'id': id, 'page': page_id, 'text': text, 'obj': obj}
text_objs.append(text_obj)
return text_objs
呼び出し元プログラム例
import sys
import fitz
sys.path.append('lib')
from lib.pdf_range_text_matcher import PdfRangeTextMatcher
pdf1 = fitz.open(sys.argv[1])
pdf2 = fitz.open(sys.argv[1])
# 範囲指定なし
m = PdfRangeTextMatcher(pdf1, pdf2)
text_objs = m.get_text_objs_from_page(pdf1, 0)
print(text_objs)
# 範囲指定あり
range_m = PdfRangeTextMatcher(pdf1, pdf2, x_min=80, y_min=70, x_max=500, y_max=720)
range_text_objs = range_m.get_text_objs_from_page(pdf1, 0)
print(range_text_objs)
テキスト抽出元の PDF のイメージ
Word でヘッダに「ヘッダ」、フッタに「フッタ」、右下にページ番号が入った PDF を作成しました。
ヘッダ
あああああ
いいいいい
ううううう
えええええ
おおおおお
かかかかか
ききききき
くくくくく
けけけけけ
こここここ
さささささ
1
フッタ
実行結果
※見やすさのため加工しています。
範囲指定がない場合には、ヘッダ、フッタ、ページ番号を含むテキストが抽出されます。
ヘッダ、フッタ、ページ番号はページ内の位置にかかわらず、先頭の方に出力されました。
[{'id': 'id_0_0', 'page': 0, 'text': 'ヘッダ', 'obj': (85.08000183105469, 44.57242965698242, 119.39065551757812, 58.153072357177734, ...)},
{'id': 'id_0_2', 'page': 0, 'text': '1', 'obj': (504.5999755859375, 759.1724243164062, 512.9320678710938, 772.7530517578125, ...)},
{'id': 'id_0_3', 'page': 0, 'text': 'フッタ', 'obj': (85.08000183105469, 776.6923828125, 119.39065551757812, 790.2730102539062, ...)},
{'id': 'id_0_4', 'page': 0, 'text': 'あああああ', 'obj': (85.08000183105469, 101.45242309570312, 140.4058074951172, 115.03306579589844, ...)},
...,
{'id': 'id_0_37', 'page': 0, 'text': 'ししししし', 'obj': (85.08000183105469, 695.4524536132812, 140.4058074951172, 709.0330810546875, ...)}]
範囲指定がある場合にはヘッダ、フッタ、ページ番号以外のテキストが抽出されています。
[{'id': 'id_0_4', 'page': 0, 'text': 'あああああ', 'obj': (85.08000183105469, 101.45242309570312, 140.4058074951172, 115.03306579589844, ...)},
{'id': 'id_0_7', 'page': 0, 'text': 'いいいいい', 'obj': (85.08000183105469, 155.45242309570312, 140.4058074951172, 169.03306579589844, ...)},
...,
{'id': 'id_0_37', 'page': 0, 'text': 'ししししし', 'obj': (85.08000183105469, 695.4524536132812, 140.4058074951172, 709.0330810546875, ...)}]