0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

tabula-pyでモバイルSuicaの利用履歴PDFをpandas DataFrame形式にする

Last updated at Posted at 2020-05-04

「モバイルSuicaの利用履歴を経費精算に簡単に出せるように編集するサービスの作成」
前回はDockerでの実行環境を作りました
完成品はこちら https://www.mobilesuica.work

動作環境

Windowsで環境を用意するのが面倒なのでDockerでPythonが動くコンテナを立てて動かしてます

#tabula-py
tabula-py
まずは簡単にこうしてみた

test.py
import tabula
import pandas as pd
df = tabula.read_pdf('a.pdf')
print(df.head())

残念ながらエラーとなった

AttributeError: 'list' object has no attribute 'head'

どうやら配列で返ってきてるっぽい
であればこうやればいいのかな?

test.py
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ページになります。

test.py
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を使います。

test.py
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ファイルまとめてというもののようなのでちょっと使いにくい。
(利用履歴を一つしか持っていなかったので、同じファイルを何度も読む形で)

test.py
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のオプションもそのまま渡せる優れものです。
最終的には以下のような形となりました。

test.py
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に落とせてしまうことが出来るのはすごい。

0
3
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
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?