LoginSignup
5
3

More than 3 years have passed since last update.

Pythonから昔の学振DC申請書のWordファイル(.doc)を読んで操作してみる

Posted at

モチベーション

とある大学事務の人から、科研費申請書をチェックするのが大変だという話を聞きました。現在は手で1部ずつ赤入れをしているそうです。

  • 公募要領に沿って記入しているか
  • 図や参考文献は適切に入っているか
  • 業績リストは適切な形式になっているか

など、自動チェックツールがあると良さそうです。
Pythonで自動チェックツールを作れるといいですね! と思いつつ、すぐ作るのは荷が重いので、まずはPythonからのWordファイルの読み書きを試してみました。
科研費申請書に準じるサンプルとして、筆者が2011年6月に提出した日本学術振興会特別研究員(学振)DC2の申請書を読んでみました。学振についてよく知らない人は、身近な博士課程出身者に聞いてみると感情のこもった応答が返ってくるかもしれません。

PythonからWordファイルってどうやって読むの?

Read from a word file in python

主にpython-docxdocx2txtがあり、どちらも.docxファイルのみに対応しています。後述しますが、.docファイルを読む時は、antiwordによる.docxへの変換が必要になります。
docx2txtはヘッダ・フッタ・ハイパーリンクからもテキストを読み取れるということなので、今回は主にdocx2txtで試してみました。

環境

  • MacOS Mojave 10.14.5
  • Anaconda 2020.02
  • Python 3.7.6
  • Jupyter Notebook 6.0.3

python-docxのインストール

bash
pip install python-docx

python-docxはPython 3.4までしか対応していないようですが、Python 3.7で動くことは動きました。AnacondaにPython 3.4が入らないので、3.7のままにしました。

docx2txtのインストール

bash
pip install docx2txt

antiwordのインストール

後述しますが、サンプルとして読みたかったWordファイルが.docxではなく.doc形式でした。
python-docxで.doc形式のファイルは開けません
Wordで開いて.docxとして保存するのはなんか負けたような気がするので、antiwordでの変換を試みました。

apt-getでインストール:失敗

結論から言うと、Macでapt-getでのantiwordのインストールはできませんでした。
antiwordはapt-getで入れればいいのか、と思ってfinkを入れ、finkのインストール途中でJDKがないと言われてなぜかAdobe Flashplayerのダウンロードページに飛ばされたりしました(もちろんFlashplayerをインストールしても何も解決しませんでした)。

bash
sudo apt-get antiword
出力結果
E: Invalid operation antiword

brewでインストール:成功

bash
brew install antiword

ここを見てbrewコマンドを入れたら無事インストールできました。

bash
(base) akpro:~ kageazusa$ antiword
    Name: antiword
    Purpose: Display MS-Word files
    Author: (C) 1998-2005 Adri van Os
    Version: 0.37  (21 Oct 2005)
    Status: GNU General Public License
    Usage: antiword [switches] wordfile1 [wordfile2 ...]
    Switches: [-f|-t|-a papersize|-p papersize|-x dtd][-m mapping][-w #][-i #][-Ls]
        -f formatted text output
        -t text output (default)
        -a <paper size name> Adobe PDF output
        -p <paper size name> PostScript output
           paper size like: a4, letter or legal
        -x <dtd> XML output
           like: db (DocBook)
        -m <mapping> character mapping file
        -w <width> in characters of text output
        -i <level> image level (PostScript only)
        -L use landscape mode (PostScript only)
        -r Show removed text
        -s Show hidden (by Word) text

2005年のツールなんですね!

python-docxでの読み書きテスト

こちらの後半のコードをコピペしたら動き、Wordファイルの作成・読み取りができました。
Python 3.7で特に問題はないようです。
なお、こちらのコメント欄にあるコードをコピペして動かしてみたら、docx_simple_serviceは読み込めませんでした。たぶんPythonのバージョンによるものだと推定しています。

error
ModuleNotFoundError: No module named 'docx_simple_service'

antiwordで.docファイルを.docxファイルに変換してdoc2txtで読む

いよいよサンプルを読んでみます。

サンプル

このような申請書を使います。最近はもうなくなった枠線が現役だった時代です。.docファイルなので、そのままではPythonで読めません。
最終版のWordファイルが発掘できなかったので、メール提出して事務チェックしてもらった最終よりちょっと前のバージョンを使います。
スクリーンショット 2020-10-26 23.22.11.png

.docファイルの読み取り

こちらの回答にある関数ですぐ読めました。
pathの指定部分だけ微妙に変えてあります。
.docファイルをantiwordで.docxファイルに変換して読み、読んだ.docxファイルはすぐ消しています。

python
import os, docx2txt

def get_doc_text(filepath, file):

    if file.endswith('.docx'):
       text = docx2txt.process(file)
       return text

    elif file.endswith('.doc'):
       # converting .doc to .docx
       doc_file = os.path.join(filepath, file)
       docx_name = file + 'x'
       docx_file = os.path.join(filepath, docx_name)

       if not os.path.exists(docx_file):
          os.system('antiword ' + doc_file + ' > ' + docx_file)

          with open(docx_file) as f:
             text = f.read()
          os.remove(docx_file) #docx_file was just to read, so deleting

       else:
          # already a file with same name as doc exists having docx extension, 
          # which means it is a different file, so we cant read it
          print('Info : file with same name of doc exists having docx extension, so we cant read it')
          text = ''

       return text

読めました!
スクリーンショット 2020-10-27 22.45.58.png

小見出しごとの内容を抽出してみる

読んだテキストを使って、小見出しごとの内容を抽出してみたいと思います。
今回の例では、小見出しは【】で括られています。【問題点】とか。

テキストの整形

改行などを削除します。

python
gakushin = get_doc_text('./sample', '110624GakushinDraftKAGE2.1.doc')
gakushin = gakushin.replace('\n', '').replace('|', '').replace('\u3000', '')

スクリーンショット 2020-10-28 19.57.37.png
まだ連続スペースが残っているので、ここを見ながら削除します。

python
import re

# 連続スペースを削除して半角スペース1個にする
gakushin = re.sub('[  ]+', ' ', gakushin)

et al. と年号の間など、できればスペースが残ってほしいところもあるので、とりあえず半角スペース1つは残しました。厳密にやるならスペース全部削除した後にet al. とか & の周りだけ置換するのがよいと思います。
スクリーンショット 2020-10-28 20.06.55.png

小見出しの抽出

【】で括られた部分を検索してみます。正規表現弱者なので、検索してここを参考にしたら動きました。

python
re.findall('\【.+?\】', gakushin)
出力結果
['【背景】',
 '【問題点】',
 '【解決方策、研究目的、研究方法、特色と独創的な点】',
 '【研究経過1】',
 '【研究経過2】',
 '【これからの研究計画の背景】',
 '【問題点・解決すべき点】',
 '【着想に至った経緯】',
 '【2-1】',
 '【2-2】',
 '【査読あり】',
 '【口頭発表・査読なし】',
 '【ポスター発表・査読なし】',
 '【研究職を志望する動機】',
 '【目指す研究者像】',
 '【自己の長所等】',
 '【特に優れた学業成績,受賞歴】',
 '【特色ある学外活動】']

小見出しが抽出できました!

小見出しの下の文章の抽出

小見出しを変数に格納して、小見出し自体を使ってテキストgakushinを分割してみます。

python
subhead = re.findall('\【.+?\】', gakushin)
text = gakushin
split_result = []

for i in range(len(subhead)):
    new_text = text.split(subhead[i])
    split_result.append(new_text[0])
    text = new_text[1]

# 最後の1回だけ[1]を入れる
split_result.append(new_text[1])

スクリーンショット 2020-10-28 22.15.43.png
文章を小見出しで分割してリスト化できました。要素数を調べてみます。

python
print('小見出しの要素数', len(subhead))
print('分割した文章の要素数', len(split_result))
出力結果
小見出しの要素数 18
分割した文章の要素数 19

小見出しの要素数+1 = 小見出しで分割した文章の要素数 となり、計算は合っているようです。
小見出しとその下の文章が合うように、pandas DataFrameに格納してみます。リストsplit_resultの最初の要素は捨てることになります。

python
import pandas as pd

df = pd.DataFrame([subhead, split_result[1:19]]).T
df.columns = ['subhead', 'text']

スクリーンショット 2020-10-28 22.22.46.png

小見出しとその下の文章が対応づけられました。文字数を数えてデータフレームに入れてみます。

python
df['length'] = df.text.apply(len)

スクリーンショット 2020-10-28 22.38.25.png
【2-2】という項目が特に長いようです。これだけ見ても【2-2】が何を表す見出しなのかはよくわかりません。研究計画っぽいですが、【1】がない理由は不明です。

まとめ

Pythonから.docファイルを読み取り、テキストを操作することができました。
今後いろいろ試していきたいと思います。

参考

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3