"BOKU"のITな日常

BOKUが勉強したり、考えたことを頭の整理を兼ねてまとめてます。

Python3でWebアプリケーションRPA(3)/自動的な値の参照や、スクロールなどの部品

f:id:arakan_no_boku:20190914133925j:plain

目次

前提

Python3とSeleniumを使います。

今、かかわっているシステムで使われているため、一部でjQueryを使っています。

インストール環境設定はできている前提で話をすすめます。

まだの場合は、こちらでインストールと環境設定を行う必要があります。

arakan-pgm-ai.hatenablog.com

記事は3回にわけて書いていきます

自分はSeleniumはよく使いますが、全面的な自動化は目指していません。

全面的な自動化は、自動化自体のテストや保守コストの負担が重いからです。

テストで何回も繰り返す面倒なところだけ部分的に素早く自動化する。

そのためのToolを作っていきます。

長くなるので、3回にわけます。

  • 1回目:自動的に入力・選択する部品(今回)
  • 2回目:自動的に何かをクリックする部品
  • 3回目:自動的な値の参照や、スクロールなどの部品

今回は、3回目です。  

自動的な値の参照や、スクロールなどの部品

最後なので、一回目・二回目で不足しているそのほかの部品を追加していきます。

部品1:画面を少しずつスクロール

Seleniumで入力を自動化して楽できる画面というのは、入力項目が多い画面です。

必然的に縦に長くなり、下の方の入力項目やボタンが隠れている場合が多いです。

隠れている項目を、Seleniumのsend_keys()とか、click()とかで操作しようとするとエラーになって失敗します。

人間の入力に近い感じになっているわけです。

そこで。

入力しながら、隠れている項目を見えるところにもってくるスクロールが、とても重要な働きをするわけです。

そんな時に使う部品です。

    def scroll_to(self, pitch):
        self.scroll_height = self.scroll_height + pitch
        self.driver.execute_script("window.scrollTo(0," + str(self.scroll_height) + ")")

これは指定した「pitch」分縦にスクロールさせるものです。

自分の環境だと、こんな感じで項目2行ごとに「60」スクロールさせてます。

auto.input_text('name_1', '漢字のなにか')
auto.input_text('name_2', '99999999')
auto.scroll_to(60)
auto.input_text_kana('name_3', 'カタカナデス')
auto.input_text_kana('name_4', 'コレモカタカナデス')
auto.scroll_to(60)
auto.select_by_value('name_5', '1')
auto.select_by_value('name_5', '2')

この60という数字に何かの法則性があるわけではなく、数字を変えながら、いろいろ試して、一番うまくいくのが、たまたま「60」だったというわけなので、他の環境で使う時は調整が必要なのであしからず。

部品2:最下行までスクロール

画面表示で一気に一番下までスクロールして、下に配置されているボタンを押したいという場合もあるので、そういうときには、こっちを使ってます。

    def scroll_to_bottom(self):
        self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

今のところ、この2種類でなんとかなってます。  

部品3:各エレメントのValue値の取得

値の参照と言えば、基本は「Value」です。

エレメントを取得して、そのvalue値を読みます。

 ただ。

Seleniumの機能だと画面から隠れている項目を選択しようとするとエラーになります。

入力の場合は、上記のようにスクロール表示させながらやっていくわけですが、参照の場合だと、入力し終わって一番下まで来てるときに、最初の項目の値を参照したい・・とかは普通にあります。

その一番上の項目が実はスクロールによって隠れてしまっている・・となると、ちょっと面倒なわけです。

なので、基本はJavaScriptでやってます。

    def get_value_by_name(self, name):
        return self.driver.execute_script("return document.getElementsByName('" + name + "')[0].value")

nameをセレクタに、valueを読みだしてます。

戻り値は、pythonの変数に受け取ることがでいます。

部品4:チェックボックスラジオボタンの選択状態取得

チェックボックスラジオボタンは上記のようにはいきません。

チェックしてるかしてないかは、checkedで読み取ります。

単一のチェックボックスの場合と、複数のチェックボックスがグループ化していて、その何番目のチェックボックスかをindexで指定する場合の2パターンです。

    def get_checked_by_name(self, name):
        return self.driver.execute_script("return document.getElementsByName('" + name + "')[0].checked")

    def get_checked_by_names(self, name, index):
        return self.driver.execute_script("return document.getElementsByName('" + name + "')[" + str(index) + "].checked")

TrueかFalseが返ってきます。 

 これもPython側の変数にうけとることができます。

部品5:Selectで選択中のValueの取得

Selectです。

これは「今、選択されている値」を取得しないと意味がないので、ちょっと違うアプローチになります。

    def get_selected_value_by_name(self, name):
        selected_index = self.driver.execute_script("return document.getElementsByName('" + name + "')[0].selectedIndex")
        return self.driver.execute_script("return document.getElementsByName('" + name + "').item(0).options[" + str(selected_index) + "].value")

選択されているindex(selectedindex)を受取、それをキーにしてValueを読むという2段階操作が必要になります。

これも、Python側で値を受け取れます。

自分は今のところ、この程度でなんとかなってます。 

部品6:ブラウザの強制終了

処理終了と同時に、ブラウザを閉じたい場合とかに使います。

例えば、複数の操作やテストをバッチにして繰り返してやるときとかは、ブラウザが閉じていないとエラーになったりするので、これを最後にいれて、毎回閉じるようにして続ける必要があります。

    def quit(self):
        self.driver.quit()

ツールクラスのソース全体です

ソース全文です。

my_rpa_tools.py

from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait as wait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC


class MyRpaTools():
    def __init__(self, web_driver):
        self.driver = web_driver
        self.scroll_height = 0

    def input_text(self, name, value):
        element = self.driver.find_element_by_xpath(
            "//input[@name='" + name + "'][@type='text']")
        element.send_keys(value)

    def input_number(self, name, value):
        element = self.driver.find_element_by_xpath(
            "//input[@name='" + name + "'][@type='number']")
        element.send_keys(value)

    def input_by_name(self, name, value):
        self.driver.execute_script(
            "document.getElementsByName('" + name + "')[0].value = '" + value + "';")

    def input_by_names(self, name, value, index):
        self.driver.execute_script(
            "document.getElementsByName('" + name + "')[" + index + "].value = '" + value + "';")

    def input_text_kana(self, name, value):
        self.input_by_name(name, value)

    def input_password(self, name, value):
        element = self.driver.find_element_by_xpath(
            "//input[@name='" + name + "'][@type='password']")
        element.send_keys(value)

    def input_textarea(self, name, value):
        element = self.driver.find_element_by_xpath(
            "//textarea[@name='" + name + "']")
        element.send_keys(value)

    def input_datepicker_current_month(self, name, select_day):
        self.driver.find_element_by_name(name).click()
        wait(self.driver, 10).until(EC.visibility_of_element_located(
            (By.XPATH, "//td[@data-handler='selectDay']/a[text()='" + select_day + "']"))).click()

    def input_direct_by_id(self, id, value):
        self.driver.execute_script(
            "$('#" + id + "').val('" + value + "');")

    # 2004年9月1日(水) のフォーマットで入力すること
    def input_datepicker_dairect(self, id, value):
        self.driver.execute_script(
            "$('#" + id + "').datepicker().datepicker('setDate','" + value + "');")
     
    def select_by_value(self, name, value):
        element = self.driver.find_element_by_name(name)
        element_select = Select(element)
        element_select.select_by_value(value)

    def click_submit(self, name):
        element = self.driver.find_element_by_xpath(
            "//input[@name='" + name + "'][@type='submit']")
        element.click()

    def click_button(self, name):
        self.driver.find_element_by_xpath(
            "//input[@type='button'][@name='" + name + "']").click()
    
    def click_checkbox(self, name):
        self.driver.find_element_by_xpath(
            "//input[@type='checkbox'][@name='" + name + "']").click()

    def click_by_name(self, name):
        self.driver.execute_script(
            "document.getElementsByName('" + name + "')[0].click();")

    def click_by_names(self, name, index):
        self.driver.execute_script(
            "document.getElementsByName('" + name + "')[" + index + "].click();")

    def click_link_titled(self, name):
        element = self.driver.find_element_by_xpath("//a[@title='" + name + "']")
        element.click()

    def click_link_by_text(self, text):
        element = self.driver.find_element_by_link_text(text)
        element.click()

    def click_link_by_index(self, index):
        self.driver.execute_script("document.getElementsByTagName('a')[" + str(index) + "].click();")
        
    def execute_script(self, scr):
        self.driver.execute_script(scr)

    def scroll_to(self, pitch):
        self.scroll_height = self.scroll_height + pitch
        self.driver.execute_script("window.scrollTo(0," + str(self.scroll_height) + ")")

    def scroll_to_bottom(self):
        self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

    def get_value_by_name(self, name):
        return self.driver.execute_script("return document.getElementsByName('" + name + "')[0].value")

    def get_checked_by_name(self, name):
        return self.driver.execute_script("return document.getElementsByName('" + name + "')[0].checked")

    def get_checked_by_names(self, name, index):
        return self.driver.execute_script("return document.getElementsByName('" + name + "')[" + str(index) + "].checked")

    def get_selected_value_by_name(self, name):
        selected_index = self.driver.execute_script("return document.getElementsByName('" + name + "')[0].selectedIndex")
        return self.driver.execute_script("return document.getElementsByName('" + name + "').item(0).options[" + str(selected_index) + "].value")

    def quit(self):
        self.driver.quit()

内部で、self.driverに引数のweb_driverをうけて各メソッドの中で使っていきます。

セレクタは特に理由ない限り、「name」を使っています。

ただ、内部でjQueryを使っているものは区別のために「id」を使ってます。

nameを使う理由は、自分の環境と今かかわっているプロジェクトの規約ではそれが適切だからです。

適切なセレクタがどれか・・というのは、自動化対象になるアプリケーションや規約によって変わると思いますので、nameが適切でない環境で使うときは、適宜読み替えてください。 

一回目・二回目へのリンク

個人的なTipsを3回にわけて書きました。

1回目

arakan-pgm-ai.hatenablog.com

2回目

arakan-pgm-ai.hatenablog.com

です。 

seleniumのインストールやWEBドライバの取得等は、こちらの記事にまとめてます。

arakan-pgm-ai.hatenablog.com

今回はこんなところで。

ではでは。

#python