K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

pdfから表を抽出

01.5.1

pdfから表を抽出

最終更新 2020-01-29 読了時間 4 分
まとめ
決算資料や統計表のように 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 が失敗します。

1
2
3
from ctypes.util import find_library

find_library("gs")  # gsが実行可能ならば /usr/local/lib/libgs.dylibなどの表示がされます
'/usr/local/lib/libgs.dylib'

pdfからテーブルを抽出する #

例として総務省のページで公開されている「政策ごとの予算との対応について」のpdfからテーブルを抽出します。罫線のはっきりした行政文書なので、camelot.read_pdf は tables.n に 1 以上を返し、tables[0].df.head() で先頭 5 行が DataFrame として見えます。

※jupyterbookでエラーが出る場合があるためコメントアウトしています

1
2
3
4
5
6
7
8
9
import camelot

# pdfを読み込んでテーブルを抽出
# pdf_name = "000788423.pdf"
# tables = camelot.read_pdf(pdf_name)
# print("パースできたテーブル数", tables.n)

# 先頭5行のみ表示
# tables[0].df.head()

今度はFLEX LNGという会社の決算情報をパースしてみます。 データはFLEXLNG|Investor Homeで取得したファイルで実行しています。 決算資料は罫線を引かずに空白で列を揃えるレイアウトなので、罫線を手がかりにする Camelot は表を 1 つも見つけられず、tables.n が 0 になります。

1
2
3
4
5
6
# pdfを読み込んでテーブルを抽出
pdf_name = "flex-lng-earnings-release-q3-2021.pdf"
tables = camelot.read_pdf(pdf_name)
print("パースできたテーブル数", tables.n)
# 先頭5行のみ表示
# tables[0].df.head()
パースできたテーブル数 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)が入っています。

1
2
3
4
5
from tabula import read_pdf

tables = read_pdf("flex-lng-earnings-release-q3-2021.pdf", pages="all")
data = tables[1]
data.head()

ASSETSUnnamed: 0Unnamed: 1Unnamed: 2Unnamed: 3
0Current assetsNaNNaNNaNNaN
1Cash and cash equivalents4138,116144,151128,878
2Restricted cash4475684
3InventoryNaN5,9154,0753,656
4Other current assets512,5038,88625,061

パースしたテーブルを可視化する #

tabula が返した表の中身は "138,116" のような桁区切り付きの文字列で、そのまま plt.bar に渡すと数値として扱えません。次のコードは「Cash and cash equivalents」の行から 3 時点の金額を取り出し、カンマを外して int にしてから棒グラフにします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import matplotlib.pyplot as plt

d = data.query("ASSETS=='Cash and cash equivalents'").iloc[0][2:]

# パースしたテーブルは文字列になっているので数値に変換する
d = [int(v.replace(",", "")) for v in d]

# プロット
plt.bar([0, 1, 2], d)
plt.xticks([0, 1, 2], ["2021 Sep 30", "2021 June 30", "2020 Dec 31"])
plt.show()

tabulateでパースしたテーブルは文字列になっているので、そこから数値を読み取ります。 figure

使いどころと落とし穴 #

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