地平線まで行ってくる。

記録あるいは忘備録。時には検討事項。

メモ:表交じりのPDFからMarkdownに変換してみる。

表形式が混在したPDFをMarkdown形式へと変換にトライしたのでメモです。前提としてはすでにPDFにテキストが埋め込まれているものを対象とします。拾い物のPDFでお試ししたので変換後の結果は割愛しています。問題のないPDFで試したら追記したいと思います。Microsoftが公開しているMarkItDownとPyMuPDFとPyMuPDF4LLM、markdropで試してみました。

 

結果としては、PyMuPDF4LLMがお手軽で有料モデルを利用しなくとも表交じりも頑張って抽出します。さらにmarkdropではmicrosoftのtable-transformerを利用している効果もあって、表のコラムが良く取れています。文書によって使い分けることになりそうです。MarkItDownではLLMを指定してみましたが、テキストがすでに埋め込まれたPDFでは利用されてないようで結果に変化はありませんでした。Markitdownから呼び出す利点はないかもしれませんが、有料ですがAzure AI Document Intelligenceと併用するのがよいのでしょうか。

 

また、markdropはLLMモデルを使って図や表の説明文を生成ができるのは魅力です。図の切り出しや数式も一定の精度があります。

 

# markdropを追記

 

 

MarkItDown

llm設定なし

from markitdown import MarkItDown

md = MarkItDown(enable_plugins=False) # Set to True to enable plugins
result = md.convert("/content/pdf_sample-ja.pdf")
print(result.text_content)

llm設定あり

from markitdown import MarkItDown
from openai import OpenAI

client = OpenAI()
md = MarkItDown(llm_client=client, llm_model="gpt-4o", preserve_layout=True)
result = md.convert("/content/pdf_sample-ja.pdf")
print(result.markdown)

 

簡単に実装できるのは助かります。しかし、どうも、テキスト埋め込み済みのPDFの場合、llmは利用しておらず、表を認知できていない。残念。

 

PyMuPDF

import fitz  # PyMuPDF
import pandas as pd

def pdf_to_markdown_with_tables(pdf_path):
    """
    表を含むPDFをMarkdown形式に変換する関数

    Args:
        pdf_path (str): PDFファイルへのパス

    Returns:
        str: Markdown形式の文字列
    """

    doc = fitz.open(pdf_path)
    markdown_text = ""

    for page in doc:
        # ページ内のテキストと表を抽出
        text_blocks = page.get_text("blocks")  # テキストブロックを取得
        tables = page.find_tables()  # 表を検出

        # 表の領域と重なるテキストブロックを削除
        text_blocks_to_keep = []
        for block in text_blocks:
            block_bbox = fitz.Rect(block[:4])  # テキストブロックの範囲を取得
            overlap = False
            for table in tables:
                if block_bbox.intersects(table.bbox):  # 表と重なっているか判定
                    overlap = True
                    break
            if not overlap:  # 重なっていない場合のみ保持
                text_blocks_to_keep.append(block)

        # テキストブロックと表を位置情報でソート
        all_elements = text_blocks_to_keep + [(table.bbox, table) for table in tables]
        all_elements.sort(key=lambda x: (x[0][1], x[0][0]) if isinstance(x[0], tuple) else (x[1], x[0]))

        # Markdownテキストを生成
        for element in all_elements:
            if isinstance(element[0], tuple):  # 表の場合
                table = element[1]
                table_data = table.extract()
                df = pd.DataFrame(table_data)
                markdown_text += df.to_markdown(index=False) + "\n\n"
            else:  # テキストブロックの場合
                markdown_text += element[4] + "\n"  # テキストブロックのテキストを追加

    return markdown_text

# 使用例
pdf_path = "/content/pdf_sample-ja.pdf"  # PDFファイルへのパスを指定
markdown_output = pdf_to_markdown_with_tables(pdf_path)

print(markdown_output)  # Markdown形式のテキストを出力

個別に表を抽出できるようなので、それを利用する。表の抽出と文字の抽出は別々に行われるようなので、表のテキストブロックを表に置き換えます。そうすると、テキスト中にMarkitdownの表を埋め込めました。

pymupdf4llm

import pymupdf4llm

def pdf_to_markdown_file(pdf_path, output_md_path):
    # to_markdown 関数を直接呼び出してPDFからMarkdownテキストを生成
    md_text = pymupdf4llm.to_markdown(pdf_path, write_images=True)
    print(md_text) # 変換状況を表示

    with open(output_md_path, "w", encoding="utf-8") as f:
        f.write(md_text)
    
    print(f"Markdown file saved to {output_md_path}")

# 使用例
pdf_to_markdown_file("/content/pdf_sample-ja.pdf", "output.md")

LLM用として開発されているだけあって表や見出しのつけ方も一番良かったです。論文にありがちな2段組みも流れに沿って抽出できる。画像もちょっと怪しいところもあるけれど、別途抽出してくれるのは良好。

 

markdrop

!pip install markdrop

from markdrop import extract_images, make_markdown, extract_tables_from_pdf

source_pdf = '/content/pdf_sample-ja.pdf'    # Replace with your local PDF file path or a URL
output_dir = './'                 # Replace with desired output directory's path

make_markdown(source_pdf, output_dir)
extract_images(source_pdf, output_dir)
extract_tables_from_pdf(source_pdf, output_dir=output_dir)

表の表記だけではなく、図の切り出しや数式も可能な範囲で良い感じで加工されます。まだ発展途中の様子です。