python

【python】PDF から取り出した文字が(cid 12345) だらけだった件のまとめ(原因・検証・最終解)

PDF から取り出した文字が(cid:12345) だらけだった件のまとめ(原因・検証・最終解)

以下のようなコードを書いてpdfplumberを使ってpdfからテキストを抽出したのだけど、ある時に(cid:12345)(cid:12345)(cid:12345)みたいな文字列が出力されるだけで、文字列が全然抽出できなかった。

import pdfplumber
import re

PDF = "XXXXX.pdf"

texts = []
with pdfplumber.open(PDF) as pdf:
    for page in pdf.pages:
        page_text = page.extract_text() or ""
        texts.append(page_text)
text = "\n\n".join(texts)        
text = re.sub(r'\n\s*\n', '\n\n', text)                  # 複数の改行を2つの改行(段落区切り)にまとめる
text = re.sub(r' +', ' ', text)                          # 連続するスペースを1つにまとめる   
text = text.replace(' ', ' ')                           # 全角スペースも半角スペースに変換    
text = re.sub(r'[\u0000-\u001F\u007F-\u009F]', '', text) # 制御文字(非表示文字など)を除去    
text = text.lower()  

with open("check2.txt", "w", encoding="utf-8") as f:
    f.write(text)  

原因

PDF内にCIDフォントと、Unicodeの対応表(ToUnicode CMap)が無い/壊れていること。
対応表がない、壊れていると、Unicodeが復元できずCIDフォントのまま吐き出されてしまう。

CIDフォント:Adobeが開発したフォント形式**「Character IDentifier(CID)」のこと。**

解消に向けて色々調べてみたところ、以下の方法が提案されていたので、検証結果を書いてきます。

❌ 方法1:正規表現で置換

(cid:\d+) を正規表現で拾って chr() 置換してみる

正規表現を使えば、できたということで以下のコードを試してみたが、結局文字化け。

def prune_text(text):
    def replace_cid(match):
        ascii_num = int(match.group(1))
        try:
            return chr(ascii_num)
        except:
            return ''
    cid_pattern = re.compile(r'\(cid:(\d+)\)')
    pruned_text = re.sub(cid_pattern, replace_cid, text)
    return pruned_text

PDF = "XXXXX.pdf"
texts = []
with pdfplumber.open(pdf_path) as pdf:
    for page in pdf.pages:
        page_text = page.extract_text() or ""
        page_text = prune_text(page_text)
        texts.append(page_text)

full_text = '\n'.join(texts)
with open("extracted_text_plumber_pruned.txt", "w", encoding="utf-8-sig") as f:
    f.write(full_text)

❌ 方法2:PyPDF2を使ったテキスト抽出

PyPDF2を使えばいいんじゃないかという案があったので、以下の様にインストールして、テキスト抽出してみたけど、文字化けしてNG。(以下のコード上ではutf-8-sig)にしてるけど、他のものでも同様。

# ライブラリのインストール
pip install PyPDF2
from PyPDF2 import PdfReader

reader = PdfReader("XXXXX.pdf")
texts = [page.extract_text() for page in reader.pages]
full_text = '\n'.join(texts)
with open("extracted_text.txt", "w", encoding="utf-8") as f:
    f.write(full_text)

❌ 方法3:Mupdfを使う

[MuPDF: The ultimate library for managing PDF documents](https://mupdf.com/)


# ライブラリのインストール
pip install pymupdf

import fitz  # pymupdf

PDF = "XXXXX.pdf"
texts = []
with fitz.open(pdf_path) as doc:
    for page in doc:
        page_text = page.get_text() or ""
        texts.append(page_text)

full_text = '\n'.join(texts)
with open("extracted_text_pymupdf.txt", "w", encoding="utf-8") as f:
    f.write(full_text)

❌ 方法4: pdf2docxを使って、PDFをword変換

該当するpdfをpdf2docxを使うと、文字化けではなく、そもそも取り出しができなかった。本当にやっかいだ。このPDF。

# ライブラリのインストール
pip install python-docx
pip install pdf2docx

✅ 方法5:Acrobatを使ってPDFを変換

上記4つを試してみてもダメだったので、Adobe Acrobat(Readerじゃないやつ)を使って、wordに変換する方法を試してみた。

結果、さすがPDFの胴元Adobe。こちらはフォントは変わってしまったものの、テキストとして読む分には変じゃない。

また、一部の表や改行などが、ずれたりする見た目のきれいさはの悪化は一定程度許容できた。

❌ 方法6:Wordを使ってPDFを変換

その後、wordを使ってPDFを変換してみたらどうだろう?と考えて試してみましたが、こちらも文字化けしてしまってNG。

その他

他にもOCRで読み取ることも考慮にいれたけど、こちらは誤変換がいやだったので、今回は却下。

まとめ

「CIDフォント」を「Unicode(文字コード)」に変換する対応表(CMap)がなければ、現時点でのコードでの修正箇所は難しい。

結果、対応は2つ

最終結論- PDFをword等に変換(王道のAdobeソフト)
- OCRで読み取り(精度が懸念)

元データが、壊れていたりする場合は、「大元から根本を断て」という結果になった。

-python