pythonでpdfから画像と表を取り出し、レポート作成の負担を減らす

##背景
レポートをかく際にpdf形式で送られてきたpdfから画像(回路図など)はクリップし保存、表はひたすら写すをやるのがめんどくさくなった。
それらをやってくれる便利なアプリ、コードは簡単に調べた感じではでてこなかった。じゃあ作ろうということで。
表の抽出はうまくいかなかったけど値は取得できたから負担は減ったということで(;^_^A

gitにも乗せたので良ければ
https://github.com/kzrn3318/create_img_excel_from_pdf
##環境
+ os：windows10
+ python：version3.8で作成

必要なライブラリのインストール

```

pip install pypdf2
pip install pillow
pip install PyMuPDF
pip install fitz
pip install pandas
pip install camelot-py[cv]
```

実行時ghostscriptがないとエラーが起きるかもしれませんが、その場合ghostscriptをインストールしてください。
コード作成時はwindows10なのでpathの文字列の関係でほかosでは動かないことがあるかもしれません、その場合コードのpathを適応できるように書き換えてください。他osでは動作確認していません。

##コード本体
以下コードです

```python:main.py
import PyPDF2
from PIL import Image
import sys,os
import glob
import fitz
import camelot
import pandas as pd

def create_dir(img_dir , pdf_dir , excel_dir):
    img_dir_glob = glob.glob(str(img_dir))
    pdf_dir_glob = glob.glob(str(pdf_dir))
    excel_dir_glob = glob.glob(str(excel_dir))
    
    if len(pdf_dir_glob) > 0:
        pass
    else:
       os.mkdir(str(pdf_dir))
       
    if len(img_dir_glob) > 0:
        pass
    else:
        os.mkdir(str(img_dir))
    
    if len(excel_dir_glob) > 0:
        pass
    else:
        os.mkdir(str(excel_dir))
        

def create_page_pdf(pdf,page_count,pdf_dir):
    pdf_writer = PyPDF2.PdfFileWriter()
    pdf_writer.addPage(pdf.getPage(page_count))
    
    with open(".\\"+str(pdf_dir)+"\pdf{}.pdf".format(page_count),"wb") as f:
        pdf_writer.write(f)
    

def create_png(pdf_path,page_count,img_dir):
    pdf  = fitz.open(pdf_path)
    for num in range(len(pdf)):
        num_count = 0
        for image in pdf.getPageImageList(num):
            num_count += 1
            xref = image[0]
            pix = fitz.Pixmap(pdf,xref)
            
            if pix.n < 5:
                pix.writePNG(".\\"+str(img_dir)+"\img{}_{}.png".format(page_count,num_count))
            else:
                pix = fitz.Pixmap(fitz.csRGB,xref)
                pix.writePNG(".\\"+str(img_dir)+"\img{}_{}.png".format(page_count,num_count))
            
            pix = None
    
    pdf.close()
    
    
def create_excel(pdf_path,excel_dir,data_count):
    
    datas = camelot.read_pdf(pdf_path,split_text=True)
    data_count = data_count
    for data in datas:
        data_count += 1
        df =  data.df
        with pd.ExcelWriter(".\\"+str(excel_dir)+"\\from_pdf_{}.xlsx".format(data_count)) as file:
            df.to_excel(file,sheet_name="sheet1",index=False,header=False)
    return data_count


if __name__ == "__main__":
    args = sys.argv
    print([i for i in args])
    if len(args) >= 5:
        print("引数を受け取りました。")
        pdf_file = args[1]
        pdf_dir = args[2]
        img_dir = args[3]
        excel_dir = args[4]
    else:
        try:
            pdf_file = args[1]
            print("引数に指定がなかったため、デフォルト値で実行します。")
        except:
            raise ValueError("少なくとも一つのpdfのfileを引数とする必要があります。出力ディレクトリを指定する場合4つの引数を指定します。")
        pdf_dir ="pdf_list"
        img_dir="img_list"
        excel_dir="excel_data"
    
    pdf = PyPDF2.PdfFileReader(pdf_file)
    
    print("画像のディレクトリ:"+str(img_dir))
    print("pdf各ページのディレクトリ:"+str(pdf_dir))
    print("エクセルのデータディレクトリ:"+str(excel_dir))
    
    create_dir(img_dir,pdf_dir,excel_dir)
    
    page_count = 0
    for page in pdf.pages:
        create_page_pdf(pdf,page_count,pdf_dir)
        page_count += 1
        
    path_list = glob.glob(".\\"+pdf_dir+"\*.pdf")
    page_count = 0
    data_count = 0
    for path in path_list:
        page_count += 1
        create_png(path,page_count,img_dir)
        data_count = create_excel(path,excel_dir,data_count)
        
    print("処理終了\n")
```

実行方法は以下のようになります、対象となるpdfと同じディレクトリ内で実行してください。
実行時にはpdfをページ分割したものをそれぞれ保存するディレクトリとpdfからimageを抽出したものを保存するディレクトリ、pdfから表を抽出し保存するディレクトリが作成されます。なお、それらはコマンドライン引数にて指定できます。

```
python main.py (対象.pdf) (pdfページ分割ディレクトリ) (pdf抽出imageディレクトリ) (pdf表抽出ディレクトリ)
```
```:例
python main.py train1.pdf pdf_dir img_dir excel_dir
```
上記の例ではpdf_dir直下にページ分割ごとにpdf保存する。img_dirに抽出したimageを保存する。excel_dirに抽出した表をexcelに変換したものを保存する。
##コードの部分解説
```python
import PyPDF2
from PIL import Image
import sys,os
import glob
import fitz
import camelot
import pandas as pd
```
見た通りですね、pythonを普段書いている人は良く見る光景ですね。各パッケージのインポートを行います。

```python
def create_dir(img_dir , pdf_dir , excel_dir):
    img_dir_glob = glob.glob(str(img_dir))
    pdf_dir_glob = glob.glob(str(pdf_dir))
    excel_dir_glob = glob.glob(str(excel_dir))

    if len(pdf_dir_glob) > 0:
        pass
    else:
       os.mkdir(str(pdf_dir))

    if len(img_dir_glob) > 0:
        pass
    else:
        os.mkdir(str(img_dir))

    if len(excel_dir_glob) > 0:
        pass
    else:
        os.mkdir(str(excel_dir))
```
ディレクトリの作成関数です、引数で受け取ったものがすでに存在しているかいないかを判断し、なければ作成します。

```python
def create_page_pdf(pdf,page_count,pdf_dir):
    pdf_writer = PyPDF2.PdfFileWriter()
    pdf_writer.addPage(pdf.getPage(page_count))

    with open(".\\"+str(pdf_dir)+"\pdf{}.pdf".format(page_count),"wb") as f:
        pdf_writer.write(f)
```
元となるpdfをページ分割し、それぞれを保存する関数です。pdf_dir直下にpdf(ページ番号).pdfを作成します。

```python
def create_png(pdf_path,page_count,img_dir):
    pdf  = fitz.open(pdf_path)
    for num in range(len(pdf)):
        num_count = 0
        for image in pdf.getPageImageList(num):
            num_count += 1
            xref = image[0]
            pix = fitz.Pixmap(pdf,xref)

            if pix.n < 5:
                pix.writePNG(".\\"+str(img_dir)+"\img{}_{}.png".format(page_count,num_count))
            else:
                pix = fitz.Pixmap(fitz.csRGB,xref)
                pix.writePNG(".\\"+str(img_dir)+"\img{}_{}.png".format(page_count,num_count))

            pix = None

    pdf.close()
```
img_dir直下に抽出したimageを.png形式で保存する。ファイル名はimg(ページ番号)_(ページにおけるimage番号).pngとなります。

```python
def create_excel(pdf_path,excel_dir,data_count):

    datas = camelot.read_pdf(pdf_path,split_text=True)
    data_count = data_count
    for data in datas:
        data_count += 1
        df =  data.df
        with pd.ExcelWriter(".\\"+str(excel_dir)+"\\from_pdf_{}.xlsx".format(data_count)) as file:
            df.to_excel(file,sheet_name="sheet1",index=False,header=False)
    return data_count
```
excel_dir直下にexcelに変換したものを保存する。

```python
def create_excel(pdf_path,excel_dir,data_count):

    datas = camelot.read_pdf(pdf_path,split_text=True)
    data_count = data_count
    for data in datas:
        data_count += 1
        df =  data.df
        with pd.ExcelWriter(".\\"+str(excel_dir)+"\\from_pdf_{}.xlsx".format(data_count)) as file:
            df.to_excel(file,sheet_name="sheet1",index=False,header=False)
    return data_count


if __name__ == "__main__":
    args = sys.argv
    print([i for i in args])
    if len(args) >= 5:
        print("引数を受け取りました。")
        pdf_file = args[1]
        pdf_dir = args[2]
        img_dir = args[3]
        excel_dir = args[4]
    else:
        try:
            pdf_file = args[1]
            print("引数に指定がなかったため、デフォルト値で実行します。")
        except:
            raise ValueError("少なくとも一つのpdfのfileを引数とする必要があります。出力ディレクトリを指定する場合4つの引数を指定します。")
        pdf_dir ="pdf_list"
        img_dir="img_list"
        excel_dir="excel_data"

    pdf = PyPDF2.PdfFileReader(pdf_file)

    print("画像のディレクトリ:"+str(img_dir))
    print("pdf各ページのディレクトリ:"+str(pdf_dir))
    print("エクセルのデータディレクトリ:"+str(excel_dir))

    create_dir(img_dir,pdf_dir,excel_dir)

    page_count = 0
    for page in pdf.pages:
        create_page_pdf(pdf,page_count,pdf_dir)
        page_count += 1

    path_list = glob.glob(".\\"+pdf_dir+"\*.pdf")
    page_count = 0
    data_count = 0
    for path in path_list:
        page_count += 1
        create_png(path,page_count,img_dir)
        data_count = create_excel(path,excel_dir,data_count)

    print("処理終了\n")
```
main.pyの実行部です。コマンドライン引数から各ディレクトリ名を取得し、実行がされます。
##まとめ
今まではpdfからせこせこトリミングしたりしてたんですがかなり簡単になったんではないでしょうか。
pyPDF2やcamelotはきじがとても少なく大変でしたね(-_-;)
表の抽出はまだ改善点がありそうですが、pdfの構造や書き方等によりそのままの抽出は難しそうですね。
なおこのコードはエクスポートされたpdfを前提に作成してます。adobescan等でscanしたpdfの指導書は適用できるか試しておりませんのでお気を付けて。