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

# モチベーション
とある大学事務の人から、[科研費申請書](https://www.jsps.go.jp/j-grantsinaid/03_keikaku/download.html)をチェックするのが大変だという話を聞きました。現在は手で1部ずつ赤入れをしているそうです。

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

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

# PythonからWordファイルってどうやって読むの？
[Read from a word file in python](https://stackoverflow.com/questions/61147974/read-from-a-word-file-in-python)

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

# 環境
- MacOS Mojave 10.14.5
- Anaconda 2020.02
- Python 3.7.6
- Jupyter Notebook 6.0.3

## python-docxのインストール

```bash:bash
pip install python-docx
```
[python-docxはPython 3.4までしか対応していない](https://pypi.org/project/python-docx/)ようですが、Python 3.7で動くことは動きました。AnacondaにPython 3.4が入らないので、3.7のままにしました。

## docx2txtのインストール

```bash:bash
pip install docx2txt
```

## antiwordのインストール
後述しますが、サンプルとして読みたかったWordファイルが.docxではなく.doc形式でした。
[python-docxで.doc形式のファイルは開けません](https://stackoverflow.com/questions/36001482/read-doc-file-with-python)。
Wordで開いて.docxとして保存するのはなんか負けたような気がするので、antiwordでの変換を試みました。

### apt-getでインストール：失敗
結論から言うと、Macでapt-getでのantiwordのインストールはできませんでした。
antiwordは[apt-getで入れればいいのか](https://stackoverflow.com/questions/36001482/read-doc-file-with-python)、と思って[fink](https://qiita.com/yu-sa/items/351969b281f3aea5e03d)を入れ、finkのインストール途中で[JDK](https://eng-entrance.com/java-install-jdk-mac)がないと言われてなぜかAdobe Flashplayerのダウンロードページに飛ばされたりしました（もちろんFlashplayerをインストールしても何も解決しませんでした）。

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

### brewでインストール：成功

```bash:bash
brew install antiword
```
[ここ](http://macappstore.org/antiword/)を見てbrewコマンドを入れたら無事インストールできました。

```bash: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での読み書きテスト
[こちら](https://qiita.com/yasthon/items/f033eccd7b56a2615af0)の後半のコードをコピペしたら動き、Wordファイルの作成・読み取りができました。
Python 3.7で特に問題はないようです。
なお、[こちら](https://qiita.com/GDaigo/items/d5b46fc43c6250dd61b1)のコメント欄にあるコードをコピペして動かしてみたら、``docx_simple_service``は読み込めませんでした。たぶんPythonのバージョンによるものだと推定しています。

```text:error
ModuleNotFoundError: No module named 'docx_simple_service'
```

# antiwordで.docファイルを.docxファイルに変換してdoc2txtで読む
いよいよサンプルを読んでみます。
## サンプル
このような申請書を使います。[最近はもうなくなった枠線](https://news.yahoo.co.jp/byline/enokieisuke/20161024-00063620/)が現役だった時代です。.docファイルなので、そのままではPythonで読めません。
最終版のWordファイルが発掘できなかったので、メール提出して事務チェックしてもらった最終よりちょっと前のバージョンを使います。
![スクリーンショット 2020-10-26 23.22.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/509e8d37-ea6b-984c-fd03-788275a3cda4.png)

## .docファイルの読み取り
[こちら](https://stackoverflow.com/questions/51727237/reading-doc-file-in-python-using-antiword-in-windows-also-docx)の回答にある関数ですぐ読めました。
pathの指定部分だけ微妙に変えてあります。
.docファイルをantiwordで.docxファイルに変換して読み、読んだ.docxファイルはすぐ消しています。

```python: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/12c63be4-de20-4710-de9f-6f9504ef5311.png)

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

```python:python
gakushin = get_doc_text('./sample', '110624GakushinDraftKAGE2.1.doc')
gakushin = gakushin.replace('\n', '').replace('|', '').replace('\u3000', '')
```
![スクリーンショット 2020-10-28 19.57.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/5d522474-8a03-0490-561e-24349b798d06.png)
まだ連続スペースが残っているので、[ここ](https://qiita.com/izso/items/7eafbdfb59ee3783e544)を見ながら削除します。

```python:python
import re

# 連続スペースを削除して半角スペース1個にする
gakushin = re.sub('[ 　]+', ' ', gakushin)
```
et al. と年号の間など、できればスペースが残ってほしいところもあるので、とりあえず半角スペース1つは残しました。厳密にやるならスペース全部削除した後にet al. とか & の周りだけ置換するのがよいと思います。
![スクリーンショット 2020-10-28 20.06.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/6eeea36f-3ee7-3b0e-82dc-8205af6f81fb.png)

## 小見出しの抽出
【】で括られた部分を検索してみます。正規表現弱者なので、検索して[ここ](https://hodade.com/seiki/page.php?s_kakko)を参考にしたら動きました。

```python:python
re.findall('\【.+?\】', gakushin)
```

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

## 小見出しの下の文章の抽出
小見出しを変数に格納して、小見出し自体を使ってテキスト``gakushin``を分割してみます。

```python: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/55676563-f0ee-ea44-71ad-fe78da90e387.png)
文章を小見出しで分割してリスト化できました。要素数を調べてみます。

```python:python
print('小見出しの要素数', len(subhead))
print('分割した文章の要素数', len(split_result))
```

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

```python: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/ed2a20f9-e771-5993-7150-c489fdb89d7a.png)

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

```python:python
df['length'] = df.text.apply(len)
```
![スクリーンショット 2020-10-28 22.38.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/13260727-8b33-35d5-a4b8-a8f3548deeb0.png)
【2-2】という項目が特に長いようです。これだけ見ても【2-2】が何を表す見出しなのかはよくわかりません。研究計画っぽいですが、【1】がない理由は不明です。

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

# 参考

- [特別研究員|日本学術振興会](https://www.jsps.go.jp/j-pd/)
- [Read from a word file in python](https://stackoverflow.com/questions/61147974/read-from-a-word-file-in-python)
- [python-docx](https://pypi.org/project/python-docx/)
- [docx2txt](https://github.com/ankushshah89/python-docx2txt)
- [Read .doc file with python](https://stackoverflow.com/questions/36001482/read-doc-file-with-python)
- [Install antiword on Mac OSX](http://macappstore.org/antiword/)
- [Python で excel・wordの読み書き](https://qiita.com/yasthon/items/f033eccd7b56a2615af0)
- [【Python】【Word】【python-docx】python-docxを使ってPythonでワード文章のひな形を作成してみる](https://qiita.com/GDaigo/items/d5b46fc43c6250dd61b1)
- [研究者、ツイッターで政治を動かす？](https://news.yahoo.co.jp/byline/enokieisuke/20161024-00063620/)
- [Reading .doc file in Python using antiword in Windows (also .docx)](https://stackoverflow.com/questions/51727237/reading-doc-file-in-python-using-antiword-in-windows-also-docx)
- [連続するスペースを削除して1つにするpythonのコード](https://qiita.com/izso/items/7eafbdfb59ee3783e544)
- [かっこで囲まれた文字を検索する](https://hodade.com/seiki/page.php?s_kakko)