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

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

# 動作環境
[Windowsで環境を用意するのが面倒なのでDockerでPythonが動くコンテナを立てて動かしてます](https://qiita.com/legacyworld/items/1c9ac1d3859a321d6e84)

#tabula-py
[tabula-py](https://github.com/chezou/tabula-py)
まずは簡単にこうしてみた

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

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

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

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

```python: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ページになります。


```python: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を使います。

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

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

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