Google翻訳とPythonを使ってPDF論文を一発で翻訳する

#概要
[Google翻訳APIをPythonで実行する](https://qiita.com/kimisyo/items/ff0ae2aae97e4d8e3f65)では、四苦八苦しながらも、Google翻訳APIにより、テキストファイルに書かれた英文を日本語に翻訳するPythonスクリプトを書いた。

元々の動機は論文の翻訳する際に、ちまちまGoogle翻訳にコピペするのが面倒くさいということであった。
そこで今回は、Pythonスクリプトを拡張し、PDFの論文を一気に翻訳するようにしたので共有したい。

#そもそもなんで日本語に翻訳して論文を読むの？
もちろん、細かい内容は原文を精読する必要がある。そりゃそうだ。
日本語で読む理由はなんといっても、論文の内容を俯瞰的に把握できるということに尽きる。

俯瞰的に把握できることで、以下のメリットがある。

- 俯瞰的に把握した上で原文を読むことになるため、より早く理解することができる。
- 俯瞰的に把握できるため、原文を読む前に、自分にとって読む必要がある論文かどうかかが判断できる。
- 英文のみを読んでいるだけでは、気づかなかった情報が得られる。

英語が苦手な人間にとってはいいことづくめである。
もちろん、このメリットは昨今のAI技術による翻訳精度の向上によるところが大きいのだが(感謝)。

#作ったもの

- pdfを引数に与えて実行すると、pdfと同じフォルダに "translate.txt"という名前のテキストファイルに翻訳文を生成するコマンドを作成。
- pdfからテキストを抽出する処理はpdfminer.sixを利用。[PDFから全テキストを抽出する方法](https://tech.bita.jp/article/18)をそのまま利用させていただいた。
- 英語を日本語に翻訳するために、前回書いたようにGoogle Apps Scriptというシロモノを使ってGoogle翻訳を呼び出すAPIを作成し、Pythonから叩いている。

#ソース
取り急ぎソースを載せる。解説は後程。

```translate.py
import argparse
import requests
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfpage import PDFPage
from io import StringIO
import re
import os


def is_float(n):
    try:
        float(n)
    except ValueError:
        return False
    else:
        return True


def get_text_from_pdf(pdfname, limit=1000):
    # PDFファイル名が未指定の場合は、空文字列を返して終了
    if (pdfname == ''):
        return ''
    else:
        # 処理するPDFファイルを開く/開けなければ
        try:
            fp = open(pdfname, 'rb')
        except:
            return ''

    # PDFからテキストの抽出
    rsrcmgr = PDFResourceManager()
    out_fp = StringIO()
    la_params = LAParams()
    la_params.detect_vertical = True
    device = TextConverter(rsrcmgr, out_fp, codec='utf-8', laparams=la_params)
    interpreter = PDFPageInterpreter(rsrcmgr, device)
    for page in PDFPage.get_pages(fp, pagenos=None, maxpages=0, password=None, caching=True, check_extractable=True):
        interpreter.process_page(page)
    text = out_fp.getvalue()
    fp.close()
    device.close()
    out_fp.close()

    # 改行で分割する
    lines = text.splitlines()

    outputs = []
    output = ""

    # 除去するutf8文字
    replace_strs = [b'\x00']

    is_blank_line = False

    # 分割した行でループ
    for line in lines:

        # byte文字列に変換
        line_utf8 = line.encode('utf-8')

        # 余分な文字を除去する
        for replace_str in replace_strs:
            line_utf8 = line_utf8.replace(replace_str, b'')

        # strに戻す
        line = line_utf8.decode()

        # 連続する空白を一つにする
        line = re.sub("[ ]+", " ", line)

        # 前後の空白を除く
        line = line.strip()
        #print("aft:[" + line + "]")

        # 空行は無視
        if len(line) == 0:
            is_blank_line = True
            continue

        # 数字だけの行は無視
        if is_float(line):
            continue

        # 1単語しかなく、末尾がピリオドで終わらないものは無視
        if line.split(" ").count == 1 and not line.endswith("."):
            continue

        # 文章の切れ目の場合
        if is_blank_line or output.endswith("."):
            # 文字数がlimitを超えていたらここで一旦区切る
            if(len(output) > limit):
                outputs.append(output)
                output = ""
            else:
                output += "\r\n"
        #前の行からの続きの場合
        elif not is_blank_line and output.endswith("-"):
            output = output[:-1]
        #それ以外の場合は、単語の切れ目として半角空白を入れる
        else:
            output += " "

        #print("[" + str(line) + "]")
        output += str(line)
        is_blank_line = False

    outputs.append(output)
    return outputs


def translate(input):
    api_url = "https://script.google.com/macros/s/*******************/exec"
    params = {
        'text': "\"" + input + "\"",
        'source': 'en',
        'target': 'ja'
    }

    #print(params)
    r_post = requests.post(api_url, data=params)
    return r_post.json()["text"]

def main():

    parser = argparse.ArgumentParser()
    parser.add_argument("-input", type=str, required=True)
    parser.add_argument("-limit", type=int, default=1000)
    args = parser.parse_args()

    path = os.path.dirname(args.input)
    base_name = os.path.splitext(os.path.basename(args.input))[0]

    # pdfをテキストに変換
    inputs = get_text_from_pdf(args.input, limit=args.limit)

    with open(path + os.sep + "text.txt", "w", encoding="utf-8") as f_text:
        with open(path + os.sep + "translate.txt", "w", encoding="utf-8") as f_trans:

            # 一定文字列で分割した文章毎にAPIを叩く
            for i, input in enumerate(inputs):
                print("{0}/{1} is proccessing".format((i+1), len(inputs)))
                # 結果をファイルに出力
                f_text.write(input)
                f_trans.write(translate(input))


if __name__ == "__main__":
    main()
```

#解説
## PDFからテキストの抽出処理

まず、```get_text_from_pdf```がPDFからテキストの抽出処理である。
抽出したテキストをそのままGoogle翻訳にかけると、変な文字が入っていたり、切れ目が悪くGoogle翻訳の精度が下がるので、改行で区切って1行ずつ、次の工夫をしながら文章を再構築した。

###　翻訳の精度を上げる工夫
- 英文の場合、ハイフンの後に改行がはいると、単語の途中の改行と考えられるので、その場合前の行のハイフンを除去して次の行をつなげるようにした。逆にハイフンなしの場合は、単語の切れ目と考えられるので、前後の行に空白を挟むようにした。
- 抽出されたテキストの中に、ただの空白文字ではなく、"\x00"というNULLのutf8文字が入ってくることがある。数式等に含まれることが多いが、翻訳にはノイズとなるため、バイト文字列に変換した上で除去している。
- 空行や数字だけの行は、翻訳の妨げになるので削除している。
- 1単語しかなく、末尾がピリオドで終わらない行も、数式や図表の一部の可能性が高いため無視している。(見出しの可能性があるなぁ）
- 前の行がピリオドで終わっていたり、空行の場合は、文の切れ目と考えられるので、改行を明示的に入れている。
- 構築した文字列が一定数を超えた場合、Googleが正しく翻訳できない可能性がるため、一旦その文字列をリストに入れて、別の文字列を構築するようにしている。この一定数はlimitオプションとしてコマンドラインの引数で指定することもできる(デフォルトは1000文字とした)

## テキストの翻訳処理
得られた文字列のリスト毎に```translate(input)```を実行し、その結果をファイルに出力している。
APIのURLは念のためアスタリスクでマスクした。


#使い方

```
$ python translate.py --h
usage: translate.py [-h] -input INPUT [-limit LIMIT]

optional arguments:
  -h, --help    show this help message and exit
  -input   input pdf file
  -limit   max string length per request (default 1000)

```
実行するとpdfと同じフォルダに translate.txt (翻訳文)とtext.txt (PDFからの抽出テキスト)が生成される。

ためしに2本ほど論文を一気に翻訳してみてみたが、十分に概要をつかむことができた。もちろん、翻訳APIのおかげなのだが。

#今後の展望
数字だけの行を除いてはいるが、それ以外にも不要な情報がある。ページ番号、ジャーナルの情報、数式の断片などである。うまく改行処理が機能しているため、翻訳がおかしくなっているわけではないが、これらをテキスト抽出後、翻訳にかける前に除外できるような、対話的なGUIを作成してみたい。