01.5.1
pdfから表を抽出
pd.read_csv の代わりに camelot.read_pdf または tabula.read_pdf で DataFrame として取り込みます。同じ PDF でも Camelot が罫線を見つけられず tables.n が 0 になることがあり、そのときは Java 製の tabula に切り替えると tables[1] に貸借対照表が入ってきます。取り込んだ直後の数値は "138,116" のような文字列なので、replace(",", "") と int で数値に戻してから plt.bar に渡します。読み終えると、PDF の表から「現金及び現金同等物」の 3 時点の値を取り出して棒グラフで比べられるようになります。直感 #
PDF の表は人間には表に見えても、ファイルの中では「座標付きの文字の集まり」でしかなく、行や列という構造は保存されていません。Camelot や tabula はこの文字の座標を調べ、罫線や文字の並びから「ここが列の境目だ」と推定して表を復元します。推定なので、罫線のない決算資料のようなレイアウトでは Camelot が表を 1 つも見つけられないことがあり、その場合は推定方法の違う tabula を試すのが実務の定番です。
camelotを使う場合 #
必要なライブラリをインストール #
Camelot は内部で OpenCV と Ghostscript を使って PDF を画像に起こし、罫線を検出します。そのため Python パッケージの opencv-contrib-python、camelot、tabula-py に加えて、Ghostscript 本体のインストールが必要です。
poetry add opencv-contrib-python camelot tabula-py ghostscript
Ghostscript は Python パッケージではなく OS 側のソフトウェアなので、macOS なら Homebrew、Ubuntu なら apt のように OS ごとに入れ方が違います。手順はCamelot 公式のインストール案内を参照してください。
ghostscriptがインストールされているか確認する #
camelot.read_pdf を呼ぶ前に、Python から Ghostscript の共有ライブラリ gs が見えているかを find_library("gs") で確かめます。ここで None が返ると Camelot は PDF を画像化できず、後の read_pdf が失敗します。
| |
'/usr/local/lib/libgs.dylib'
pdfからテーブルを抽出する #
例として総務省のページで公開されている「政策ごとの予算との対応について」のpdfからテーブルを抽出します。罫線のはっきりした行政文書なので、camelot.read_pdf は tables.n に 1 以上を返し、tables[0].df.head() で先頭 5 行が DataFrame として見えます。
※jupyterbookでエラーが出る場合があるためコメントアウトしています
| |
今度はFLEX LNGという会社の決算情報をパースしてみます。
データはFLEXLNG|Investor Homeで取得したファイルで実行しています。
決算資料は罫線を引かずに空白で列を揃えるレイアウトなので、罫線を手がかりにする Camelot は表を 1 つも見つけられず、tables.n が 0 になります。
| |
パースできたテーブル数 0
tabula-py を使う場合 #
poetry add tabula-pyなどとしてtabulaをインストールしてください。
tabulaはバックグラウンドでtabula-java
を使用していますが、Javaのバージョンが古い場合こちらがエラーになる場合があるようです。
参考文献:subprocess.CalledProcessError While extracting table from PDF using tabula-py
Camelot が 0 件だった同じ PDF を、今度は tabula.read_pdf に pages="all" を付けて全ページ読み込みます。tabula は文字の位置関係から列を推定するので、罫線のない決算資料でも複数の表が返り、2 番目の tables[1] に貸借対照表(ASSETS)が入っています。
| |
| ASSETS | Unnamed: 0 | Unnamed: 1 | Unnamed: 2 | Unnamed: 3 | |
|---|---|---|---|---|---|
| 0 | Current assets | NaN | NaN | NaN | NaN |
| 1 | Cash and cash equivalents | 4 | 138,116 | 144,151 | 128,878 |
| 2 | Restricted cash | 4 | 47 | 56 | 84 |
| 3 | Inventory | NaN | 5,915 | 4,075 | 3,656 |
| 4 | Other current assets | 5 | 12,503 | 8,886 | 25,061 |
パースしたテーブルを可視化する #
tabula が返した表の中身は "138,116" のような桁区切り付きの文字列で、そのまま plt.bar に渡すと数値として扱えません。次のコードは「Cash and cash equivalents」の行から 3 時点の金額を取り出し、カンマを外して int にしてから棒グラフにします。
| |

使いどころと落とし穴 #
Camelot は罫線のある行政文書や帳票に強く、tabula は空白で揃えた財務諸表のようなレイアウトに強いので、tables.n が 0 なら迷わず道具を替えます。どちらを使っても取り込んだ値は文字列のままなので、int や pd.to_numeric への変換と、Unnamed: 0 のような壊れた列名の付け直しが必ず後に続きます。ページをまたぐ表は 2 つの DataFrame に分かれて返るため、pd.concat でつなぐ前に見出し行が重複していないかを head() で確かめます。