0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

文字(日本語)埋め込みPDFをレイアウトを保持したままエクセル化

Posted at

出発点

企業や官公庁などが、ウェブサイトで公表する各種資料では、PDF形式が使われることが多い。
ただ、文章などはともかく、統計値など数値データは、PDFにされると、再利用に困る・・・
(同時にCSVなどで数値データ部分を公表してくれれば良いが、なかなか広まらない)

一応エクセルでは、データ→データの取得→ファイルから→PDFから、でPDFのテーブルデータを取り込めるが、なかなかに使いにくい(複数ファイルを一括処理するとさらに大変)

そこで、Pythonでうまいこと一括してテーブル部分のデータを取得できないかと考えた。

とりあえずやってみた(その1)

基本方針は、pdfplumberで読込、テーブル部分を検知、テーブル部分のみExcel書き出し

・1ページあたり1テーブルが前提
・1ファイルに複数ページある場合を考慮して、すべて連結
・2ページ目以降の先頭行をヘッダとみなして、除外可能
・列幅を自動制御

    def export_table(file, output_xlsx, header = True):
        tables = []
        with pdfplumber.open(file) as pdf:
            for n, page in enumerate(pdf.pages):
                #テーブルと認識した部分のみ抽出
                table = page.extract_table()
                if table is not None:
                    #1ファイルに複数ページある場合、先頭行をヘッダとみなして除外
                    if not header and n > 0:
                        table.pop(0)
                    tables.extend(table)
        if len(tables) == 0:
            return None
        #Pandasで見やすくなるように処理
        df = pd.DataFrame(tables[1:], columns=tables[0])
        # 空文字列やスペースだけをNaNに変換
        df.replace(r'^\s*$', pd.NA, regex=True, inplace=True)
        # 全列が空の行を削除
        df.dropna(how='all', inplace=True)
        # いったんメモリ上にexcelとして書き出し
        output = io.BytesIO()
        with pd.ExcelWriter(output, engine='openpyxl') as writer:
            df.to_excel(writer, index=False, sheet_name='Sheet1')
        output.seek(0)
        excel_bytes = output.getvalue()
        #改行と列幅調整
        wb = load_workbook(filename=io.BytesIO(excel_bytes))
        ws = wb.active
        if ws is not None:
            for row in ws.iter_rows():
                for cell in row:
                    cell.alignment = Alignment(wrap_text=True)
            # 改行考慮で列幅を調整
            for col_cells in ws.columns:
                max_length = 0
                column = col_cells[0].column
                if column is None:
                    continue
                column_letter = get_column_letter(column)
                for cell in col_cells:
                    if cell.value:
                        lines = str(cell.value).splitlines()
                        longest_line = max(lines, key=len)
                        max_length = max(max_length, len(longest_line))
                adjusted_width = (max_length + 2) * 1.2  # 調整
                ws.column_dimensions[column_letter].width = adjusted_width
        try:
            wb.save(output_xlsx)
        except Exception as ex:
            print(ex)
            return False
        else:
            return True

問題点

pdfplumberは、ものすごく便利で、PDFファイル内の罫線を自動認識し、テーブル(二次元配列)として出力してくれる。

たいていの場合は、これだけで問題ないが、テストを続けていくと、日本語の壁にぶつかった。
一部環境では、日本語部分が空白文字になる問題が出現した。
(例: 2020年1月1日 → 2020 1 1となる)
おそらくCMap絡みと思われる事象が頻発し、残念ならpdfplumberは、この問題に対応していないようだった。

解決案

pdfplumberでは読めない漢字もPopplerでは読める!
Popplerのpdftotextでは、文字の位置も取得できるため、位置の基準の変換は必要であるが、うまく組み合わせると、いけるのでは・・・・と考えたが、かなり時間がかかりそうだったため断念・・・。

あれやこれや試行錯誤中、ふと思いついて、いったんpdftocairoでpdf出力してから、pdfplumberで読み込ませたら、見事に意図したとおり日本語が読み込めた!

とりえずやってみた(その2)

動きとしては、subprocessで(pdftocaito -pdf)を標準出力へ→pdfplumberで読込→以下おなじ
、という感じで

    def cairo_pdf(file:Path):
        #標準出力へ(-)
        cmd = [
            "pdftocairo",
            "-pdf",
            str(file),
            "-",
        ]
        result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        if result.returncode == 0:
            output = result.stdout
            if output:
                # pdfplumberで読み込めるよう変換
                return io.BytesIO(output)
            else:
                raise IOError
        else:
            print("Error:", result.stderr)
            raise Exception

pdfplumberはio.BytesIOを引数にとれるため、そのまま指定すればOK
副産物として、pdfplumberで罫線認識がうまくいかないPDFファイルも、いったん pdftocairo -pdfを介すとうまく読み込める場合があった。

おまけ

よくあるPDFファイル(Excelファイルでテーブル作って、Microsfot print to pdfしたもの等)はほとんどpdfplumberだけで読めるが、一部業務用システムの出力したPDFファイルがかなりの曲者。

なかには縦書きのセル幅を縮めて無理やり横書きにしたり・・・

本件も、一部企業が出すPDFファイルへの対応のために試行錯誤した記録となります。
利用用途としてはデータ分析なため、テーブルレイアウトの保持は絶対条件、かつ、なるべく汎用性ありとなかなかに大変でした。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?