「モバイルSuicaの利用履歴を経費精算に簡単に出せるように編集するサービスの作成」
前回はDockerでの実行環境を作りました
完成品はこちら https://www.mobilesuica.work
動作環境
Windowsで環境を用意するのが面倒なのでDockerでPythonが動くコンテナを立てて動かしてます
#tabula-py
tabula-py
まずは簡単にこうしてみた
import tabula
import pandas as pd
df = tabula.read_pdf('a.pdf')
print(df.head())
残念ながらエラーとなった
AttributeError: 'list' object has no attribute 'head'
どうやら配列で返ってきてるっぽい
であればこうやればいいのかな?
df = tabula.read_pdf('a.pdf')
print(df[0].head())
出来ました(結果の駅名は変えてます)
月 日 種別 利用駅 種別.1 利用駅.1 残額 差額
0 2 6 繰 NaN NaN NaN \9,753 NaN
2 2 7 入 東京 出 横浜 \9,573 -180
3 2 7 入 横浜 出 東京 \9,393 -180
複数ページまとめて
デフォルトだと最初のページだけなのでpages='all'を付けて2ページまとめていきます
モバイルSuicaの利用履歴は最大100件で繰り延べを入れると101件あり、PDFにすると最大2ページになります。
df = tabula.read_pdf('a.pdf',pages='all')
for i in range(len(df)):
print(f"{i+1}ページには{len(df[i])}行あります")
実行結果
(app-root) bash-4.2# python3 test.py
1ページには51行あります
2ページには50行あります
ちゃんと1ページ目は「繰」(繰り延べ)分が1行多いのが見て取れます。
ばらばらだと扱いにくいので一つのDataFrmaeにします。concatを使います。
df = tabula.read_pdf('a.pdf',pages='all')
d = pd.concat(df,ignore_index=True)
print(f"{len(d)}行あります")
実行結果
(app-root) bash-4.2# python3 test.py
101行あります
ignore_index=Trueですが、これを入れないとindexがちゃんと通番になりません。
比較してみます。
ignore_index=Trueなし
1ページ目と2ページ目の境目でindexが0に戻る
50 3 4 入 東京 出 横浜 \9,443 -180
0 3 5 現金 NaN NaN NaN \9,543 100
ignore_index=Trueあり
そのまま通番になっている
50 3 4 入 東京 出 横浜 \9,443 -180
51 3 5 現金 NaN NaN NaN \9,543 100
ここまで行くと複数ファイルでも同様に出来る。
tabula-pyにconvert_into_by_batchとうのもありますが、ディレクトリを指定してその下のPDFファイルまとめてというもののようなのでちょっと使いにくい。
(利用履歴を一つしか持っていなかったので、同じファイルを何度も読む形で)
fileList = ['a.pdf','a.pdf','a.pdf']
dfList = []
for fileName in fileList:
df = tabula.read_pdf(fileName,pages='all')
for i in range(len(df)):
dfList.append(df[i])
d = pd.concat(dfList,ignore_index=True)
print(f"{len(d)}行あります")
実行結果
(app-root) bash-4.2# python3 test.py
303行あります
ここまでやって1ページ目と2ページ目でちょっと違いがあることに気が付きました。
1ページ目のチャージ部分
24 2 21 現金 NaN NaN NaN \9,883 +100
2ページ目のチャージ部分
78 3 21 現金 NaN NaN NaN \10,317 100
2ページ目には"+"がありません。データ処理的には特に問題なく出来るのですが気持ち悪い。
調べていくと数字として認識しているか(2ページ目)、文字として認識しているか(1ページ目)の違いでした。
14 2 15 バス等 OKK NaN NaN \9,103 -2,000
3桁を超えるとThousand Separator(,)が入ってしまうためそのページは全て文字として認識されるようです。
2ページはそれが無かったため(全て3桁以下)数字として認識されていました。
文字としてそのまま取ってしまう方が便利なのでpandasで調べると、**dtype='object'**とすればよいことがわかりました。
tabula-pyはpandasのオプションもそのまま渡せる優れものです。
最終的には以下のような形となりました。
fileList = ['a.pdf','a.pdf','a.pdf']
dfList = []
for fileName in fileList:
df = tabula.read_pdf(fileName,pages='all',pandas_options={'dtype':'object'})
for i in range(len(df)):
dfList.append(df[i])
d = pd.concat(dfList,ignore_index=True)
わずかこれだけでPDFの表をDataFrameに落とせてしまうことが出来るのはすごい。