地獄の黒塗り作業から解放せよ!大量にあるPDF文書内のセンシティブな情報を自動で黒塗りする

黒塗りイメージ

 

hapicom Inc.ことハッピーコンピューター株式会社では、様々な業界におけるご相談やご依頼の中で「一筋縄ではいかない問題」に直面することが多々あります。

本シリーズでは、過去にあった一筋縄ではいかない問題について、公開できる範囲でストーリー仕立てにしてご紹介していきます。

あまりオープンな場ではみられないような案件が多いので、こんな世界もあるのかと楽しんで読んでいただければ幸いです。

地獄の黒塗り作業から解放せよ!問題編

「これって関係ない会社の名前とかまとめて黒塗りできたりしますか」

ピコンッとチャットに投げられたそれは、二つ返事ですぐに、はいできます、とは答えられない程度には一筋縄ではいかなさそうだと思った。

チャットの相手は弁護士で、残業代の未払いに関して労働者側から依頼を受け、勤め先の会社を相手に訴訟を起こそうとしている中での出来事である。

裁判では事前に集めた証拠品をもとに相手方と戦うのであるが、会社を相手にこのような労働問題にまつわる訴訟を提起するとなると、証拠となるデータや紙の書類はほとんど全てその会社の社内にあるため、裁判までの間に会社側は都合の悪い証拠を処分したり改ざんしたりすることができてしまう。

そこで、労働問題に関する訴訟では通常、証拠保全と呼ばれる手続きが取られる。

証拠保全とは、裁判官の立ち会いのもと会社の敷地に立ち入り、裁判の証拠として必要だと思われるデータや書類を証拠品として押収するための手続きである。

特筆すべきは、証拠品を闇に葬られないよう、証拠保全の実施が当日に会社側に通知され、即日強行される点である。

ニュースやドラマで警察がいきなり会社にやってきて物品を押収するという場面をたまに見かけるが、それと似たような感じで通常業務を行っている会社に弁護士や裁判官らがぞろぞろと立ち入り、証拠品リストに記載のあるデータや書類を一つずつ押収していく。

この証拠保全によって、残業を行っていた証拠となりうるパソコンの稼働履歴やメールの送受信履歴などを、処分や改ざんが行われていないであろう状態で確保することができる。

ここでの我々hapicomの役割は、弁護士に付随する証拠保全エンジニアとして特別に許可をもらい、証拠保全の際に一緒に会社に立ち入り、システムログの抽出など専門的知識が必要な作業を含め証拠品の押収作業を行うことである。

--

今回もこの冒頭の弁護士とともに証拠保全に出向き、そして今、押収した大量の書類データについてチャットでやり取りをしている。

「裁判所からセンシティブな情報は隠すように言われて」

続けて届いたチャットメッセージには、証拠保全で押収した文書データを保管する裁判所から何やら要請があった旨が書かれていた。

押収した大量の文書データの中に、今回の訴訟に関係のない人や組織の名前などセンシティブな情報が多分に含まれており、このままでは保管できないから黒塗りをしてほしいと言われたとのことだった。

弁護士によれば、裁判官によってこのあたりの判断は異なり、今回の裁判官はかなりセーフティ寄りに考えるタイプで、万が一データが外部に漏れても大丈夫な状態で保管したいと考えているのではないか、ということらしい。

もちろん反論はしたのだが、裁判官への説得むなしく、黒塗りは必須となった。

どのような理屈にせよ裁判同様、証拠保全においても裁判官の最終判断が絶対であり、裁判官がそうせよと言うのだから対応しないわけにはいかない。

押収した文書データはPDFファイルにして千超、しかも各ファイルはそれぞれ数ページある、ということは、ページ数はざっと見積もって数千だ。

「事務所の人で手分けしてやれば一日あれば」

弁護士は言うが、弁護士事務所の人員を黒塗りするために一日費やせば、いったいどの程度の損失になるだろうか。

また、今後も黒塗りを要請される可能性があることを考えれば、今回さえ手作業で乗り切れれば、というある意味で怠惰な解決方法はあまり得策ではないと思われた。

弁護士事務所の方々をこのような生産性のない作業から解放させるべく、hapicomのメンバーは立ち上がった。

地獄の黒塗り作業から解放せよ!解決編

問題編で背景や経緯を書き連ねましたが、今回の問題を簡潔にまとめると「大量にあるPDF文書内のセンシティブな情報を自動で黒塗りする」というものになります。

期限は明確に提示されていないものの、緊急の要件ではあるので数日中に片付けたいというような状況です。

求められる品質は「裁判官が納得するレベルのもの」という曖昧な状態ですが、このようなケースでは1ターンで完璧な状態を目指すのは大変ですので、それなりの状態で一旦提出し指摘されたところを修正していく逐次方式を前提にすることで、作業時間の短縮と最終的な相手の満足度を両方追えます(状況によっては専門家として不誠実に見られることがあるので注意)。

ということで実際に我々がとった行動は、まず最初のボールをすぐに打ち返すことを目指し、ありものの組み合わせで簡単に黒塗りを自動化できる方法がないかを模索することでした。

方法の模索

壁になりそうな部分は下記の処理で、どちらもオープンソース、すなわち無償で利用することのできるライブラリであったような気がしたので、なんとなくできそうだという感覚はありました。

  1. センシティブな情報に該当するであろう人名や組織名などの固有名詞をテキストから自動抽出*1する
  2. PDF内のテキストを読み取ったり、指定した固有名詞が出現する位置に図形を追加したりする

まず1について先人がいないかWebで探してみると、自然言語処理の研究も行っているTIS株式会社の方のブログでまさに固有名詞を抽出して黒塗りするという事例があり、対象がPDFファイルではないものの、この方向性でいけそうだという確信を深めました。

qiita.com

また、オープンソースで活用できそうな固有名詞抽出のためのツールについて調べると、東北大学自然言語処理研究グループが公開しているja_core_news_trf (cl-tohoku/bert-base-japanese-char-v2) という言語モデルが固有名詞の抽出にも使えるということで、これを使わせていただくことにしました。

一方2については、黒塗りするための操作が可能なライブラリがあるか調べてみた結果、PyMuPDFというライブラリを使えば、PDF内の全テキストの取得、指定した固有名詞に対応するPDF内の座標の取得、指定した座標での長方形の追加が可能だとわかりました。

この時点で、以下の手順でPDFファイルの自動黒塗りができると判断し、残りの不確定要素である固有名詞の抽出精度について検証を進めることにしました。

  1. PDFファイルを読み込む
  2. PDF内のテキスト情報を取得する
  3. テキストの中から固有名詞を抽出する
  4. 固有名詞に対応する箇所に黒色の長方形を追加*2する
  5. PDFファイルを保存する

検証と実践

調べただけでは分からない部分については、実際に自分で試してみて確認するしかありません。

ということで、コードを書いてPDF文書の黒塗りを試してみました。

実際に手元にあるPDF文書を処理させてみると、思っていたよりもかなり高い精度で固有名詞が抽出できていました。

黒塗り結果の例

サンプル用のPDFファイルでの黒塗り例

そしてこの時点で、冒頭のチャットの質問に対して「できます」と答えられる状態になったと言えます。

とはいえ100%の精度というのは難しいもので、ここから先は人の目で直接見て調整する必要があります。

例えば上のサンプル画像では、西暦の数字を商品名として誤って抽出しています。

幸い、情報漏洩が一切発生しないように、というような厳格な要件ではないので、文書をざっと見渡して、目立っている語についてのみ個別に処理ルールを設けて手当てをしていきます。

ライブラリのおかげで必要なコード量も少なく、作業時間としてはそれほどかかりませんでしたが、文書数が多く処理の待ち時間がそれなりにあったため、全ての文書を一通り黒塗りするのに数時間必要でした。

それでも、緊急で対応する必要のある問題に対して、数日以内で片を付けるという目標を達成することができました。

最後は忘れずに、黒い長方形の裏側にまだ残っているであろうテキスト情報を消すためPDFを一括でJPG画像に変換してから提出し、裁判官の指摘を待つことにしました。

指摘と修正

想定通り、裁判官からの指摘は入りました。

指摘内容は文書中のURLを隠してほしいというもので、つまり、それ以外の部分については問題ないという、想定よりもかなり良いものでした。

URLを隠すのは人名や組織名などを黒塗りするよりもはるかに簡単で、正規表現というものを用いることで、URLのパターンにマッチするものを正確かつ迅速に見つけ出すことができます。

したがってこの段階で、「大量にあるPDF文書内のセンシティブな情報を自動で黒塗りする」という一筋縄ではいかない問題は一応の解決を見ることになりました。

地獄の黒塗り作業から解放せよ!後日談

後日、新たに別の文書群についても黒塗りしなければならず自動化で対応していて良かったと思わされる出来事はあったものの、この件に関しては以降しばらくの間音沙汰はなく、頭の中のすぐに取り出せる記憶領域からは完全に霧散していた。

数ヶ月経った頃、弁護士から連絡があった。

大量の文書データを武器に戦った弁護士は見事裁判で勝利したらしく、メールで勝利報告を送ってくれた。

「おかげさまで勝つことができました」

お世辞ではなく、実際に大量の文書データを証拠品として使うことができたからこそ、時間外労働をしていたことが認められ、勝つことができたのだろう。

役に立てて嬉しいという気持ちはもちろんあるのだが、あれだけの量の文書の中身を精査し、依頼人が時間外労働をしていたということを裁判で認めさせ、勝利したということに、畏敬の念を感じずにはいられなかった。

一次関数的にしかスケールしない、個々に対応していくしかないような仕事をしている人に対して、楽していい部分は楽させたいというような気持ちが強まった気がした。

(付録)PDF文書に黒塗りするコード

自動黒塗りのコードはpythonを使って書きました。

まず、それぞれのライブラリをインストールします。

pip install PyMuPDF
pip install spacy
python -m spacy download ja_core_news_trf

あとは下記のコードで、srcフォルダ内にあるPDFファイルが一つずつ読み込まれ、黒塗りされたPDFファイルがdstフォルダ内に保存されていきます。

import glob
import os
import fitz
import spacy

SRC_FOLDER = "src/"
DST_FOLDER = "dst/"

if __name__ == "__main__":
    nlp = spacy.load("ja_core_news_trf")
    files = glob.glob(SRC_FOLDER+"*")
    for in_file in files:
        out_file = DST_FOLDER + os.path.basename(in_file)
        doc = fitz.open(in_file)

        for page in doc:
    
            # 固有名詞を抽出
            texts = page.get_text("words", flags=fitz.TEXT_PRESERVE_WHITESPACE)
            for t in texts:
                text = t[4]
                result = nlp(text)
                pns = {}
                for ent in result.ents:
                    if ent.label_ == "PERSON" or ent.label_ == "NORP" or ent.label_ == "GPE" or ent.label_ == "EVENT" or ent.label_ == "FAC" or ent.label_ == "ORG" or ent.label_ == "PRODUCT":
                        pns[ent.text] = True

                # 黒塗りの座標ズレを抑制
                if not page.is_wrapped:
                    page.wrap_contents()

                # 検索して黒塗り
                for pn in pns:
                    rects = page.search_for(pn)
                    for rect in rects:
                        page.draw_rect(rect, fill = (0, 0, 0))
    
        doc.save(out_file)

*1:専門的にはこのような処理を固有表現認識(Named Entity Recognition, NER)と呼びます。

*2:この処理により文書を黒塗りしたような見た目になりますが、実際にはファイル内にはまだテキスト情報が残っていますので、画像ファイルに変換するなどの後処理が必要です。