[python] seleniumをつかったdeepL翻訳の自動化

# 説明
pythonのseleniumをつかってdeepLという翻訳サイトを操作してファイルの中身を自動的に翻訳できたらよいと思って作ったもの。
動作はインターネット環境に大きく影響を受けます。
# 実行環境
* Intel(R) Core(TM) i7-8700 6コア12スレッド
* メモリ16GB
* windows10 64bit 
* 仮想8コア、物理4コア
* Python 3.8.11
* ANACONDA
* JupyterLab 3.1.7

#コード
## モジュール
```python:modules
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup
import time
import urllib.parse
import os
from joblib import Parallel, delayed
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
import pandas as pd
from tika import parser
import glob
import subprocess
import psutil
#使わないものあり
```
## ブラウザーをセットアップする関数

```python:strat_browser
def strat_browser(path="chromedriver.exe",headless=True,Tor=None):
    import os
    from selenium import webdriver
    if path[-16:]=="chromedriver.exe":
        if os.path.exists(path):
            from selenium.webdriver.chrome.options import Options
            options = Options()
            if headless:
                options.add_argument('--headless')
            if Tor:
                proc = subprocess.Popen(Tor)
                # Torブラウザの設定
                print("You need to have your Tor browser running")
                options.add_argument("--proxy-server=socks5://127.0.0.1:9150")
            return webdriver.Chrome(path, options=options)
        else:
            raise FileNotFoundError("chromedriver.exe does not exist in the location specified by path")
    elif path[-15:]=="geckodriver.exe":
        if os.path.exists(path):
            from selenium.webdriver.firefox.options import Options
            options = Options()
            if headless:
                options.add_argument('--headless')
            if Tor:
                # Torブラウザ不可
                import warnings
                warnings.warn("You can't use firefox and the Tor browser at the same time")
            return webdriver.Firefox(executable_path=path, options=options)
        else:
            raise FileNotFoundError("geckodriver.exe does not exist in the location specified by path")
    else:
        raise NameError("Please specify the correct driver path")
```

**`driver = strat_browser(path="chromedriver.exe",headless=True,Tor=None)`**
firefoxで起動したりcharomeで起動したりといったときに毎回書き直すのがめんどうだったのでつくった
>**path ="chromedriver.exe"**<br>chromeを起動するために使うchromedriver.exeかfirefoxを起動するために使うgeckodriver.exeのパス。パスを見て　chromeとfirefoxのどちらを起動すればよいのか判断する
**driver = strat_browser(path="chromedriver.exe",headless=True,Tor=None)**

>**headless=True"**<br>headlessモードで実行するかどうかを決める。

>**Tor=None"**<br>使いたい場合はここにTorブラウザーのfirefox.exeへのパスを入れる。そうするとバックでTorが起動する。Torを起動するとIPアドレスが変わることを利用するため。Firefoxでは使えないので注意。

## 特定の名前のプロセスをすべて削除するための関数
```python:task_kill
def task_kill(name):
    dict_pids = {
        p.info["pid"]: p.info["name"]
        for p in psutil.process_iter(attrs=["pid", "name"])
    }
    tor = []
    for i in range(len(dict_pids)):
        if list(dict_pids.values())[i] == name:
            p = psutil.Process(list(dict_pids.keys())[i])
            p.terminate()  #or p.kill()
    return None
```
**`task_kill(name)`**
これを利用して先ほどのstrat_browser()でTorを起動していた場合にそれを消す。
task_kill("firefox.exe")で消える
> **name**<br> プロセス名

## DeepL用Class
```python:deepL_lang
class deepL_lang:
    def __init__(self,lang,path="chromedriver.exe",headless=True,Tor=None,reload=None):
        self.path = path
        self.lang=lang
        self.headless = headless
        self.Tor = Tor
        self.reload = reload
        self.url = "https://www.deepl.com/"+lang+"/translator"
        self.driver = strat_browser(path,headless,self.Tor)
        self.driver.get(self.url)
    def get_translated_text_long(self,from_text):
        join_let=""#元通りがよいなら/nで
        if self.Tor:
            self.restrat_Tor()
        if self.reload:
            self.reload_driver()
        text = from_text.split('\n')
        max_len = max(map(len, text))
        f = 4000 // max_len
        a = len(text)%f
        print(len(text)//f+1,"times")
        for i in range(len(text)//f):
            text = text[:i]+[join_let.join(text[i:i+f])]+text[i+f:]
        text = text[:-a]+[join_let.join(text[-a:])]
        return join_let.join([self.get_translated_text(i) for i in text])
    def get_translated_text(self,from_text):
        if type(from_text)==int or from_text==None or from_text.isspace():
            return from_text
        if self.reload:
            self.reload_driver()
        self.driver.implicitly_wait(10)
        wait = WebDriverWait(self.driver, 10)
        texts = wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "lmt__source_textarea")))
        texts.clear()
        texts.send_keys(from_text)
        time.sleep(2)
        text_f = self.driver.find_element_by_class_name("lmt__source_textarea")
        wait.until(EC.visibility_of_element_located((By.CLASS_NAME, "lmt__language_select__active")))
        lan = self.driver.find_elements_by_class_name("lmt__language_select__active")[1]
        if text_f.get_attribute("lang")[:2]==self.lang:
            return from_text
        self.driver.execute_script("arguments[0].click();", lan)
        if self.lang == "en":
            lan_en = wait.until(EC.visibility_of_element_located((By.XPATH, "//button[@dl-test='translator-lang-option-en-GB']")))
            if len(lan_en)>0:
                lan_en = lan_en[0]
            else:
                lan_en = wait.until(EC.visibility_of_element_located((By.XPATH, "//button[@dl-test='translator-lang-option-en-EN']")))
            self.driver.execute_script("arguments[0].click();", lan_en)
            #lan_en.click()
        else:
            lan_en = wait.until(EC.visibility_of_element_located((By.XPATH, "//button[@dl-test='translator-lang-option-{}-{}']".format(self.lang,self.lang.upper()))))
            self.driver.execute_script("arguments[0].click();", lan_en)
        ans = None
        while ans==None or "[...]" in ans or len(self.driver.find_elements_by_class_name("loadingIndicator_module_loadingIndicator__e5ced9d0"))>0:
            ans = self.get_text_from_page_source("lmt__translations_as_text__text_btn")
        return ans
    def get_text_from_page_source(self,class_name):
        time_sta = time.perf_counter()
        while True:
            html = self.driver.page_source
            soup = BeautifulSoup(html, features='lxml')
            target_elem = soup.find(class_=class_name)
            to_text = target_elem.text
            if to_text:
                #print(to_text)
                break
            elif time.perf_counter()-time_sta>10:
                to_text="non response for 10s"
                #print(to_text)
                break
        return to_text
    def reload_driver(self):
        self.driver.quit()
        self.driver = strat_browser(self.path,self.headless)
        self.driver.get(self.url)
    def restrat_Tor(self):
        task_kill("firefox.exe")
        proc = subprocess.Popen(self.Tor)
        time.sleep(10)
    def __del__(self):
        if self.Tor:
            task_kill("firefox.exe")
        self.driver.quit()
```
**`translate=deepL_lang(lang,path="chromedriver.exe",headless=True,Tor=None,reload=None)`**
クラス本体。ブラウザの種類や表示、非表示、Torの使用、ページを毎回再読み込みするかを設定。
翻訳する前の文章が何語かは勝手に判断する
> **lang** <br> 翻訳先の言語を英小文字2字で示す。
- 英語：en
- 日本語：ja
- 中国語：zh
- ロシア語：ru
- ルーマニア語：ro
- リトアニア語：lt
- ラトビア語：lv
- ポルトガル語：pt
- ポーランド語：pl
- ブルガリア語：bg
- フランス語：fr
- フィンランド語：fi
- ハンガリー語：hu
- ドイツ語：de
- デンマーク語：da
- チェコ語：cs
- スロベニア語：sl
- スロバキア語：sk
- スペイン語：es
- スウェーデン語：sv
- ギリシャ語：el
- オランダ語：nl
- エストニア語：et
- イタリア語：it
**path**<br>chromeを起動するために使うchromedriver.exeかfirefoxを起動するために使うgeckodriver.exeのパス。パスを見て　chromeとfirefoxのどちらを起動すればよいのか判断する。firefoxだとTorは使えないので注意
**headless=True**<br>実行している際にブラウザーを表示するかどうか、つまりヘッドレスモードで実行するかどうか。デフォルトは表示しない
**Tor=None**<br>Torブラウザーをバックで起動するかどうかの設定。firefoxでは使えない
**reload=None**<br>毎回サイトを再読み込みするかどうかの設定。Trueにすると翻訳を一回かけるたびにdeepLのサイトを再読み込みする。

**`text = translate.get_translated_text(from_text)`**
主に使う関数。from_textを上記で設定した言語に翻訳した結果を返す
**`text = translate.get_translated_text_long(from_text)`**
5000文字を超えそうな長い文章を翻訳するときに使う。入力された文章を5000文字を超えないように改行で適当に区切り翻訳したうえでつなぎ合わせる。いまのスクリプトだと改行は翻訳後全部消える
**`translate.reload_driver()`**
ブラウザを再読み込みする

**`translate.restrat_Tor()`**
Torを再起動する

**`del translate`**
プログラムの最後に作成したclassを削除することで同時に起動してあったブラウザやTorが閉じて綺麗に終われる

##使用例　フォルダの中のPDFファイルをすべて日本語に翻訳する
```python:data
pdfs = "PDFが入ってるフォルダのパス"
files = glob.glob(pdfs)
df = pd.DataFrame(files)
df[1] = [parser.from_file(i)["content"] for i in files]
#一列目にファイル名、二列目にファイルの内容が格納された。三列目に翻訳された文章を入れる
ja = deepL_lang("ja",headless=None,reload=True)#reloadをするのはブラウザのエラー防止
for i in range(len(df[1])):
    df.iat[i, 2] = ja.get_translated_text_long(df.iat[i, 1])
    print(i+1,"個めのファイルの処理終了")
df.to_csv('pdf_out.csv')#pdf_out.csvに出力
del ja
```