この記事を読むのに必要な時間は 約12分 です。
前回記事はこちらです👇
https://clshinji.com/【python】pdfのテキストを抽出する<第2回>/124/
PDFからテキストを抽出したい
「スシローのアレルギー情報をチェックできるチャットボット」を作成することを目指して、Pythonでの開発にチャレンジしています。
前回はスシローさんのアレルギー情報として公開されている「PDFをデータフレーム形式で読み込む」ことまで成功しましたので、今回は
PDFから読み込んだデータフレームの整形
についてまとめていきます。
PDFをデータフレームとして取得する
さっそくですが、前回作成したtabula-pyでのPDF読込みと、pandasによるデータフレームの整形について、完成したコードがこちらです。
また、処理の手順は次のとおりです。
- 設定値(列名等の固定値)をymlファイルから読み込む
- PDFファイルから表を読み込み&データチェック
- ページ毎のデータフレームを1つにまとめる
- CSV出力する
import tabula
import pandas as pd
import road_yml
# --------------------------------------------
# 1.設定値(列名等の固定値)をymlファイルから読み込む
# --------------------------------------------
# ymlファイルから列名を読み込んでセットする
config = road_yml.yml_list()
# 列名のチェック用
print('pattern1')
print(f'column counts(p.1):{len(config.column_names_pg1)}')
print(f'column names (p.1):{config.column_names_pg1}')
print('pattern2')
print(f'column counts(p.1):{len(config.column_names_pg2)}')
print(f'column names (p.1):{config.column_names_pg2}')
# --------------------------------------------
# 2. PDFファイルから表を読み込み&データチェック
# --------------------------------------------
# PDFファイルから表を読み込む
df = tabula.read_pdf('allergy.pdf', pages='all')
# 読み込まれたデータの確認
print(f'data type(p.1): {type(df[0])}')
# ページ数を取得する
page_counts = len(df)
print(f'page counts:{page_counts}')
# 全ページのカラム数が一致していることを確認する
# p.1のみ31列として読み込まれることが多い?
for i in range(len(df)):
df_org = df[i].copy()
# print(f'column names :{df_org.columns}')
print(f'page No.:{i}, column counts:{len(df_org.columns)}')
# --------------------------------------------
# 3. ページ毎のデータフレームを1つにまとめる
# --------------------------------------------
# 1ページ目だけ先に読み込む
# 1ページ目の表を取得する
df_org = df[0].copy()
print(f"p.1 columns counts = {len(df_org.columns)}")
if len(df[0].columns) == 31:
print("column_names_pg1 apply")
# 1ページ目だけ31ページある場合に実行
# カラム名を設定する
df_org.columns = config.column_names_pg1
# 「'●', '○'」を含まない行を削除する
df_allergy = df_org[df_org[config.column_names_pg1].isin(['●', '○']).any(axis=1)].copy()
# 1ページ目の不要な列を削除する
df_allergy = df_allergy.drop(columns='不要')
else:
print("column_names_pg2 apply")
# カラム名を設定する
df_org.columns = config.column_names_pg2
# 「'●', '○'」を含まない行を削除する
df_allergy = df_org[df_org[config.column_names_pg2].isin(['●', '○']).any(axis=1)].copy()
# 2ページ目以降の表を取得して結合する
for i in range(1, len(df)):
df_org = df[i].copy()
df_org.columns = config.column_names_pg2
# print(f'column names :{df_org.columns}')
print(f'page No.:{i}, column counts:{len(df_org.columns)}')
# 「'●', '○'」を含まない行を削除する
df_org = df_org[df_org[config.column_names_pg2].isin(['●', '○']).any(axis=1)].copy()
# df_allergyにdf_orgを縦方向に連結する
df_allergy = pd.concat([df_allergy, df_org], axis=0, ignore_index=True).copy()
print(f' -> df_allergy shape: {df_allergy.shape}')
# --------------------------------------------
# 4. CSV出力する
# --------------------------------------------
# 作成したdf_allergyをcsv形式で保存する
df_allergy.to_csv('allergy_table.csv')
このコードを実行して、最終的に生成されたデータフレームがこちらです👇
(先頭10行だけ表示しています)
少し見づらいかもしれませんが、しっかりPDFから表形式で読み込むことに成功しました。
CSVデータでも出力できましたので、このデータを使ってお店に行ったときに使えるアプリを組みたいと思います。
ちなみに、最初はチャットボット形式が良いかなと思っていたのですが、表を確認するだけであればチャットボットとして作る必要がないかと思いましたので、単純なWebアプリとして作り上げたいと考えております。
次回は、ついにWebアプリとしてデプロイするまでをまとめたいと思います。
それでは、次回に続きます!
区分 | メニュー名称 | 卵 | 乳成分 | 小麦 | えび | かに | そば | 落花生 | くるみ | アーモンド | あわび | いか | いくら | オレンジ | カシューナッツ | キウイフルーツ | 牛肉 | ごま | さけ | さば | 大豆 | 鶏肉 | バナナ | 豚肉 | まつたけ | もも | やまいも | りんご | ゼラチン | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 寿司 | 青森産生サーモン | ○ | ○ | ○ | ○ | ○ | NaN | NaN | NaN | NaN | NaN | ○ | ○ | NaN | NaN | NaN | ○ | ○ | ● | ○ | ○ | NaN | NaN | ○ | NaN | NaN | NaN | ○ | NaN |
1 | 寿司 | 赤えび | NaN | NaN | ○ | ● | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ○ | NaN | NaN | NaN | ○ | NaN | NaN | ○ | NaN | NaN | NaN | ○ | NaN |
2 | 寿司 | 炙り3貫盛り | ○ | ○ | ○ | ● | ○ | NaN | NaN | NaN | NaN | NaN | ○ | ○ | NaN | NaN | NaN | ○ | ○ | ● | ○ | ○ | NaN | NaN | ○ | NaN | NaN | NaN | ○ | NaN |
3 | 寿司 | 炙り特ネタ大とろレモン | NaN | NaN | ○ | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ○ | NaN | NaN | NaN | NaN | ○ | NaN | ○ | NaN | ○ | NaN | NaN | ○ | NaN | NaN | NaN | ○ | NaN |
4 | 寿司 | 牛タンにぎり | NaN | ○ | ○ | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ● | ○ | NaN | NaN | ○ | ○ | NaN | ○ | NaN | NaN | NaN | ○ | ○ |
5 | 寿司 | 炙りサーモンバジルチーズ | ● | ● | ○ | ○ | ○ | NaN | NaN | NaN | ○ | NaN | ○ | NaN | NaN | ○ | NaN | ○ | NaN | ● | ○ | ● | ○ | ○ | ○ | NaN | NaN | NaN | ● | ○ |
6 | 寿司 | 炙り〆さばレモン | NaN | NaN | ● | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ○ | NaN | NaN | ● | ● | NaN | NaN | ○ | NaN | NaN | NaN | ○ | NaN |
7 | 寿司 | 炙りジャンボとろサーモンレモン | NaN | NaN | ○ | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ○ | NaN | ● | NaN | ○ | NaN | NaN | ○ | NaN | NaN | NaN | ○ | NaN |
8 | 寿司 | 炙り上穴子 | ○ | ○ | ● | ○ | ○ | NaN | ○ | NaN | NaN | NaN | NaN | NaN | ○ | NaN | NaN | ○ | ○ | NaN | ○ | ● | ○ | NaN | ○ | NaN | ○ | NaN | ○ | ○ |
9 | 寿司 | 炙り活〆真鯛レモン | NaN | NaN | ○ | ○ | ○ | NaN | NaN | NaN | NaN | NaN | ○ | NaN | NaN | NaN | NaN | ○ | NaN | ○ | ○ | ○ | NaN | NaN | ○ | NaN | NaN | NaN | ○ | NaN |
おまけ 【設定ファイル config.yml】
上のコードで登場している設定ファイル(config.yml)を参考に掲載しておきます。
PDFの表を読み込んだ結果から、トライ&エラーで列名を作ってみていますので、もしかしたら今後スシローさんのPDFの形式が変わったら使えなくなってしまうかもしれません。
もし更新されたら、その都度修正をしていきます。
column_names_pg1:
- '区分'
- 'メニュー名称'
- '不要'
- '卵'
- '乳成分'
- '小麦'
- 'えび'
- 'かに'
- 'そば'
- '落花生'
- 'くるみ'
- 'アーモンド'
- 'あわび'
- 'いか'
- 'いくら'
- 'オレンジ'
- 'カシューナッツ'
- 'キウイフルーツ'
- '牛肉'
- 'ごま'
- 'さけ'
- 'さば'
- '大豆'
- '鶏肉'
- 'バナナ'
- '豚肉'
- 'まつたけ'
- 'もも'
- 'やまいも'
- 'りんご'
- 'ゼラチン'
column_names_pg2:
- '区分'
- 'メニュー名称'
- '卵'
- '乳成分'
- '小麦'
- 'えび'
- 'かに'
- 'そば'
- '落花生'
- 'くるみ'
- 'アーモンド'
- 'あわび'
- 'いか'
- 'いくら'
- 'オレンジ'
- 'カシューナッツ'
- 'キウイフルーツ'
- '牛肉'
- 'ごま'
- 'さけ'
- 'さば'
- '大豆'
- '鶏肉'
- 'バナナ'
- '豚肉'
- 'まつたけ'
- 'もも'
- 'やまいも'
- 'りんご'
- 'ゼラチン'
コメント