厚生労働省のブラック企業リストをPythonで解析する(PDFMiner.six)
スポンサーリンク
タイトルの通り、5月ごろに話題になった例のPDFをPython3 + PDFMiner.six でパースしてTSV(タブ区切りテキスト化)する話です。
以前からやろうと思っていながら放置していました。
すでにgithub でTSVデータにして変換してくださった方がいるので完全に二番煎じ。
ただ、そのTSVデータ、PDFをWordで開いてHTMLに変換してパースしていると書かれており、さすがにナンセンスな感じが拭えない*1。
自分でやらなくても他人様の成果物をありがたく使わせてもらえばいいやと言いたいところですが、このTSVファイル、ちょっと気に入らないところがあって……。
- 行末に余計なタブがある
- 各列の値が全部ダブルクォートで囲まれている
- 右端のカラムが複数行になっている場合の扱い(送検、不起訴処分、とか)*2
まあちょっと自分でやってみたら地味に面倒でした。
PDFのまま解析するライブラリあるんだからそっちを使えばすぐに解決するかと思ったら大違いだったという。
PythonのPDFライブラリを使えばいいと安直に考えたのがそもそもの発端……。
準備
PythonにはPDFMiner というライブラリがあるのでこれを使用。
Google先生のご託宣によると、Python 2.x 版とPython 3.x 版がある。今回はPython 3.x版を使う。
PDFMiner2というのも見つけたが、メンテナンスされているのかはっきりしない。PDFLibの方は読み書き両方できるようだけど構造解析まではできるのかわからなかったのでひとまず保留。
PDFMiner のインストール
まずはインストールする。pip3
は環境によってはpip
のはず。
pip3 install pdfminer.six
web上の情報を見ると日本語を扱うためにはcmap
が必要なのでソースからビルドしろと書いてある……。何も気にせずパッケージ管理ツール任せでインストールしたけど問題は起きていない。
注意点としては、付属のコマンドの文字コードがCR+LFになっているせいでpyenv
環境下ではエラーになって実行できない。
$ pdf2txt.py pdfminer.six/samples/simple1.pdf env: python\r: No such file or directory
適当にエディタで開いて開業コードを変換しておく(もしくはdos2unix
とか)。
日本語の縦書きサンプル(宮沢賢治の文章の序文)が付属しているのでテストできる。
$ pdf2txt.py pdfminer.six/samples/simple1.pdf (省略)
“Hello"とか"World"とか出力されれば正しくインストールされている。なんか違和感のあるサンプルだけどそっとしておこう。
$ pdf2txt.py -V pdfminer.six/samples/jo.pdf (省略)
ちゃんと日本語の文章が(横書きに変換されて)表示される。-V
オプションは縦書き用のオプション。
解析
表部分がどういう形式になっているのかPDFの構造を確認しないとなんとも言えない。表の行と列に対してループをまわしてそれぞれのセルのデータを取り出せるというのが理想。
そもそものPDFのファイル形式は下記のサイトが詳しい。今回はPDFMinerライブラリが細かいことはやってくれるのでそれほど気にしなくてもいい。
参考:詳細PDF入門 ー 実装して学ぼう!PDFファイルの構造とその書き方読み方 - プログラムモグモグ
ファイルの構造を確認
厚生労働省のPDFが素直なPDFファイルだといいのですが、変な構造だったりすると非常に困る……。
結論から言うと、2ページ目位以降はタイトルと都道府県労働局の名称、日付のデータの下にテキストがたくさん並ぶという形式。
- なぜか文字列の最後に全角スペースがくっついている箇所がある(北海道の「(有)市木 」 *3) -カラムの幅を超える文字列は折り返される
- 複数の要素を列挙しているカラムがある(「違反法条」、「その他参考事項」)
- 会社名と支店名の間の空白が不統一(兵庫県労働局と岡山県労働局)
PDFMinerでPDFを読み込むとLTTextBox
クラスのインスタンスからget_text
メソッドでテキストを取り出せる。
表紙ページをダンプすると全角スペース*4がずらりと出力されるのでびっくりする。おそらく右寄せせずに全角スペースを連打して書式を整えたんでしょう、わかります。
WordかExcelかはともかく、文字列を右寄せしたいならちゃんとソフトウェア側の機能を使うべき。
PDFMiner によるページ構造解析について
PDFそのものにはページ以外の論理構造はない。紙を模した論理的な領域の特定の位置に文字を配置するというデータ形式なので表なのか普通の文章かどうかは関係がない。よって表形式だからといってそのまま構造を保ったまま文字を取り出すには構造を推定する必要がある。
PDFMinerは不完全ながら構造を解析(推定)してページ内のオブジェクトにアクセスできる。一見、ちゃんと表の行と列の順序になっていると見せかけてところどころ変な順序になっている。そのためバウンディングボックスの値を確認してソートし直す必要がある……。
ドキュメントによるとLTTextBox
の中にLTTextLine
クラスのノードとLTChar
が格納されているはずだけど、LTTextBox
のget_text
メソッド経由で文字列としてテキストを取り出せているので深追いはしない。
このPDF、一部のセル(1列目と4、5列目)は折り返されているので改行コードを除去する必要があるのと、4列目および5列目はリスト形式にする必要がある。
大雑把にいうと、各ページの0番目のオブジェクトがページのタイトル、1番目が都道府県労働局となっている。最終更新日だけが6番目という不自然な位置だけど残りの2番目から8番目がヘッダ行。
残りが実際のデータなんだけど、微妙に面倒なことになっていて列の切り出しが不完全な箇所がある。どのタイミングで切り出すかはともかく、基本的にはテキストを取り出して半角スペースで分割すれば良い。
出力用のスクリプト
実装するうえでの方針の説明(こっちが本命)。
- 1ページ目はスキップする
- 企業名だけではなくどこの都道府県の労働局の管轄かという情報も残す
- 出力形式はひとまずTSV(将来的にはJSONか)
ごく一部とはいえ複数の列が結合してひとまとまりのテキストの数号になってしまう。バウンディングボックスの左上の座標でソートして整列しようとしたが各カラム内でy軸方向にセンタリングされているせいでうまくいかず……。
試行錯誤の末、バウンディングボックスのx座標と、y座標の平均をそれぞれ使用してソート。バウンディングボックスを綺麗に切り出して重心を計算してソートすればもっとスマートなコードになるはず。もっと踏み込むならモンキーパッチ(オープンクラス)でバウンディングボックスの重心を計算するメソッドを追加してもいいかも。
なお、PDFの座標系は左下原点系なので注意。
Convert PDF to TSV ( from Japan's MHLW illegal com …
対症療法的なスクリプトとなっております。PDFMiner側のオブジェクトのままではなく自前で扱いやすいデータ構造に変換してしまった方がもっとスマートにできたような気がします。
特に隣の列と結合している部分は罫線のオブジェクトから幅に関する情報を取り出せばすんなり処理できたんじゃないかと。
テキストの集合に対して自分で領域分割してやればよかったのか。
PDFのファイル構造の仕様書と解説本自体はあるので時間のあるうちにしっかり勉強したいと思ってます。
成果物
冒頭のリンク先のリポジトリにあるものとは微妙に形式が異なります。ダブルクォートによるエスケープがないのと、最終カラムはカンマ区切りのリストになっています。各カラムの列の順序は元のPDFと同じです。
厚生労働省ブラック企業リスト(2017年8月15日版) · GitHub
また、企業名の表記における区切り文字は元のまま統一されていません。
そのほか感想など
地味に面倒だったというのが一番の感想。PDFの構造解析の問題をWordでHTMLに変換することで回避できているとすると、Microsoftの文書の構造解析技術はかなりすごいのではないかと思った。
過去記事でネタにしたMicrosoftのOCR用のAPIの性能から推測すると文書の構造解析はかなりいいのではないかと思う。
それはさておき、5月に公表されてから基本的に毎月更新しているみたいですが、大変残念な欠陥があるようです。
毎回ファイル名が同じ!!
つまり、一定期間経過するとこのリストから企業名が消えてしまうことが予想される。というか掲載基準にそう書いてある……。
ほとぼりが冷めたら勝手に消えていくというシステム。これはどうかと思います。
このデータをもとに検索サイトを作ってやればいいだけですし、もちろんそのつもりだったりします。
補足
厚生労働省のサイト。
- 長時間労働削減に向けた取組|厚生労働省
- http://www.mhlw.go.jp/kinkyu/dl/170510-01.pdf
- http://www.mhlw.go.jp/kinkyu/dl/170510-02.pdf
各都道府県別のデータが欲しいなら、それぞれの都道府県の労働局のページで公開されているPDFを入手してパースしてもいいと思う。
例えば北海道労働局。
厚生労働省と更新されるタイミングが違うのかどうかは未確認。
参考サイト
- GitHub - pdfminer/pdfminer.six: Python PDF Parser – fork with Python 2+3 support using six
- Programming with PDFMiner
- いるかのボックス: Python3でPDFのテキストを抽出する