PythonとSeleniumでDeepLに英文流して自動翻訳させる

# 改変履歴
'21/1/26 DeepLのテキストエリアのCSSセレクタが変更になったようなので、関数の方も修正。
`21/5/18　またまたCSSセレクタが変更になったようなので、修正

# やりたいこと

とりあえず、Science Directから書誌情報＋アブストをゲットした。次は、これをDeepLに流し込んで翻訳させていく、っていう処理をしたい。有料プランに契約すれば、ファイルを一気に翻訳させることが可能になるけど、まあ、ものは試しということでselenimuとchromedriverでやってみようということでチャレンジ。

# 準備

##とりあえずCSVファイルをpythonに読み込む。

```python
import pandas as pd

df = pd.read_csv("DB.csv",header=None, delimiter=",", quoting=1)
print(df.at[0,1])  #   タイトル
print(df.at[0,9])  #   アブスト
print(df.at[0,10]) #　 キーワード

for title in df[1]:
    print(title)
```

まあ、これはすんなり。

## SeleniumとChromDriverでDeepLにアクセス

```python
from selenium import webdriver  

load_url = "https://www.deepl.com/ja/translator"
driver = webdriver.Chrome(executable_path='c:/work/chromedriver.exe')  #  driver = webdriver.Chrome()
driver.get(load_url)
```

ここもまあすんなりいく。

# 英文をDeepLに送る
テキストを送り込んで、翻訳をゲットする、ということで、英文を入力するtextareaと翻訳を出力してくるtextareaのcss selectorをChromeのdevelopper画面から探し出す。
で、入力についてはすんなり、

```html
#dl_translator > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--source > div.lmt__textarea_container > div > textarea
```
だということがわかった。
ということで、

```python
title = df.at[0,1]
input_selector = "#dl_translator > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--source > div.lmt__textarea_container > div > textarea"
driver.find_element_by_css_selector(input_selector).send_keys(title)
```
すなおに、英語はDeepLに送りこめた。

2021/5/18
久しぶりに確認してみたら、DeepLの仕様変更したっぽくて、CSS Selectorが変わってた。
ということで、以下が2021年5月18日時点のiput部のSelector

```python
input = "#dl_translator > div.lmt__text > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--source > div.lmt__textarea_container > div.lmt__inner_textarea_container > textarea"
```


# 日本語訳の取得　その１

一方で、出力のほうが嵌まった。
とりあえず、一回適当な英文で翻訳を作らせて出力させて、その出力テキストを使ってDevelopper画面で検索して探してみたら、なんとtextareaにそのまま入力されているのではなく、buttonタグが付けられて表示されている。
へー、と思いながら、button要素を取得して、テキストを取ろうとしたのだが、なぜか取れない。
VSCodeのデバッガで変数の状態を見ても、要素がもつtextは空文字になっている。え？なんで？？
Chrome上では見えてるし、Developper画面ではちゃんとbuttonタグにはさまれて訳が表示されているのに、なんで空文字なん？？

とりあえず、対処として、DeepLにはクリップボードに訳をコピーしてくれる便利なボタンがあるので、それをSeleniumでクリックしてクリップボードにコピーさせ、クリップボードから訳を取得することにする。
ってことで、クリップボードを扱うためのパッケージ`pyperclip`をインストールして、クリップボードにコピーしてくれるボタンのCSS Selectorを見つけ出して、以下のようなスクリプトをかいて実行。

```python
import pyperclip

title = df.at[0,1]
input_selector = "#dl_translator > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--source > div.lmt__textarea_container > div > textarea"
driver.find_element_by_css_selector(input_selector).send_keys(title)

time.sleep(5)

OutputCopyBtn = "#dl_translator > div.lmt__text > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--target > div.lmt__textarea_container.lmt__textarea_container_no_shadow > div.lmt__target_toolbar.lmt__target_toolbar--visible > div.lmt__target_toolbar__copy > button"
#"#dl_translator > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--target > div.lmt__textarea_container > div.lmt__target_toolbar.lmt__target_toolbar--visible > div.lmt__target_toolbar__copy > button"
driver.find_element_by_css_selector(OutputCopyBtn).click()

print(pyperclip.paste())
````

ふむ、これで取るのは取れた。

# 日本語訳の取得　その2
なんか負けた感があるので、button要素から直接テキストを取れないかリトライ。
ググってると、行きついたのが[@riikunn1004さんの記事](https://qiita.com/riikunn1004/items/68c7621baaa54cf27230)。
なるほど。`.getAttribute("textContent")`というのでとれるのか。
画面上でも見えてるし、Developper画面でも見えてるのに取れない理由はよくわからないけど、これを参考に、以下のようなスクリプトにしてみた。

```python
#2021,5,18更新
Output_selector = "#dl_translator > div.lmt__text > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--target > div.lmt__textarea_container.lmt__textarea_container_no_shadow > div.lmt__translations_as_text > p.lmt__translations_as_text__item.lmt__translations_as_text__main_translation > button.lmt__translations_as_text__text_btn"
# "#dl_translator > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--target > div.lmt__textarea_container > div.lmt__translations_as_text > p > button.lmt__translations_as_text__text_btn"
Outputtext = driver.find_element_by_css_selector(Output_selector).get_attribute("textContent")
print(Outputtext)
```
これで取れることを確認。ふう。

# おまけ
もうすこしDeepLのページの造りをしらべてみた。
入力のテキストエリアと同様に出力のテキストエリアも

```
#dl_translator > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--target > div.lmt__textarea_container > div.lmt__inner_textarea_container > textarea
```
で指定されている。ただ、CSSみてみると`:disabled`にがついていて操作が無効化されている。
実際に、英文を入力するまえは出力エリアは選択できない。
で、英語を入力すると、ここのエリアのクラス名が変わり、disableが消える。（css selectorは変わらない。）

ただ、このエリアに訳が入っているのか？というと入っていない。
画面では訳が入ってるように見えるが、現時点では空のまま。

このエリアをマウスでクリックして、テキストエリアにフォーカスをあてると、以下のような要素がポンっと表れて訳が表示される。（ちなみに英語としてtestを入力してる。なので訳はしけん）

```html
<div class="lmt__textarea_base_style" 
  style="
    position: absolute; transform: translate(-500%, -500%); 
    padding: 16px 32px 80px 24px; margin: 0px; overflow: hidden; font-family: &quot;
    Open Sans&quot;, sans-serif; font-size: 24px; font-stretch: 100%; font-weight: 400;
    line-height: 36px; height: 468.5px; width: 448.5px;">
  <span style="outline: green solid 1px;">しけん</span>
  <span style="outline: red solid 1px; display: inline-block; position: relative; height: 1em;"></span>
  <span style="outline: blue solid 1px;"></span>
</div>
```
こいつのCSS Selectorは

```html
#dl_translator > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--target > div.lmt__textarea_container > div.lmt__inner_textarea_container > div
```

んー、何となくわかってきた。
つまり、DeepLさんのページは、英文が入力されると、画面上に訳は表示されるけど、テキストエリアに表示させているわけではない。

実は下の方に、こういう形で

```
<button class="lmt__translations_as_text__text_btn">しけん</button>
```
で隠し持っている。
で、画面上で出力の「テキストエリア」っぽく見えるところにカーソルを持ってきてクリックを押すと上のようなdiv要素が展開し、訳が表示される、というようになっている。この切り替えをするために、button要素にしてる、ということかな？

随分ややこしい造りにしてる。なんでこんな造りにしてるんやろ？？
ただ、なるほど。こういう造りであれば、「テキストエリア」っぽく見えるところを一回クリックしない限り、テキストエリアの回りを探しても訳にはたどり着けない。
また、buttonも基本的にはdisableかinvisibleかとにかく見えないようにしているんだろうから、`.text`では取れない。
さらには、フォーカスをあてたら現れる要素のCSS Selectorを予め取得して、訳を作らせた後にアクセスしようとしても、一回テキストエリアにフォーカスをあてない限りは、「そんな要素はない」と弾かれる。ただ1回フォーカスをあててみたら、要素が見つかるので取れるんだろと思う。

ほんと、なんでこんなややこしいことしてるんだ？？

# ~~最終的な~~ソースコード　

とりあえず、DeepLに流して英文を自動翻訳させる部分は以下のような形。
途中のwhile文は、要するに英文の翻訳に時間がかかるから、その待ち時間のため。
1秒おきに翻訳できたかどうかをチェックしに行き、翻訳が完成していたらブレークする。

```python
import pandas as pd
import time
from selenium import webdriver  #  
import chromedriver_binary

df = pd.read_csv("Reliablity EngineeringDB.csv",header=None, delimiter=",", quoting=1)

df.columns=["Authors", "Title", "jTitle", "VolIssue","Year", "Pages", "ISSN","DOI","URL","Abst","Keywords"]
print(df)

Title.df.at[0,1]

load_url = "https://www.deepl.com/ja/translator"
driver = webdriver.Chrome()  #  driver = webdriver.Chrome("c:/work/chromedriver.exe")
driver.get(load_url)

input_selector = "#dl_translator > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--source > div.lmt__textarea_container > div > textarea"
driver.find_element_by_css_selector(input_selector).send_keys(Title)
while 1:
    Output_selector = "#dl_translator > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--target > div.lmt__textarea_container > div.lmt__translations_as_text > p > button.lmt__translations_as_text__text_btn"
    Outputtext = driver.find_element_by_css_selector(Output_selector).get_attribute("textContent")
    if Outputtext != "" :
        break
    time.sleep(1)
print(Outputtext)
```

# 関数化
上記で最終にするつもりだったけど、実際にやりたいことをやるにあたり関数にしり、待機処理の部分をいじったりしたので、そちらも掲載する。

```python
'''
DeepLを使った翻訳を行う関数
入力　翻訳したい英語
出力　翻訳された日本語
例外　入力が文字列でない場合
'''
import time
from selenium import webdriver  
import chromedriver_binary

def TranslationByDeepL( mytext ):
    if mytext =="":
        return ""
    if type(mytext) is not str:
        raise   Exception("文字列ではありません")

    #DeeLのページのURLとCSS Selector
    load_url = "https://www.deepl.com/ja/translator"

    #'21/1/26 Selectorが変更になったっぽくて、要素にアクセスできなくなってたので確認して修正
    input_selector = "#dl_translator > div.lmt__text > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--source > div.lmt__textarea_container.halfViewHeight > div > textarea"
                    #"#dl_translator > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--source > div.lmt__textarea_container > div > textarea"
    Output_selector = "#dl_translator > div.lmt__text > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--target > div.lmt__textarea_container.lmt__textarea_container_no_shadow > div.lmt__translations_as_text > p.lmt__translations_as_text__item.lmt__translations_as_text__main_translation > button.lmt__translations_as_text__text_btn"
                    # "#target-dummydiv"
                    #"#dl_translator > div.lmt__sides_container > div.lmt__side_container.lmt__side_container--target > div.lmt__textarea_container > div.lmt__translations_as_text > p > button.lmt__translations_as_text__text_btn"
 

    '''
    WebDriverの処理がうまくいかなかったら1秒待機して再度WebDriverの処理を行う
    ただ、10回トライしてダメだったらエラーを返して関数処理終
    以下、WebDriver使うところでは同様の処理
    '''
    errCount=0
    f_succsess=False
    while not f_succsess:
        try: # DeepLにアクセス
            options = Options()
            options.add_argument('--headless')
            driver = webdriver.Chrome(options=options)  #  driver = webdriver.Chrome()
            driver.get(load_url)
            f_succsess = True
        except Exception  as identifier:
            errCount=errCount+1
            if errCount >=10:
                raise identifier
    
    #DeepLに英文を送る
    errCount=0
    f_succsess=False
    while not f_succsess:
        try: #DeepLに英文を送る
            driver.find_element_by_css_selector(input_selector).send_keys(mytext)
            f_succsess = True
        except Exception  as identifier:              
            errCount=errCount+1
            if errCount >=10:
                raise identifier
            time.sleep(1)

    #フラグ用
    Output_before = ""
    while 1:
        errCount=0
        f_succsess=False
        while not f_succsess:
            try:# DeepLの出力を取得する
                Output = driver.find_element_by_css_selector(Output_selector).get_attribute("textContent")
                f_succsess = True
            except Exception  as identifier:               
                errCount=errCount+1
                if errCount >=10:
                    raise identifier
                time.sleep(1) 
        '''
        取得したoutputが空文字なら、まだ翻訳が終了してないということで、1秒後に再チェック。
        取得したoutputが空文字でない場合、1つ前のoutputと比べて違う内容になってるなら、
        まだ翻訳が終わり切ってないということで1秒後に再チェック。
        取得したoutputが空文字でない場合、1つ前のoutputと同じ内容なら、翻訳終了ということで出力。
        '''        
        if Output != "" : #出力が空文字でないとすれば結果の出力が始まった
            if Output_before == Output:#出力が1つ前の出力と同じなら、出力が完了したってこと
                break
            Output_before = Output            
        time.sleep(1)

    #chromeを閉じる
    driver.close()
 
    #結果出力
    return Output
```