Sansan Builders Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

人事異動のデータ化の取り組み 〜 CamelotによるPDFの表データ抽出

DSOC サービス開発部 GEES/JES/COEグループの松本です。

最近はコロナの影響で巣ごもりをしており、家の庭で芝生を育てています。
色々と手抜きをしてしまったがために生え揃いがまばらで、かわいい反面、芝生の上を裸足で歩けるようになるにはまだまだ時間がかかりそうです。気長に待ってみようと思います。

今日はSansanの機能の一つである、人事異動ニュースを支えるデータ化の取り組みについてご紹介したいと思います。

JES とは

Sansanの機能の中に、人事異動ニュースと呼ばれる機能があります。
sin.sansan.com

これは、所有する名刺に紐づく人物の人事異動が公開されたときにそれをニュースとして配信し、交換前に所有名刺の情報を最新情報に更新できるという機能です。
ニュースとして提供するために人事異動情報のデータ化を行っていますが、そのシステムがJESと呼ばれるもので、DSOCで開発を行っているサービスの一つになります。

課題

企業が発表する人事異動はほとんどの場合PDFで提供されます。例として、弊社が発表した人事異動情報は以下のようなPDFで提供いたしました。
https://ir.corp-sansan.com/ja/ir/news/auto_20200721465347/pdfFile.pdf

例えば決算情報は、XBRLのような構造化されたデータが合わせて提供されることが多く、自動的に情報を抽出するのが容易です。一方、人事異動情報はXBRLのように構造化されたデータは存在していません。そのため、発表された人事異動情報を弊社のオペレータにより人手でデータ化を行っています。

人手でのデータ化は精度が良い一方、データ化コストは高くなってしまいます。更にデータ量に応じて人的リソースを確保する必要があるため、繁閑差の調整が難しいという課題がありました。

そこで今回は自動化を始める手がかりとして、表形式*1で記載されている人事異動情報のデータ化をターゲットにしました。

Camelot

Camelotとは

camelot-py.readthedocs.io

CamelotとはPython製のOSSです。PDFからテーブルデータを抜き出し、Pythonの配列やpandasのDataFrame形式で情報を抽出することが可能なツールです。以下の特徴があります。

  • 2種類のパーサーを内包
    • Stream: 罫線が存在せず、文字だけで段組みがされている表
    • Lattice: 罫線が存在する表
  • パーサーの設定値を細かく指定可能
  • PandasのDataFrame形式でデータを出力でき、後続の処理が行いやすい

tabulaなどの他のテーブルをパースするライブラリと比較も行いましたが、コードの可読性の観点でCamelotを今回採用しています。Camelotになにか問題が合ったときにコードをデバッグする必要がありますが、我々のチームではRubyが得意なエンジニアが多いため、JavaよりもPythonの方が読みやすいだろうという判断です。
(Camelotの紹介ページでも、Tabulaよりも精度が良いと謳っているのも根拠の一つです)

アーキテクチャ

JESサービスはRuby on Railsで構成されていますが、今回のライブラリはPython製です。
PythonをRubyでbindingする方式でももちろん動かすことは可能ではあると思いますが非常に手間がかかること、及び、今後Pythonのsklearnなどを取り込んでいって自動化していきたいという思いから、Pythonのサービスを新規に立てることにしました。

f:id:kiyonori-matsumoto:20210517173912p:plain
アーキテクチャ

自動的に人事異動情報を入力するという機能ですが、当初は100%の精度が出せるという想定ではなかったため、オペレータの手を借りて結果を補正しつつ正解データをためていくことを想定し、UI層で結合するようにしています。

適用結果

最初ご紹介しました弊社の人事異動ドキュメントに対してCamelotを適用して、テーブルの抽出結果を見てみようと思います。

まずは表の抽出です。

import camelot

# PDFファイルを読み込み
tables = camelot.read_pdf('sansan.pdf')

# テーブルの位置を可視化
camelot.plot(tables[0], kind='contour').show()
f:id:kiyonori-matsumoto:20210513100426p:plain
実行結果

※こちらの人事情報は2020年7月21日のものです。

赤枠で示される部分が、Camelotが表として認識した箇所になります。1ページ目に実は3つの表が含まれているのですが、各々の表が正しく認識できました。

次に表のデータを取り出してみます。

# DataFrameとしてデータを取り出せる
>>> tables[0].df.__class__

pandas.core.frame.DataFrame


# 表のテキストデータを参照
>>> tables[0].df

0 1 2
0 氏 名 属性 現役職
1 寺田 親弘 再任 代表取締役社長/CEO
2 富 岡 圭 再任 取締役/Sansan 事業部 事業部長
3 塩見 賢治 再任 取締役/Eight 事業部 事業部長
4 常 樂 諭 再任 取締役/CISO/DSOC (Data Strategy & Operation Center) センター長
5 大間 祐太 再任 取締役/CHRO
6 橋本 宗之 新任 執行役員/CFO

※こちらの人事情報は2020年7月21日のものです。

わずか数行のコードですが、PandasのDataFrame形式で表のデータを取り出すことができました。

表データを取り出したあと

表からデータを取り出しただけでは不十分で、JESではCamelotにより取り出した表データに対して、いくつか加工 or 選別を行い、人事異動データとして抽出しています。
具体的には以下のようなことを行っています。

タイトル行から記載されている項目を推定

人事異動の記載フォーマットに決まりがなく様々な種類があるため、表の列番号だけから記載されている内容を確定することができません。そのため、タイトル行を抽出した上で、タイトルの文字列を識別し、該当の列に記載されている項目がどのような種類のデータであるか判別する処理を行っています。更に、複数ページにまたがって記載される表もあるため、ページをさかのぼってタイトル行を取得することも行っています。

ふりがなの分離

人名に対してふりがなを付与している資料も多いです。更に、ふりがなは殆どのケースに置いて氏名と同じセル内に記載されており、単純に氏名のセルの値を取り出すだけだと氏名とふりがなとが混ざった状態で取り出されてしまいます。
そのため、氏名のセルの内容に対してはデータを取り出した後にふりがなの分離処理を行っています。現状は正規表現だけで分離を試みていますが、現状正規表現だけで高い精度で分離することができました。
また、Camelotはデフォルトでは縦書きの文字列を検出しようとします。ふりがなが漢字の上に記載されている場合、縦方向の文字間隔よりも横方向の文字間隔の方が大きいため、縦書きであると判断されます。その結果、誤った文字列として検出されてしまいます。Camelotの柔軟に設定できる解析オプションに於いて、縦書きの文字列解析を行わないように設定することで本問題を解決しています。

苦労したこと

PDFの読み込み時にエラーが発生する

Camelotは、PDFファイルを1枚ずつに分割して回転を補正し保存した上で、ページごとに表の抽出処理を行うという順で処理を行っています。このPDFファイルの分割及び保存に PyPDF2 というライブラリを利用しているのですが、このライブラリが直近メンテナンスされておらず、約半数のPDFファイルの読み込みに失敗してしまいました。
上記分割・回転処理を、メンテナンスされているPyMuPDFに置き換えることでエラーが発生しないように回避し、より多くのPDFファイルが読み込めるようになりました。

Pythonでのサーバー構築

Pythonでサービスを構築するのはチームとして初の試みであったため、Webフレームワークの選定から行いました。JESのフロントエンドはReactで作成しているため、JSON APIを容易に作成できることを要件として検討しました。
結果、今回はFastAPIをWebフレームワークとして採用しました。要因は2点あります。
1つめは見た目がシンプルであることです。デコレータで登録するルーティングを書くだけでそのパスへのルートが作成できたり、CORS, OAuthの設定もフレームワークで用意されていたりと、シンプルにモダンなAPIを作成できる点が非常に良いと感じました。
2つめはOpenAPIのドキュメントのエンドポイントもコードから生成されることです。フロントエンドの開発が別のエンジニアが行うことを想定し、OpenAPIで仕様のやり取りを行うことを考えていました。FastAPIを利用することでコードから自動的にOpenAPIのドキュメントが作成されるため、コードとドキュメントとの不整合が発生しえず、常に最新の状態で開発を行うことができる点に魅力を感じました。また、OpenAPIの定義とPythonの型定義が統合されていて、コードの型を定義することでOpenAPIの型が整備されるという点も、開発効率の観点で非常に効率的でした。

弊社DSOCの高橋もFastAPIの紹介を本ブログで行っていますのでよろしければご一読ください。

buildersbox.corp-sansan.com

まとめ

今回は、人事異動のデータ化においての我々の取り組みをご紹介しました。
DSOCでは様々なアナログデータのデジタル化を行っていますが、オペレータによる入力に加えてこのような各種自動化技術を検討及び改善を行い、高精度かつ高速にデータ化できるシステムを作り上げています。

*1:複数名の人事異動情報を単一の資料で提供する形式が多く、人事異動情報は表形式で表現されていることが多いです

© Sansan, Inc.